From 391fb5cbe7aa5e69de91e2c44a09169c8787b385 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 10:25:52 -0700 Subject: [PATCH 01/35] docs: add examples guides across public surfaces --- docs/api/examples.md | 107 ++ docs/api/index.md | 4 + docs/fastdata/kv/examples.md | 141 +++ docs/fastdata/kv/index.md | 4 + docs/neardata/examples.md | 142 +++ docs/neardata/index.md | 4 + docs/rpc/examples.md | 132 +++ docs/rpc/index.md | 4 + docs/snapshots/examples.mdx | 143 +++ docs/snapshots/index.mdx | 4 + docs/transfers/examples.md | 101 ++ docs/transfers/index.md | 4 + docs/tx/examples.md | 181 ++++ docs/tx/index.md | 4 + .../current/api/examples.md | 107 ++ .../current/api/index.md | 4 + .../current/fastdata/kv/examples.md | 141 +++ .../current/fastdata/kv/index.md | 4 + .../current/neardata/examples.md | 142 +++ .../current/neardata/index.md | 4 + .../current/rpc/examples.md | 132 +++ .../current/rpc/index.md | 4 + .../current/snapshots/examples.mdx | 143 +++ .../current/snapshots/index.mdx | 4 + .../current/transfers/examples.md | 101 ++ .../current/transfers/index.md | 4 + .../current/tx/examples.md | 181 ++++ .../current/tx/index.md | 4 + i18n/ru/translation-policy.yml | 7 + sidebars.js | 7 +- static/ru/api.md | 4 + static/ru/api/examples.md | 99 ++ static/ru/api/examples/index.md | 99 ++ static/ru/api/index.md | 4 + static/ru/fastdata/kv.md | 4 + static/ru/fastdata/kv/examples.md | 133 +++ static/ru/fastdata/kv/examples/index.md | 133 +++ static/ru/fastdata/kv/index.md | 4 + static/ru/guides/llms.txt | 7 + static/ru/llms-full.txt | 974 ++++++++++++++++++ static/ru/llms.txt | 7 + static/ru/neardata.md | 4 + static/ru/neardata/examples.md | 134 +++ static/ru/neardata/examples/index.md | 134 +++ static/ru/neardata/index.md | 4 + static/ru/rpc.md | 4 + static/ru/rpc/examples.md | 124 +++ static/ru/rpc/examples/index.md | 124 +++ static/ru/rpc/index.md | 4 + static/ru/snapshots.md | 4 + static/ru/snapshots/examples.md | 134 +++ static/ru/snapshots/examples/index.md | 134 +++ static/ru/snapshots/index.md | 4 + static/ru/structured-data/site-graph.json | 91 ++ static/ru/transfers.md | 4 + static/ru/transfers/examples.md | 93 ++ static/ru/transfers/examples/index.md | 93 ++ static/ru/transfers/index.md | 4 + static/ru/tx.md | 4 + static/ru/tx/examples.md | 173 ++++ static/ru/tx/examples/index.md | 173 ++++ static/ru/tx/index.md | 4 + 62 files changed, 4878 insertions(+), 1 deletion(-) create mode 100644 docs/api/examples.md create mode 100644 docs/fastdata/kv/examples.md create mode 100644 docs/neardata/examples.md create mode 100644 docs/rpc/examples.md create mode 100644 docs/snapshots/examples.mdx create mode 100644 docs/transfers/examples.md create mode 100644 docs/tx/examples.md create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md create mode 100644 static/ru/api/examples.md create mode 100644 static/ru/api/examples/index.md create mode 100644 static/ru/fastdata/kv/examples.md create mode 100644 static/ru/fastdata/kv/examples/index.md create mode 100644 static/ru/neardata/examples.md create mode 100644 static/ru/neardata/examples/index.md create mode 100644 static/ru/rpc/examples.md create mode 100644 static/ru/rpc/examples/index.md create mode 100644 static/ru/snapshots/examples.md create mode 100644 static/ru/snapshots/examples/index.md create mode 100644 static/ru/transfers/examples.md create mode 100644 static/ru/transfers/examples/index.md create mode 100644 static/ru/tx/examples.md create mode 100644 static/ru/tx/examples/index.md diff --git a/docs/api/examples.md b/docs/api/examples.md new file mode 100644 index 0000000..96a7ed1 --- /dev/null +++ b/docs/api/examples.md @@ -0,0 +1,107 @@ +--- +sidebar_label: Examples +slug: /api/examples +title: FastNear API Examples +description: Plain-language workflows for using FastNear API docs for account summaries, key lookups, and asset-specific follow-up. +displayed_sidebar: fastnearApiSidebar +page_actions: + - markdown +--- + +# FastNear API Examples + +Use this page when the user wants a readable account- or asset-shaped answer and you want the shortest path through the FastNear API docs. Start with the smallest endpoint that can answer the question, then widen only if you need canonical RPC detail or indexed history. + +## When to start here + +- The user wants balances, holdings, staking, or a broad wallet-style account summary. +- You need to resolve a public key to one or more accounts. +- The answer should look like application data, not raw JSON-RPC envelopes. +- You want a fast first answer before deciding whether canonical RPC detail is necessary. + +## Minimum inputs + +- network: mainnet or testnet +- primary identifier: `account_id` or public key +- whether the user wants a broad summary or one specific asset family +- whether you may need exact canonical follow-up or recent activity history afterward + +## Common jobs + +### Get a wallet-style account summary + +**Start here** + +- [V1 Full Account View](/api/v1/account-full) for the broadest account snapshot. + +**Next page if needed** + +- [V1 Account FT](/api/v1/account-ft), [V1 Account NFT](/api/v1/account-nft), or [V1 Account Staking](/api/v1/account-staking) for narrower follow-up. +- [Transactions API account history](/tx/account) if the next question becomes "how did this account get here?" + +**Stop when** + +- The summary already answers the holdings or portfolio question in the shape the user wanted. + +**Widen when** + +- The user asks for exact canonical account or access-key semantics. Move to [RPC Reference](/rpc). +- The user asks for activity or execution history rather than current holdings. Move to [Transactions API](/tx). + +### Resolve a public key to one or more accounts + +**Start here** + +- [V1 Public Key Lookup](/api/v1/public-key) when you want the primary account match. +- [V1 Public Key Lookup All](/api/v1/public-key-all) when you need the broader set of associated accounts. + +**Next page if needed** + +- [V1 Full Account View](/api/v1/account-full) after resolution if the user immediately wants balances or holdings for the returned accounts. + +**Stop when** + +- You have identified the account or accounts that belong to the key. + +**Widen when** + +- The user starts asking about exact access-key permissions, nonces, or canonical key state. Move to [View Access Key](/rpc/account/view-access-key) or [View Access Key List](/rpc/account/view-access-key-list). +- The user wants recent activity for the resolved accounts rather than just identity resolution. Move to [Transactions API](/tx). + +### Follow one asset family instead of the whole account + +**Start here** + +- [V1 Account FT](/api/v1/account-ft) for fungible-token balances. +- [V1 Account NFT](/api/v1/account-nft) for NFT holdings. +- [V1 Account Staking](/api/v1/account-staking) for staking positions. + +**Next page if needed** + +- [V1 Full Account View](/api/v1/account-full) if the user later wants the broader account picture. +- [Transactions API account history](/tx/account) if the user asks how those holdings changed over time. + +**Stop when** + +- The asset-specific endpoint already answers the product question without extra reconstruction. + +**Widen when** + +- The indexed view is not enough and the user needs exact on-chain semantics. Move to [RPC Reference](/rpc). +- The question becomes historical or execution-oriented instead of "what does this account hold now?" Move to [Transactions API](/tx). + +## Common mistakes + +- Leading with the broad account snapshot when the user only asked about one asset family. +- Using FastNear API when the user explicitly needs canonical RPC fields or permissions. +- Staying in account-summary pages after the question turns into transaction history. +- Forgetting that `?network=testnet` works only on compatible pages. + +## Related guides + +- [FastNear API](/api) +- [API Reference](/api/reference) +- [RPC Reference](/rpc) +- [Transactions API](/tx) +- [Choosing the Right Surface](/agents/choosing-surfaces) +- [Agent Playbooks](/agents/playbooks) diff --git a/docs/api/index.md b/docs/api/index.md index febc986..75b3375 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -56,6 +56,10 @@ https://test.api.fastnear.com - [V1 public key](/api/v1/public-key) when you need account resolution from a key. - [V1 FT top holders](/api/v1/ft-top) for token-distribution views. +## Need a workflow? + +Use [FastNear API Examples](/api/examples) for plain-language flows like account summaries, key-to-account resolution, and asset-specific follow-up. + ## Troubleshooting ### I only need one low-level value from chain state diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md new file mode 100644 index 0000000..b319c48 --- /dev/null +++ b/docs/fastdata/kv/examples.md @@ -0,0 +1,141 @@ +--- +sidebar_label: Examples +slug: /fastdata/kv/examples +title: KV FastData Examples +description: Plain-language workflows for using KV FastData docs for exact keys, key history, predecessor-scoped inspection, and canonical RPC follow-up. +displayed_sidebar: kvFastDataSidebar +page_actions: + - markdown +--- + +# KV FastData Examples + +Use this page when the question is about indexed contract storage and you already have a precise scope in mind. The key decision on this surface is choosing the narrowest useful scope first: exact key, account, predecessor, or batch of known keys. Stay within KV FastData while the answer is still about indexed key-value data, then widen to RPC only when canonical on-chain state is required. + +## When to start here + +- You want indexed contract storage instead of broad account or asset views. +- You already know a contract, exact key, predecessor, or account scope. +- You need latest indexed rows or indexed history over time. +- You want faster storage-oriented answers before deciding whether canonical RPC state is necessary. + +## Minimum inputs + +- network +- contract ID plus one of: exact key, account scope, predecessor scope, or known set of keys +- whether you need latest indexed state or historical changes +- whether canonical follow-up may be required + +## Common jobs + +### Look up one exact key right now + +**Start here** + +- [GET Latest by Exact Key](/fastdata/kv/get-latest-key) when one fully qualified key is already known. + +**Next page if needed** + +- [GET History by Exact Key](/fastdata/kv/get-history-key) if the question becomes “how did this key change?” + +**Stop when** + +- The latest indexed row already answers the storage question. + +**Widen when** + +- The user needs exact current chain state rather than indexed storage. Move to [View State](/rpc/contract/view-state). + +### Turn one exact key into a change history + +**Start here** + +- [GET History by Exact Key](/fastdata/kv/get-history-key) for path-based history lookup. +- [History by Key](/fastdata/kv/history-by-key) when the fully qualified key route is the better fit. + +**Next page if needed** + +- Revisit [GET Latest by Exact Key](/fastdata/kv/get-latest-key) if you want the current indexed value alongside the history. + +**Stop when** + +- You can explain how the key changed over time. + +**Widen when** + +- The user asks whether the latest indexed value matches canonical on-chain state right now. + +### Trace writes from one predecessor + +**Start here** + +- [All by Predecessor](/fastdata/kv/all-by-predecessor) for latest rows across contracts touched by one predecessor. +- [History by Predecessor](/fastdata/kv/history-by-predecessor) when you need the write history over time. + +**Next page if needed** + +- Narrow to an exact key if one row becomes the real focus. + +**Stop when** + +- You can answer what this predecessor changed and where. + +**Widen when** + +- The user stops asking about indexed writes and starts asking about present canonical state. + +### Batch-check several known keys + +**Start here** + +- [Multi Lookup](/fastdata/kv/multi) when you already know a fixed set of fully qualified keys. + +**Next page if needed** + +- Move one interesting key to [GET History by Exact Key](/fastdata/kv/get-history-key) if the batch result raises a historical question. + +**Stop when** + +- The batch response already answers which of the keys matter. + +**Widen when** + +- The user wants broader contract inspection instead of a known set of keys. + +## Worked investigation + +### Start with one indexed key, then confirm history and canonical state + +Use this investigation when one contract key looks suspicious and you need to connect its latest indexed value, indexed history, and canonical `view_state` follow-up into one clear story. + +**Goal** + +- Explain what a contract key looks like now, how it got there in indexed history, and whether the canonical RPC state agrees. + +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Latest indexed value | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Fetch the newest indexed row for the exact key first | Gives the fastest narrow answer before widening into history | +| Indexed key history | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) or [`history-by-key`](/fastdata/kv/history-by-key) | Pull the same key’s change history over time | Shows whether the current value is stable, recent, or part of a suspicious sequence | +| Scope expansion | KV FastData [`latest-by-account`](/fastdata/kv/latest-by-account) or [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Widen to account or predecessor scope if the one key is only part of a larger pattern | Helps explain whether the key changed in isolation or as part of a wider write set | +| Canonical confirmation | RPC [`view_state`](/rpc/contract/view-state) | Confirm the current on-chain state once the indexed pattern is clear | Separates indexed storage history from exact current chain state | + +**What a useful answer should include** + +- the exact key and contract scope investigated +- the latest indexed value and what changed in history +- whether canonical `view_state` matched the indexed current value + +## Common mistakes + +- Starting with broad account or predecessor scans when an exact key is already known. +- Using KV FastData when the user really wants balances or holdings. +- Confusing indexed history with exact current chain state. +- Reusing pagination tokens or changing filters mid-scan. + +## Related guides + +- [KV FastData API](/fastdata/kv) +- [RPC Reference](/rpc) +- [FastNear API](/api) +- [Choosing the Right Surface](/agents/choosing-surfaces) +- [Agent Playbooks](/agents/playbooks) diff --git a/docs/fastdata/kv/index.md b/docs/fastdata/kv/index.md index 9d74a9d..7fa6a52 100644 --- a/docs/fastdata/kv/index.md +++ b/docs/fastdata/kv/index.md @@ -51,6 +51,10 @@ Use [FastNear API](/api) for higher-level account views, [NEAR Data API](/nearda - [All by Predecessor](/fastdata/kv/all-by-predecessor) or [History by Predecessor](/fastdata/kv/history-by-predecessor) when the predecessor is the right scope - [Multi Lookup](/fastdata/kv/multi) when you already know several exact keys +## Need a workflow? + +Use [KV FastData Examples](/fastdata/kv/examples) for plain-language flows like exact-key lookups, key-history investigation, predecessor-scoped inspection, and canonical RPC follow-up. + ## Default workflow 1. Pick the narrowest scope that matches the user's question. diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md new file mode 100644 index 0000000..ff9f16c --- /dev/null +++ b/docs/neardata/examples.md @@ -0,0 +1,142 @@ +--- +sidebar_label: Examples +slug: /neardata/examples +title: NEAR Data API Examples +description: Plain-language workflows for using NEAR Data API docs for polling, redirect helpers, and escalation to canonical RPC inspection. +displayed_sidebar: nearDataApiSidebar +page_actions: + - markdown +--- + +# NEAR Data API Examples + +Use this page when freshness matters more than protocol-native exactness. NEAR Data API is for polling and recent block-family reads: start with the freshest or most stable block mode that matches the job, stay on the polling-oriented surface as long as it answers the question, and widen to RPC only when canonical block or state semantics become necessary. + +## When to start here + +- You want recent optimistic or finalized block-family data. +- You are building a polling client, monitor, or freshness check. +- Redirect helpers are acceptable or useful in your client flow. +- The job is about “what changed recently?” rather than canonical historical confirmation. + +## Minimum inputs + +- network +- freshness mode: optimistic or finalized +- whether you have a specific height/hash or want the latest block-family object +- whether the client can follow redirects cleanly +- whether a later RPC follow-up may be required + +## Common jobs + +### Monitor the optimistic head + +**Start here** + +- [Optimistic block](/neardata/block-optimistic) for the freshest block-family read. + +**Next page if needed** + +- [Last optimistic block redirect](/neardata/last-block-optimistic) if your client wants a helper route that always points at the newest optimistic block. + +**Stop when** + +- You can report the latest optimistic head or detect freshness drift. + +**Widen when** + +- The user needs finalized stability instead of maximum freshness. Move to [Final block by height](/neardata/block) or [Last final block redirect](/neardata/last-block-final). + +### Track finalized block progress safely + +**Start here** + +- [Final block by height](/neardata/block) when you already know the height you want to confirm. +- [Block headers](/neardata/block-headers) when header-level polling is enough. + +**Next page if needed** + +- [Last final block redirect](/neardata/last-block-final) when the client should follow the newest finalized block without computing the height first. + +**Stop when** + +- You can show finalized progress without pulling in deeper protocol detail. + +**Widen when** + +- The user needs exact canonical block fields or transaction semantics. Move to [RPC Reference](/rpc). + +### Use redirect helpers in a polling client + +**Start here** + +- [Last final block redirect](/neardata/last-block-final) or [Last optimistic block redirect](/neardata/last-block-optimistic) depending on the freshness requirement. + +**Next page if needed** + +- Follow the canonical target returned by the helper and continue reading the block-family payload there. + +**Stop when** + +- The client can reliably follow the helper route and consume the final block resource. + +**Widen when** + +- Redirect behavior itself becomes a problem for the client. Move to the direct block routes instead. + +### Escalate from fresh block polling to canonical RPC inspection + +**Start here** + +- Use the relevant NEAR Data block route to find the recent block or block-family event of interest. + +**Next page if needed** + +- [Block by Height](/rpc/block/block-by-height), [Block by ID](/rpc/block/block-by-id), or another RPC method once you know the exact block or follow-up object you need. + +**Stop when** + +- You can clearly name the recent block that deserves canonical follow-up. + +**Widen when** + +- The user asks for exact protocol-native structure, not just freshness-oriented reads. + +## Worked investigation + +### Start with an optimistic block, then confirm the finalized and canonical story + +Use this investigation when you need early detection from the optimistic head, but the final answer still needs a stable finalized view and, sometimes, canonical RPC confirmation. + +**Goal** + +- Catch a recent change quickly, then narrow it into a finalized and canonical block story without overfetching. + +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Fastest detection | NEAR Data [`block-optimistic`](/neardata/block-optimistic) | Poll optimistic block reads to notice a new block-family change as early as possible | Gives the earliest useful signal before finalized confirmation exists | +| Latest optimistic helper | NEAR Data [`last-block-optimistic`](/neardata/last-block-optimistic) | Use the redirect helper when the client should always follow the newest optimistic target | Keeps the polling client simple when “latest” matters more than explicit heights | +| Stable confirmation | NEAR Data [`block`](/neardata/block) or [`last-block-final`](/neardata/last-block-final) | Re-check the same block family once finality catches up | Confirms that the observed optimistic change survived into finalized history | +| Light block summary | NEAR Data [`block-headers`](/neardata/block-headers) | Read header-level data if only timing or progression is needed | Avoids wider block payloads when header-level confirmation is enough | +| Canonical follow-up | RPC [Block by ID](/rpc/block/block-by-id) or [Block by Height](/rpc/block/block-by-height) | Fetch the exact canonical block once you know which one matters | Moves from freshness-oriented reads to protocol-native confirmation only when necessary | + +**What a useful answer should include** + +- which optimistic observation first triggered the investigation +- when the same observation became finalized +- whether canonical RPC inspection changed the interpretation + +## Common mistakes + +- Treating NEAR Data API as a streaming product instead of a polling surface. +- Starting with canonical RPC when the real need is a recent block monitor. +- Forgetting that redirect helpers may return `401` before redirecting if the key is invalid, or may be awkward for some HTTP clients. +- Staying on NEAR Data when the user has already asked for exact protocol-native block details. + +## Related guides + +- [NEAR Data API](/neardata) +- [RPC Reference](/rpc) +- [Transactions API](/tx) +- [Choosing the Right Surface](/agents/choosing-surfaces) +- [Agent Playbooks](/agents/playbooks) diff --git a/docs/neardata/index.md b/docs/neardata/index.md index 26c6495..968a2a0 100644 --- a/docs/neardata/index.md +++ b/docs/neardata/index.md @@ -45,6 +45,10 @@ https://testnet.neardata.xyz - [Final block by height](/neardata/block) and [Block headers](/neardata/block-headers) for finalized block-family queries. - [Last final block redirect](/neardata/last-block-final) and [Last optimistic block redirect](/neardata/last-block-optimistic) when you want helper redirects. +## Need a workflow? + +Use [NEAR Data API Examples](/neardata/examples) for plain-language flows like optimistic polling, finalized confirmation, redirect helpers, and escalation into canonical RPC inspection. + ## Troubleshooting ### Some endpoints redirect instead of returning the final payload directly diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md new file mode 100644 index 0000000..f415be9 --- /dev/null +++ b/docs/rpc/examples.md @@ -0,0 +1,132 @@ +--- +sidebar_label: Examples +slug: /rpc/examples +title: RPC Examples +description: Plain-language workflows for using FastNear RPC docs for exact state checks, block inspection, contract views, and transaction submission. +displayed_sidebar: rpcSidebar +page_actions: + - markdown +--- + +# RPC Examples + +Use this page when you already know the answer needs canonical RPC behavior and you want the shortest doc path to get there. The goal is not to memorize every method. It is to pick the right starting page, stop as soon as the RPC result answers the question, and widen only when a higher-level surface would help. + +## When to start here + +- The user asked for exact on-chain state or protocol-native fields. +- You need a direct contract view call or transaction submission flow. +- You are inspecting blocks, chunks, validators, or protocol metadata. +- Correctness depends on node semantics rather than indexed summary data. + +## Minimum inputs + +- network: mainnet or testnet +- primary identifier: `account_id`, public key, contract ID plus method, transaction hash, or block height/hash +- whether you need current state, historical state, or submission/finality behavior +- whether the result should stay canonical or become a human-friendly summary afterward + +## Common jobs + +### Check exact account or access-key state + +**Start here** + +- [View Account](/rpc/account/view-account) for canonical account fields. +- [View Access Key](/rpc/account/view-access-key) or [View Access Key List](/rpc/account/view-access-key-list) for key inspection. + +**Next page if needed** + +- [FastNear API full account view](/api/v1/account-full) if you need a wallet-style summary after confirming the canonical state. +- [Transactions API account history](/tx/account) if the next question is "what has this account been doing?" + +**Stop when** + +- The RPC fields already answer the state or permission question. + +**Widen when** + +- The user wants balances, NFTs, staking, or other product-shaped output. +- The user really wants recent activity history rather than current canonical state. + +### Inspect a block or protocol snapshot + +**Start here** + +- [Block by ID](/rpc/block/block-by-id) or [Block by Height](/rpc/block/block-by-height) for a specific block. +- [Latest Block](/rpc/protocol/latest-block) for the current canonical head. +- [Status](/rpc/protocol/status), [Health](/rpc/protocol/health), or [Network Info](/rpc/protocol/network-info) for node and network diagnostics. + +**Next page if needed** + +- [Block Effects](/rpc/block/block-effects) if the block lookup needs state-change context. +- [Transactions API block history](/tx/block) or [Transactions API block range](/tx/blocks) if you need a more readable execution window. + +**Stop when** + +- The canonical block or protocol payload answers the question directly. + +**Widen when** + +- The user wants recent polling-oriented block data instead of one canonical snapshot. Move to [NEAR Data API](/neardata). +- The user needs a history story across many transactions, not just one block payload. Move to [Transactions API](/tx). + +### Run a contract view call + +**Start here** + +- [Call Function](/rpc/contract/call-function) for a contract view method. +- [View State](/rpc/contract/view-state) when the question is about raw contract storage. +- [View Code](/rpc/contract/view-code) if code presence or hash is the real question. + +**Next page if needed** + +- [FastNear API](/api) if the user actually wants a product-shaped answer such as holdings or account summary after the raw call. +- [KV FastData API](/fastdata/kv) if the next task is indexed key-value history rather than an exact RPC state read. + +**Stop when** + +- The contract view result already answers the question in canonical form. + +**Widen when** + +- The user wants indexed history or a simpler summary instead of raw contract output. +- The user starts asking "what changed over time?" rather than "what does it return right now?" + +### Send and confirm a transaction + +**Start here** + +- [Send Transaction](/rpc/transaction/send-tx) when you want canonical submission behavior with explicit waiting semantics. +- [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) or [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit) when those exact submission modes are the point. +- [Transaction Status](/rpc/transaction/tx-status) to confirm the canonical result. + +**Next page if needed** + +- [Transactions by Hash](/tx/transactions) for a readable history record after submission. +- [Receipt Lookup](/tx/receipt) when you need to investigate downstream execution or callback flow. + +**Stop when** + +- You have the submission result and final canonical status you needed. + +**Widen when** + +- The next question is about receipts, affected accounts, or execution history in a human-friendly order. +- You need a broader investigation workflow instead of one canonical status check. + +## Common mistakes + +- Starting in RPC when the user really wants a holdings summary or indexed history. +- Forgetting to switch from regular RPC to archival RPC for older state. +- Treating docs UI browser auth as a production backend pattern. +- Staying in low-level transaction status calls after the question becomes forensic or history-oriented. + +## Related guides + +- [RPC Reference](/rpc) +- [Auth & Access](/auth) +- [FastNear API](/api) +- [Transactions API](/tx) +- [Choosing the Right Surface](/agents/choosing-surfaces) +- [Agent Playbooks](/agents/playbooks) diff --git a/docs/rpc/index.md b/docs/rpc/index.md index 602f4a9..3b56d9d 100644 --- a/docs/rpc/index.md +++ b/docs/rpc/index.md @@ -44,6 +44,10 @@ https://archival-rpc.testnet.fastnear.com - [`send_tx`](/rpc/transaction/send-tx) for transaction submission; [`tx`](/rpc/transaction/tx-status) for execution status. - [`validators`](/rpc/validators/validators-current) for the current epoch's validator set. +## Need a workflow? + +Use [RPC Examples](/rpc/examples) for plain-language flows like exact state checks, block inspection, contract view calls, and send-and-confirm transaction work. + ## Use RPC when - You want protocol-native request and response shapes. diff --git a/docs/snapshots/examples.mdx b/docs/snapshots/examples.mdx new file mode 100644 index 0000000..28e69c4 --- /dev/null +++ b/docs/snapshots/examples.mdx @@ -0,0 +1,143 @@ +--- +sidebar_label: Examples +sidebar_position: 3 +slug: /snapshots/examples +title: Snapshot Examples +description: Plain-language operator workflows for choosing the right FastNear snapshot path and executing common bootstrap and recovery flows. +displayed_sidebar: snapshotsSidebars +page_actions: + - markdown +--- + +# Snapshot Examples + +Use this page when the question is “which snapshot path should I actually run?” rather than “what command exists?” Snapshots are operator workflows, not data APIs: start by choosing the correct node goal, network, and storage model, then run the narrowest download path that gets the node online safely. + +## When to start here + +- You are bootstrapping or recovering a node. +- You need to choose among standard RPC, optimized `fast-rpc`, or archival hot/cold snapshot paths. +- You want the shortest route from operator intent to the right command sequence. +- You already know this is infrastructure work, not application data access. + +## Minimum inputs + +- network: mainnet or testnet +- node goal: standard RPC, optimized `fast-rpc`, or archival +- storage layout, especially whether hot and cold archival data can be separated +- whether you are using the default nearcore data path or custom hot/cold roots +- target data path and rough download constraints + +## Common jobs + +### Bootstrap an optimized mainnet `fast-rpc` node + +**Start here** + +- [Mainnet snapshots](/snapshots/mainnet), specifically the optimized `fast-rpc` path. + +**Next page if needed** + +- Revisit the standard mainnet RPC path if the node cannot support the optimized profile. + +**Stop when** + +- You have the right `fast-rpc` command and environment variables for the target machine. + +**Widen when** + +- The real requirement is archival retention rather than fast sync. + +### Recover a standard RPC node to the default nearcore path + +**Start here** + +- [Mainnet snapshots](/snapshots/mainnet) or [Testnet snapshots](/snapshots/testnet), depending on network, and choose the standard RPC snapshot path for that environment. + +**Next page if needed** + +- Adjust `DATA_PATH`, `THREADS`, or bandwidth settings only after the standard path is clear. + +**Stop when** + +- You can run the correct RPC recovery command with the expected data path. + +**Widen when** + +- The operator actually needs archival history or hot/cold data placement. + +### Bring up archival mainnet hot and cold data correctly + +**Start here** + +- [Mainnet snapshots](/snapshots/mainnet), archival section. + +**Next page if needed** + +- Fetch the latest archival snapshot height, then run separate hot-data and cold-data downloads with the correct paths. + +**Stop when** + +- The hot-data and cold-data plan is clear and the order of operations is correct. + +**Widen when** + +- The operator is really looking for broader nearcore bootstrap guidance beyond FastNear snapshots. + +### Bootstrap testnet archival hot data + +**Start here** + +- [Testnet snapshots](/snapshots/testnet), archival section. + +**Next page if needed** + +- Fetch the latest testnet archival snapshot height before the download step. + +**Stop when** + +- You have the right testnet archival hot-data command and block anchor. + +**Widen when** + +- The user is not doing infrastructure bootstrap and should be routed back to API or RPC docs. + +## Worked investigation + +### Choose and execute the right mainnet recovery path + +Use this investigation when an operator says “I need this node back online” and you need to quickly decide whether the correct path is optimized `fast-rpc`, standard RPC, or archival hot/cold recovery. + +**Goal** + +- Turn a vague recovery request into the right mainnet snapshot path and the minimum command sequence to start safely. + +| Path or command | How we use it | Why we use it | +| --- | --- | --- | +| Mainnet optimized `fast-rpc` path | Choose it first when the goal is the fastest high-performance RPC recovery and the node can support the optimized profile | It is the preferred fast recovery path when archival retention is not required | +| Mainnet standard RPC path | Use it when the operator needs a simpler RPC recovery without the optimized profile | It gives a straightforward default recovery route into the normal nearcore data path | +| Latest archival block lookup | Fetch the latest archival snapshot height before archival recovery | Anchors the hot/cold downloads to a specific archival snapshot block | +| Archival hot-data command | Run it first and place the result on NVMe | Hot archival data must land on the fast storage tier to support the node correctly | +| Archival cold-data command | Run it after hot data and place it on the cold storage tier | Completes archival recovery without forcing all archival data onto the expensive hot tier | + +**What a useful answer should include** + +- which recovery path was chosen and why +- which critical env vars matter for the chosen path +- where data should land on disk +- whether the operator should stay in FastNear snapshot docs or move to broader nearcore guidance + +## Common mistakes + +- Using snapshot docs when the task is really about reading chain data. +- Choosing archival recovery when a standard or optimized RPC path would do. +- Forgetting the hot/cold storage split for archival data. +- Jumping into commands before deciding the network and node goal. + +## Related guides + +- [Snapshots overview](/snapshots) +- [Mainnet snapshots](/snapshots/mainnet) +- [Testnet snapshots](/snapshots/testnet) +- [RPC Reference](/rpc) +- [NEAR Data API](/neardata) diff --git a/docs/snapshots/index.mdx b/docs/snapshots/index.mdx index d9d1bb8..a29726a 100644 --- a/docs/snapshots/index.mdx +++ b/docs/snapshots/index.mdx @@ -62,6 +62,10 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash See nearcore for node requirements, and fastnear/static for the snapshot download script source used by these guides. +## Need a workflow? + +Use [Snapshot Examples](/snapshots/examples) for operator-oriented flows like choosing between optimized `fast-rpc`, standard RPC recovery, and archival hot/cold snapshot paths. + ## Choose a network
diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md new file mode 100644 index 0000000..9e93064 --- /dev/null +++ b/docs/transfers/examples.md @@ -0,0 +1,101 @@ +--- +sidebar_label: Examples +slug: /transfers/examples +title: Transfers API Examples +description: Plain-language workflows for using Transfers API docs for narrow transfer history, pagination, and escalation into broader investigation. +displayed_sidebar: transfersApiSidebar +page_actions: + - markdown +--- + +# Transfers API Examples + +Use this page when the question is specifically about asset movement and you want the shortest path through the transfer-history docs. This surface is intentionally narrow: start with the tightest transfer filter that answers the question, stay focused on sends and receives, and widen only when the question stops being transfer-only. + +## When to start here + +- The user cares about incoming or outgoing NEAR or FT transfers. +- You want a wallet feed, audit view, or support answer focused on asset movement. +- You already know the account and do not need the full execution story yet. +- Mainnet transfer history is enough for the task. + +## Minimum inputs + +- `account_id` +- no network choice: this surface is mainnet-only today +- optional direction, asset, amount, or time filters +- whether the answer needs only a few events or a longer history scan +- whether broader transaction context may be needed later + +## Common jobs + +### Find outgoing transfers for one account in a narrow time window + +**Start here** + +- [Query Transfers](/transfers/query) with the account, outgoing direction, and the tightest useful time filter. + +**Next page if needed** + +- Narrow again by asset or amount if the response still contains unrelated transfers. + +**Stop when** + +- You can answer who sent what, when, and in which asset. + +**Widen when** + +- The user asks why the transfer happened or what other actions surrounded it. Move to [Transactions API](/tx). + +### Build a transfer feed with `resume_token` pagination + +**Start here** + +- [Query Transfers](/transfers/query) for the first page of recent events. + +**Next page if needed** + +- Reuse the exact returned `resume_token` to fetch the next page with the same filters. + +**Stop when** + +- You have enough pages to answer the requested feed, support review, or compliance check. + +**Widen when** + +- The user asks for transaction metadata beyond transfer events. +- The feed needs balances or holdings, not just movement. Move to [FastNear API](/api). + +### Escalate from transfer-only history to full transaction investigation + +**Start here** + +- [Query Transfers](/transfers/query) to identify the specific transfer events that matter. + +**Next page if needed** + +- [Transactions API account history](/tx/account) if the user wants the surrounding execution story for the same account. +- [Transactions by Hash](/tx/transactions) when you already know which transaction to inspect next. + +**Stop when** + +- You have identified the right transfer event and the right next investigation surface. + +**Widen when** + +- The user explicitly needs receipt-level or canonical RPC confirmation. Move to [Transactions API](/tx) first, then [RPC Reference](/rpc) if needed. + +## Common mistakes + +- Using Transfers API when the user really wants balances, holdings, or account summaries. +- Treating transfer history as full execution history. +- Reusing a `resume_token` with different filters. +- Starting here for testnet questions; this surface is mainnet-only today. + +## Related guides + +- [Transfers API](/transfers) +- [Transactions API](/tx) +- [FastNear API](/api) +- [Choosing the Right Surface](/agents/choosing-surfaces) +- [Agent Playbooks](/agents/playbooks) diff --git a/docs/transfers/index.md b/docs/transfers/index.md index 0260fbf..65274f2 100644 --- a/docs/transfers/index.md +++ b/docs/transfers/index.md @@ -66,6 +66,10 @@ When that happens, widen to [Transactions API](/tx) or [FastNear API](/api) inst - [Query Transfers](/transfers/query) for the account-centric feed with direction, asset, amount, and time filters +## Need a workflow? + +Use [Transfers API Examples](/transfers/examples) for plain-language flows like narrow transfer searches, `resume_token` pagination, and escalation into broader transaction investigation. + ## Troubleshooting ### I need full transaction metadata diff --git a/docs/tx/examples.md b/docs/tx/examples.md new file mode 100644 index 0000000..f2c0b74 --- /dev/null +++ b/docs/tx/examples.md @@ -0,0 +1,181 @@ +--- +sidebar_label: Examples +slug: /tx/examples +title: Transactions API Examples +description: Plain-language workflows for using Transactions API docs for transaction lookups, receipt investigation, account history, and block windows. +displayed_sidebar: transactionsApiSidebar +page_actions: + - markdown +--- + +# Transactions API Examples + +Use this page when the question is "what happened?" and you want indexed history before dropping to canonical RPC confirmation. Start with the identifier you already have, explain the execution story in readable order, and widen only if exact RPC semantics become necessary. + +## When to start here + +- You already have a transaction hash, receipt ID, account ID, or bounded block range. +- The user wants execution history, support/debug context, or a readable timeline. +- You need indexed history without rebuilding it from raw RPC calls. +- The first answer should explain what happened before it dives into protocol detail. + +## Minimum inputs + +- network: mainnet or testnet +- primary identifier: transaction hash, receipt ID, `account_id`, or block/range +- whether you are investigating one item or a history window +- whether canonical RPC confirmation is required before you stop + +## Common jobs + +### Look up one transaction + +**Start here** + +- [Transactions by Hash](/tx/transactions) when you already know the transaction ID. + +**Next page if needed** + +- [Receipt Lookup](/tx/receipt) if the interesting part is now a downstream receipt. +- [Block](/tx/block) if the block context matters. +- [Transaction Status](/rpc/transaction/tx-status) if you need canonical RPC confirmation. + +**Stop when** + +- You can explain the outcome, affected accounts, and the main execution takeaway. + +**Widen when** + +- The user asks for exact RPC-level status semantics or submission behavior. +- The transaction lookup alone is not enough to explain downstream execution. + +### Investigate a receipt + +**Start here** + +- [Receipt Lookup](/tx/receipt) when the receipt ID is your best anchor. + +**Next page if needed** + +- [Transactions by Hash](/tx/transactions) to reconnect the receipt to the originating transaction story. +- [Account History](/tx/account) if you need to see the surrounding activity for one of the touched accounts. + +**Stop when** + +- You can say where the receipt fits in the execution flow and why it matters. + +**Widen when** + +- The user needs exact canonical confirmation beyond the indexed receipt view. Move to [RPC Reference](/rpc). +- The question shifts from one receipt to a broader history investigation. + +### Review recent account activity + +**Start here** + +- [Account History](/tx/account) for an account-centric activity feed. + +**Next page if needed** + +- [Transactions by Hash](/tx/transactions) for a specific transaction from the feed. +- [Receipt Lookup](/tx/receipt) if one receipt becomes the real focus. + +**Stop when** + +- The account history already answers what the account has been doing. + +**Widen when** + +- The user actually wants transfer-only movement instead of broader execution context. Move to [Transfers API](/transfers). +- The user actually wants exact current state or holdings, not history. Move to [RPC Reference](/rpc) or [FastNear API](/api). + +### Reconstruct a bounded block window + +**Start here** + +- [Blocks](/tx/blocks) for a bounded block-range scan. +- [Block](/tx/block) when you already know the exact block you want. + +**Next page if needed** + +- [Transactions by Hash](/tx/transactions) to inspect a specific item from the block window. +- [Receipt Lookup](/tx/receipt) when one receipt becomes the important follow-up. + +**Stop when** + +- The bounded history window answers the question without dropping into lower-level protocol details. + +**Widen when** + +- The user needs exact canonical block fields or transaction finality. Move to [RPC Reference](/rpc). +- The user really wants freshest polling-oriented block reads rather than indexed history. Move to [NEAR Data API](/neardata). + +## Worked investigations + +### Prove callback order in a staged release flow + +Use this investigation when you staged async work first, released it later, and need to prove not just that the transactions succeeded, but that the downstream callbacks executed in a particular order. + +**Goal** + +- Turn two transaction hashes into a durable forensic record that covers receipt DAGs, block anchors, and contract state changes. + +In staged-release flows, the stage transaction usually remains the primary forensic anchor because the yielded callback receipts stay on its original transaction tree, not on the release transaction's tree. + +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Stage and release trace capture | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Query the stage transaction hash and the release transaction hash with `wait_until: "FINAL"`, usually hot RPC first and archival RPC on `UNKNOWN_TRANSACTION` | The receipt DAG is the primary proof surface for callback order and tells you which receipts belong to which transaction tree | +| Stage materialization check | RPC [`query(call_function)`](/rpc/contract/call-function) | Poll the staging contract's view method, such as `staged_calls_for({ caller_id })`, with `finality: "final"` until the yielded steps appear | Confirms the yielded callbacks are actually live before the release transaction tries to resume them | +| Transaction enrichment | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch both transaction hashes to recover `block_height`, `block_hash`, `receiver_id`, and indexed execution status | Gives each transaction a durable block anchor so later archival or human follow-up does not depend on memory | +| Recorder state snapshots | RPC [`query(call_function)`](/rpc/contract/call-function) | Read the downstream recorder state before the release, then poll it after release until the expected entries appear | Proves actual downstream effect order in contract state, not just metadata in the receipt tree | +| Receipt pivot | Transactions API [`POST /v0/receipt`](/tx/receipt) | Use any interesting yielded or downstream receipt ID to reconnect it to the originating transaction | Lets you move quickly from one receipt in the DAG back to the broader transaction story | +| Per-block reconstruction | Transactions API [`POST /v0/block`](/tx/block) | Fetch the included block and the cascade blocks with receipts enabled | Reconstructs the block-by-block execution timeline once you know which blocks matter | +| Account activity context | Transactions API [`POST /v0/account`](/tx/account) | Fetch function-call history for the contracts that participated in the cascade over the same window | Gives humans a simpler account-history view to compare against the trace | +| Block-pinned state replay | RPC [`query(call_function)`](/rpc/contract/call-function) | Re-run the recorder view with `block_id` pinned to the interesting heights | Turns final state into a time series so you can say when state changed, not just what it became | + +**What a useful answer should include** + +- why the stage transaction, not the release transaction, is usually the primary forensic anchor +- the callback order you observed +- the blocks where the observable state changed +- any receipt or account pivots the next investigator should keep + +### Start from a receipt ID and rebuild the execution story + +Use this investigation when the only thing you have is a receipt ID from a trace, error report, or callback tree and you need to get back to a readable story of what happened. + +**Goal** + +- Pivot from one receipt to the originating transaction, then widen just enough to explain the surrounding execution and state effects. + +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Receipt anchor | Transactions API [`POST /v0/receipt`](/tx/receipt) | Look up the receipt ID first and identify the receipt payload, status, and linked transaction context | Receipt IDs show up in traces and logs before humans know the full transaction story | +| Transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Pull the originating transaction by hash once the receipt lookup gives you the pivot | Turns one receipt into a readable execution story with receiver, block, and status context | +| Canonical confirmation | RPC [`tx`](/rpc/transaction/tx-status) or [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Confirm the protocol-level result when the indexed view is not enough or the user needs canonical semantics | Useful when the investigation must distinguish between indexed interpretation and exact RPC behavior | +| Block context | Transactions API [`POST /v0/block`](/tx/block) | Fetch the containing block, and widen to nearby cascade blocks if the execution spilled over multiple heights | Places the receipt into a block timeline that is easier to explain | +| Account window | Transactions API [`POST /v0/account`](/tx/account) | Pull recent activity for the accounts touched by the receipt | Helps correlate the receipt with the surrounding account-level history | +| State replay | RPC [`query(call_function)`](/rpc/contract/call-function) | Re-run the relevant view method at a pinned `block_id` if the receipt changed contract-visible state | Lets you prove whether a receipt only existed in metadata or also changed durable contract state | + +**What a useful answer should include** + +- the originating transaction you recovered from the receipt +- whether the receipt was the main event or only one step in a larger cascade +- the minimum block and account context needed to explain it +- whether the state effect was durable and at which block height it became visible + +## Common mistakes + +- Trying to submit a transaction from the history API instead of raw RPC. +- Using Transactions API when the user only wants current balances or holdings. +- Dropping to raw RPC before indexed history has answered the readable "what happened?" question. +- Reusing opaque pagination tokens in a different endpoint or filter context. + +## Related guides + +- [Transactions API](/tx) +- [RPC Reference](/rpc) +- [FastNear API](/api) +- [NEAR Data API](/neardata) +- [Choosing the Right Surface](/agents/choosing-surfaces) +- [Agent Playbooks](/agents/playbooks) diff --git a/docs/tx/index.md b/docs/tx/index.md index 1c8f4ce..08336f8 100644 --- a/docs/tx/index.md +++ b/docs/tx/index.md @@ -49,6 +49,10 @@ https://tx.test.fastnear.com - [Receipt lookup](/tx/receipt) for execution-flow investigation. - [Block range](/tx/blocks) when you want a bounded history scan. +## Need a workflow? + +Use [Transactions API Examples](/tx/examples) for plain-language flows like transaction lookups, receipt investigation, account activity, and block-window history. + ## Troubleshooting ### I expected to submit a transaction here diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md new file mode 100644 index 0000000..e996789 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -0,0 +1,107 @@ +--- +sidebar_label: Examples +slug: /api/examples +title: "Примеры FastNear API" +description: "Пошаговые сценарии использования FastNear API для сводок по аккаунтам, поиска по ключам и перехода к более узким представлениям активов." +displayed_sidebar: fastnearApiSidebar +page_actions: + - markdown +--- + +# Примеры FastNear API + +Используйте эту страницу, когда нужен читаемый ответ в форме сводки по аккаунту или активу и хочется пройти по документации FastNear API самым коротким путём. Начинайте с самого узкого эндпоинта, который уже может решить задачу, и расширяйтесь только тогда, когда понадобятся канонические детали RPC или история исполнения. + +## Когда начинать здесь + +- Пользователю нужны балансы, активы, стейкинг или общая сводка по аккаунту в формате кошелька. +- Нужно определить один или несколько аккаунтов по публичному ключу. +- Ответ должен выглядеть как прикладные данные, а не как сырой JSON-RPC. +- Нужен быстрый первый ответ до того, как станет понятно, требуется ли каноническое подтверждение через RPC. + +## Минимальные входные данные + +- сеть: mainnet или testnet +- основной идентификатор: `account_id` или публичный ключ +- нужен ли широкий обзор или одна конкретная категория активов +- понадобится ли затем точное каноническое подтверждение или история активности + +## Частые задачи + +### Получить сводку по аккаунту в формате кошелька + +**Начните здесь** + +- [V1 Full Account View](/api/v1/account-full) для самого широкого снимка аккаунта. + +**Следующая страница при необходимости** + +- [V1 Account FT](/api/v1/account-ft), [V1 Account NFT](/api/v1/account-nft) или [V1 Account Staking](/api/v1/account-staking) для более узкого продолжения. +- [Transactions API account history](/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?» + +**Остановитесь, когда** + +- Сводка уже отвечает на вопрос о портфеле или активах в нужной пользователю форме. + +**Расширяйте, когда** + +- Пользователь спрашивает о точной канонической семантике аккаунта или ключей доступа. Переходите к [RPC Reference](/rpc). +- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](/tx). + +### Определить аккаунты по публичному ключу + +**Начните здесь** + +- [V1 Public Key Lookup](/api/v1/public-key), когда нужен основной аккаунт для ключа. +- [V1 Public Key Lookup All](/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. + +**Следующая страница при необходимости** + +- [V1 Full Account View](/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. + +**Остановитесь, когда** + +- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. + +**Расширяйте, когда** + +- Пользователь спрашивает о точных правах ключа, nonce или каноническом состоянии access key. Переходите к [View Access Key](/rpc/account/view-access-key) или [View Access Key List](/rpc/account/view-access-key-list). +- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](/tx). + +### Продолжить по одной категории активов, а не по всему аккаунту + +**Начните здесь** + +- [V1 Account FT](/api/v1/account-ft) для балансов FT-токенов. +- [V1 Account NFT](/api/v1/account-nft) для владения NFT. +- [V1 Account Staking](/api/v1/account-staking) для позиций стейкинга. + +**Следующая страница при необходимости** + +- [V1 Full Account View](/api/v1/account-full), если позже понадобится более широкий снимок аккаунта. +- [Transactions API account history](/tx/account), если вопрос смещается к тому, как активы менялись со временем. + +**Остановитесь, когда** + +- Эндпоинт по конкретной категории активов уже даёт готовый продуктовый ответ без дополнительной реконструкции. + +**Расширяйте, когда** + +- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](/rpc). +- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](/tx). + +## Частые ошибки + +- Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. +- Использовать FastNear API, хотя пользователю прямо нужны канонические поля RPC или права доступа. +- Оставаться на страницах сводок по аккаунту, когда вопрос уже стал вопросом об истории транзакций. +- Забывать, что `?network=testnet` поддерживается только на совместимых страницах. + +## Полезные связанные страницы + +- [FastNear API](/api) +- [API Reference](/api/reference) +- [RPC Reference](/rpc) +- [Transactions API](/tx) +- [Choosing the Right Surface](/agents/choosing-surfaces) +- [Agent Playbooks](/agents/playbooks) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/index.md index 92ad506..52f699c 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/index.md @@ -54,6 +54,10 @@ https://test.api.fastnear.com - [V1 поиск по публичному ключу](/api/v1/public-key) — когда нужно определить аккаунт по ключу. - [V1 топ держателей FT](/api/v1/ft-top) — для представлений распределения токенов. +## Нужен сценарий? + +Используйте [FastNear API Examples](/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, резолв ключа в аккаунт и переход к узким представлениям активов. + ## Устранение неполадок ### Мне нужно только одно низкоуровневое значение из состояния цепочки diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md new file mode 100644 index 0000000..1dca291 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -0,0 +1,141 @@ +--- +sidebar_label: Examples +slug: /fastdata/kv/examples +title: "Примеры KV FastData API" +description: "Пошаговые сценарии использования KV FastData для точных ключей, истории ключей, анализа по предшественнику и перехода к каноническому RPC." +displayed_sidebar: kvFastDataSidebar +page_actions: + - markdown +--- + +# Примеры KV FastData API + +Используйте эту страницу, когда вопрос касается индексированного хранилища контракта и уже есть точная область поиска. Главное решение на этой поверхности — выбрать самую узкую полезную область: точный ключ, аккаунт, `predecessor_id` или набор известных ключей. Оставайтесь внутри KV FastData, пока речь идёт об индексированных данных key-value, и переходите к RPC только тогда, когда нужно каноническое состояние в цепочке. + +## Когда начинать здесь + +- Нужны индексированные данные хранилища контракта, а не широкие сводки по аккаунтам или активам. +- Уже известен контракт, точный ключ, `predecessor_id` или область по аккаунту. +- Нужны последние индексированные записи или история изменений во времени. +- Нужен быстрый ответ по хранилищу до решения о дальнейшей проверке через RPC. + +## Минимальные входы + +- сеть +- ID контракта плюс одно из: точный ключ, область аккаунта, область по `predecessor_id` или известный набор ключей +- нужно ли последнее индексированное состояние или история изменений +- может ли позже понадобиться каноническая проверка + +## Частые задачи + +### Посмотреть один точный ключ прямо сейчас + +**Начните здесь** + +- [Последнее по точному ключу](/fastdata/kv/get-latest-key), когда точный ключ уже известен. + +**Следующая страница при необходимости** + +- [История по точному ключу](/fastdata/kv/get-history-key), если вопрос превращается в «как менялся этот ключ?» + +**Остановитесь, когда** + +- Последняя индексированная запись уже отвечает на вопрос о хранилище. + +**Расширяйте, когда** + +- Пользователю нужно точное текущее состояние в цепочке, а не индексированное хранилище. Переходите к [View State](/rpc/contract/view-state). + +### Превратить один точный ключ в историю изменений + +**Начните здесь** + +- [История по точному ключу](/fastdata/kv/get-history-key) для поиска истории по пути. +- [History by Key](/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. + +**Следующая страница при необходимости** + +- Возвращайтесь к [Последнему по точному ключу](/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. + +**Остановитесь, когда** + +- Уже можно объяснить, как ключ менялся со временем. + +**Расширяйте, когда** + +- Пользователь спрашивает, совпадает ли последнее индексированное значение с каноническим состоянием цепочки прямо сейчас. + +### Проследить записи от одного `predecessor_id` + +**Начните здесь** + +- [Всё по `predecessor_id`](/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. +- [История по `predecessor_id`](/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. + +**Следующая страница при необходимости** + +- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. + +**Остановитесь, когда** + +- Уже можно ответить, что именно этот предшественник изменил и где. + +**Расширяйте, когда** + +- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее каноническое состояние. + +### Пакетно проверить несколько известных ключей + +**Начните здесь** + +- [Пакетный поиск по ключам](/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. + +**Следующая страница при необходимости** + +- Переведите один интересный ключ в [Историю по точному ключу](/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. + +**Остановитесь, когда** + +- Пакетный ответ уже показывает, какие ключи действительно важны. + +**Расширяйте, когда** + +- Пользователь хочет более широкий анализ контракта вместо фиксированного набора ключей. + +## Готовое расследование + +### Начать с одного индексированного ключа, затем подтвердить историю и каноническое состояние + +Используйте это расследование, когда один ключ контракта выглядит подозрительно и нужно связать его последнее индексированное значение, историю изменений и каноническую проверку через `view_state` в одну ясную историю. + +**Цель** + +- Объяснить, как ключ контракта выглядит сейчас, как он пришёл к этому состоянию в индексированной истории и совпадает ли с этим каноническое состояние RPC. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Последнее индексированное значение | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | +| История индексированного ключа | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) или [`history-by-key`](/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | +| Расширение области | KV FastData [`latest-by-account`](/fastdata/kv/latest-by-account) или [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Расширяемся до области аккаунта или предшественника, если один ключ — только часть более широкой картины изменений | Помогает понять, менялся ли ключ изолированно или как часть более широкого набора записей | +| Каноническое подтверждение | RPC [`view_state`](/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированный паттерн уже понятен | Разделяет индексированную историю хранилища и точное текущее состояние в цепочке | + +**Что должен включать полезный ответ** + +- какой именно ключ и какая область контракта были исследованы +- как выглядит последнее индексированное значение и какие изменения видны в истории +- совпал ли `view_state` с текущим индексированным значением + +## Частые ошибки + +- Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. +- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. +- Путать индексированную историю с точным текущим состоянием в цепочке. +- Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. + +## Полезные связанные страницы + +- [KV FastData API](/fastdata/kv) +- [RPC Reference](/rpc) +- [FastNear API](/api) +- [Choosing the Right Surface](/agents/choosing-surfaces) +- [Agent Playbooks](/agents/playbooks) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md index 4e9293f..c785e79 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md @@ -51,6 +51,10 @@ https://kv.test.fastnear.com - [Всё по `predecessor_id`](/fastdata/kv/all-by-predecessor) или [История по `predecessor_id`](/fastdata/kv/history-by-predecessor) — когда правильная область — `predecessor_id` - [Пакетный поиск по ключам](/fastdata/kv/multi) — когда уже известно несколько точных ключей +## Нужен сценарий? + +Используйте [KV FastData Examples](/fastdata/kv/examples) для простых пошаговых сценариев: exact-key lookups, история ключей, анализ по `predecessor_id` и переход к каноническому RPC. + ## Рабочий цикл по умолчанию 1. Выберите самую узкую область, подходящую под вопрос пользователя. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md new file mode 100644 index 0000000..2eb4083 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -0,0 +1,142 @@ +--- +sidebar_label: Examples +slug: /neardata/examples +title: "Примеры NEAR Data API" +description: "Пошаговые сценарии использования NEAR Data API для опроса блоков, маршрутов перенаправления и перехода к каноническому RPC." +displayed_sidebar: nearDataApiSidebar +page_actions: + - markdown +--- + +# Примеры NEAR Data API + +Используйте эту страницу, когда свежесть важнее протокольной точности. NEAR Data API предназначен для опроса и чтения недавних данных по семейству блоков: начинайте с самого свежего или самого стабильного режима блоков, который подходит задаче, оставайтесь на этой поверхности для опроса, пока она отвечает на вопрос, и переходите к RPC только тогда, когда действительно нужна каноническая семантика блока или состояния. + +## Когда начинать здесь + +- Нужны недавние оптимистичные или финализированные данные по семейству блоков. +- Нужен клиент опроса, монитор или проверка свежести. +- Маршруты перенаправления приемлемы или полезны для клиентского сценария. +- Задача звучит как «что изменилось недавно?», а не как каноническое историческое подтверждение. + +## Минимальные входы + +- сеть +- режим свежести: optimistic или finalized +- есть ли конкретная высота или хеш, либо нужен самый свежий объект семейства блоков +- может ли клиент корректно следовать перенаправлениям +- потребуется ли позже проверка через RPC + +## Частые задачи + +### Отслеживать последний оптимистичный блок + +**Начните здесь** + +- [Оптимистичный блок](/neardata/block-optimistic) для самого свежего чтения по семейству блоков. + +**Следующая страница при необходимости** + +- [Перенаправление на последний оптимистичный блок](/neardata/last-block-optimistic), если нужен маршрут перенаправления, который всегда ведёт к самому новому оптимистичному блоку. + +**Остановитесь, когда** + +- Уже можно сообщить о последнем оптимистичном блоке или зафиксировать отставание по свежести. + +**Расширяйте, когда** + +- Нужна finalized-стабильность вместо максимальной свежести. Переходите к [Финализированному блоку по высоте](/neardata/block) или [Перенаправлению на последний финализированный блок](/neardata/last-block-final). + +### Безопасно отслеживать ход финализации блоков + +**Начните здесь** + +- [Финализированный блок по высоте](/neardata/block), когда уже известна нужная высота. +- [Заголовки блока](/neardata/block-headers), когда достаточно чтения заголовков. + +**Следующая страница при необходимости** + +- [Перенаправление на последний финализированный блок](/neardata/last-block-final), когда клиент должен следовать за самым новым финализированным блоком без предварительного вычисления высоты. + +**Остановитесь, когда** + +- Уже можно показывать движение финализированных блоков без перехода к более глубоким протокольным деталям. + +**Расширяйте, когда** + +- Пользователю нужны точные канонические поля блока или семантика транзакций. Переходите к [RPC Reference](/rpc). + +### Использовать маршруты перенаправления в клиенте опроса + +**Начните здесь** + +- [Перенаправление на последний финализированный блок](/neardata/last-block-final) или [Перенаправление на последний оптимистичный блок](/neardata/last-block-optimistic) в зависимости от требуемой свежести. + +**Следующая страница при необходимости** + +- Следуйте по канонической цели, которую вернул маршрут перенаправления, и уже там читайте нужные данные по семейству блоков. + +**Остановитесь, когда** + +- Клиент надёжно проходит по маршруту перенаправления и получает нужный ресурс блока. + +**Расширяйте, когда** + +- Само перенаправление мешает клиенту. Тогда переходите на прямые маршруты блоков. + +### Перейти от свежего опроса к каноническому разбору через RPC + +**Начните здесь** + +- Используйте подходящий маршрут NEAR Data, чтобы найти недавний блок или событие в семействе блоков, которое нужно исследовать. + +**Следующая страница при необходимости** + +- [Block by Height](/rpc/block/block-by-height), [Block by ID](/rpc/block/block-by-id) или другой RPC-метод, как только станет понятно, какой именно блок или следующий объект для проверки нужен. + +**Остановитесь, когда** + +- Уже можно чётко назвать недавний блок, который заслуживает канонической проверки. + +**Расширяйте, когда** + +- Пользователь просит точную структуру данных в терминах протокола, а не просто свежее чтение. + +## Готовое расследование + +### Начать с оптимистичного блока, затем подтвердить финализированную и каноническую картину + +Используйте это расследование, когда нужно раннее обнаружение по оптимистичному блоку, но финальный ответ всё равно должен опираться на стабильный финализированный вид и, при необходимости, на каноническое подтверждение через RPC. + +**Цель** + +- Быстро заметить недавнее изменение, а затем сузить его до финализированной и канонической истории блока без лишних запросов. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | +| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | +| Стабильное подтверждение | NEAR Data [`block`](/neardata/block) или [`last-block-final`](/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | +| Лёгкая сводка по блоку | NEAR Data [`block-headers`](/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | +| Каноническая проверка | RPC [Блок по ID](/rpc/block/block-by-id) или [Блок по высоте](/rpc/block/block-by-height) | Получаем точный канонический блок, как только понятно, какой именно блок важен | Переводит расследование от свежих данных к протокольно точному подтверждению только когда это действительно нужно | + +**Что должен включать полезный ответ** + +- какое наблюдение по оптимистичному блоку впервые запустило расследование +- когда то же наблюдение стало финализированным +- изменил ли канонический разбор через RPC интерпретацию + +## Частые ошибки + +- Воспринимать NEAR Data API как потоковый продукт, а не как поверхность для опроса. +- Начинать с канонического RPC, когда настоящая задача — мониторинг свежих блоков. +- Забывать, что невалидный ключ может вернуть `401` ещё до перенаправления, а сами перенаправления подходят не каждому HTTP-клиенту. +- Оставаться на NEAR Data после того, как пользователь уже попросил точные протокольные детали блока. + +## Полезные связанные страницы + +- [NEAR Data API](/neardata) +- [RPC Reference](/rpc) +- [Transactions API](/tx) +- [Choosing the Right Surface](/agents/choosing-surfaces) +- [Agent Playbooks](/agents/playbooks) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md index ee4ac5b..9148700 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md @@ -45,6 +45,10 @@ https://testnet.neardata.xyz - [Финализированный блок по высоте](/neardata/block) и [Заголовки блока](/neardata/block-headers) — для запросов по финализированным блокам. - [Перенаправление на последний финализированный блок](/neardata/last-block-final) и [Перенаправление на последний оптимистичный блок](/neardata/last-block-optimistic) — когда нужны вспомогательные маршруты с перенаправлением. +## Нужен сценарий? + +Используйте [NEAR Data API Examples](/neardata/examples) для простых пошаговых сценариев: optimistic polling, finalized confirmation, redirect helpers и переход к каноническому RPC. + ## Устранение неполадок ### Некоторые эндпоинты перенаправляют на канонический URL вместо прямого возврата результата diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md new file mode 100644 index 0000000..7708ea4 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -0,0 +1,132 @@ +--- +sidebar_label: Examples +slug: /rpc/examples +title: "Примеры RPC" +description: "Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций." +displayed_sidebar: rpcSidebar +page_actions: + - markdown +--- + +# Примеры RPC + +Используйте эту страницу, когда уже понятно, что ответ должен опираться на каноническое поведение RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы выбрать правильную стартовую страницу, остановиться, как только RPC-ответ уже решает задачу, и расширять набор поверхностей только тогда, когда это действительно поможет. + +## Когда начинать здесь + +- Пользователь просит точное состояние в цепочке или поля в протокольной форме. +- Нужен прямой вызов view-метода контракта или сценарий отправки транзакции. +- Нужно исследовать блоки, чанки, валидаторов или метаданные протокола. +- Важна семантика узла, а не индексированное агрегированное представление. + +## Минимальные входные данные + +- сеть: mainnet или testnet +- основной идентификатор: `account_id`, публичный ключ, ID контракта плюс метод, хеш транзакции или высота/хеш блока +- нужно ли текущее состояние, историческое состояние или поведение отправки/финальности +- должен ли результат остаться каноническим или затем превратиться в более удобное для человека резюме + +## Частые задачи + +### Проверить точное состояние аккаунта или ключа доступа + +**Начните здесь** + +- [View Account](/rpc/account/view-account) для канонических полей аккаунта. +- [View Access Key](/rpc/account/view-access-key) или [View Access Key List](/rpc/account/view-access-key-list) для проверки ключей. + +**Следующая страница при необходимости** + +- [FastNear API full account view](/api/v1/account-full), если после подтверждения канонического состояния нужна ещё и сводка в формате кошелька. +- [Transactions API account history](/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» + +**Остановитесь, когда** + +- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. + +**Расширяйте, когда** + +- Пользователю нужны балансы, NFT, стейкинг или другой продуктовый вид данных. +- Пользователя интересует не текущее каноническое состояние, а недавняя история активности. + +### Проверить блок или снимок состояния протокола + +**Начните здесь** + +- [Block by ID](/rpc/block/block-by-id) или [Block by Height](/rpc/block/block-by-height) для конкретного блока. +- [Latest Block](/rpc/protocol/latest-block) для текущей канонической головы цепочки. +- [Status](/rpc/protocol/status), [Health](/rpc/protocol/health) или [Network Info](/rpc/protocol/network-info) для диагностики узла и сети. + +**Следующая страница при необходимости** + +- [Block Effects](/rpc/block/block-effects), если после поиска блока нужен контекст по изменениям состояния. +- [Transactions API block history](/tx/block) или [Transactions API block range](/tx/blocks), если нужна более читаемая картина исполнения в рамках блока или диапазона. + +**Остановитесь, когда** + +- Канонический ответ блока или протокола уже напрямую отвечает на вопрос. + +**Расширяйте, когда** + +- Нужны данные по свежим блокам в режиме опроса, а не один канонический снимок. Переходите к [NEAR Data API](/neardata). +- Нужна история по нескольким транзакциям, а не только ответ одного блока. Переходите к [Transactions API](/tx). + +### Выполнить view-вызов контракта + +**Начните здесь** + +- [Call Function](/rpc/contract/call-function) для view-метода контракта. +- [View State](/rpc/contract/view-state), когда вопрос касается сырого хранилища контракта. +- [View Code](/rpc/contract/view-code), когда на самом деле нужно понять, есть ли код и каков его хеш. + +**Следующая страница при необходимости** + +- [FastNear API](/api), если после сырого вызова пользователю нужен продуктовый ответ, например по активам или сводке аккаунта. +- [KV FastData API](/fastdata/kv), если следующая задача связана с индексированной историей по ключам и значениям, а не с точным RPC-чтением. + +**Остановитесь, когда** + +- Результат view-вызова уже отвечает на вопрос в канонической форме. + +**Расширяйте, когда** + +- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. +- Вопрос смещается от «что метод возвращает сейчас?» к «что менялось со временем?» + +### Отправить транзакцию и подтвердить результат + +**Начните здесь** + +- [Send Transaction](/rpc/transaction/send-tx), когда нужно каноническое поведение отправки с явной семантикой ожидания. +- [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. +- [Transaction Status](/rpc/transaction/tx-status), чтобы подтвердить канонический результат. + +**Следующая страница при необходимости** + +- [Transactions by Hash](/tx/transactions), если после отправки нужна более читаемая история по транзакции. +- [Receipt Lookup](/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. + +**Остановитесь, когда** + +- У вас уже есть результат отправки и нужный канонический финальный статус. + +**Расширяйте, когда** + +- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. +- Нужен уже не единичный статус, а более широкий сценарий расследования. + +## Частые ошибки + +- Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. +- Забывать переключаться с обычного RPC на архивный RPC для старого состояния. +- Воспринимать браузерную аутентификацию в интерфейсе документации как продовый паттерн для бэкенда. +- Продолжать пользоваться низкоуровневыми статусами транзакций, когда вопрос уже превратился в расследование или исторический разбор. + +## Полезные связанные страницы + +- [RPC Reference](/rpc) +- [Auth & Access](/auth) +- [FastNear API](/api) +- [Transactions API](/tx) +- [Choosing the Right Surface](/agents/choosing-surfaces) +- [Agent Playbooks](/agents/playbooks) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/index.md index 148830a..fcfcd3d 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/index.md @@ -43,6 +43,10 @@ https://archival-rpc.testnet.fastnear.com - [`send_tx`](/rpc/transaction/send-tx) — отправка транзакций; [`tx`](/rpc/transaction/tx-status) — статус исполнения. - [`validators`](/rpc/validators/validators-current) — валидаторы текущей эпохи. +## Нужен сценарий? + +Используйте [RPC Examples](/rpc/examples) для простых пошаговых сценариев: точные проверки состояния, анализ блоков, view-вызовы контрактов и отправка с подтверждением транзакций. + ## Используйте RPC, когда - нужны канонические формы запросов и ответов из протокола; diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx new file mode 100644 index 0000000..cfabb52 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx @@ -0,0 +1,143 @@ +--- +sidebar_label: Examples +sidebar_position: 3 +slug: /snapshots/examples +title: "Примеры снапшотов" +description: "Пошаговые операторские сценарии для выбора правильного пути снапшотов FastNear и выполнения типовых задач запуска и восстановления." +displayed_sidebar: snapshotsSidebars +page_actions: + - markdown +--- + +# Примеры снапшотов + +Используйте эту страницу, когда вопрос звучит как «какой сценарий со снапшотом вообще запускать?», а не «какая команда существует?». Снапшоты — это операторские сценарии, а не API для данных: сначала выбирайте правильную цель узла, сеть и схему хранения, а затем запускайте самый узкий путь загрузки, который безопасно поднимет узел. + +## Когда начинать здесь + +- Нужно поднять или восстановить узел. +- Нужно выбрать между обычным RPC, optimized `fast-rpc` и архивным сценарием с hot/cold-данными. +- Нужен самый короткий путь от задачи оператора к правильной последовательности команд. +- Уже понятно, что это инфраструктурная работа, а не чтение прикладных данных. + +## Минимальные входы + +- сеть: mainnet или testnet +- цель узла: обычный RPC, optimized `fast-rpc` или архивный режим +- схема хранения, особенно возможность разделить hot- и cold-архивные данные +- используется ли стандартный каталог данных nearcore или отдельные каталоги для hot/cold +- целевой путь к данным и примерные ограничения по загрузке + +## Частые задачи + +### Поднять optimized `fast-rpc`-узел в mainnet + +**Начните здесь** + +- [Снапшоты mainnet](/snapshots/mainnet), конкретно путь для optimized `fast-rpc`. + +**Следующая страница при необходимости** + +- Вернитесь к обычному сценарию mainnet RPC, если узел не подходит для optimized profile. + +**Остановитесь, когда** + +- Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. + +**Расширяйте, когда** + +- На самом деле требуется архивное хранение, а не просто быстрый запуск. + +### Восстановить обычный RPC-узел в стандартный каталог nearcore + +**Начните здесь** + +- [Снапшоты mainnet](/snapshots/mainnet) или [Снапшоты testnet](/snapshots/testnet) в зависимости от сети и выберите обычный RPC-сценарий для нужного окружения. + +**Следующая страница при необходимости** + +- Настраивайте `DATA_PATH`, `THREADS` или ограничения по пропускной способности только после того, как понятен стандартный сценарий. + +**Остановитесь, когда** + +- Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. + +**Расширяйте, когда** + +- Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. + +### Правильно поднять архивные hot- и cold-данные mainnet + +**Начните здесь** + +- [Снапшоты mainnet](/snapshots/mainnet), раздел архивного режима. + +**Следующая страница при необходимости** + +- Сначала получите последнюю высоту архивного снапшота, затем запускайте отдельные загрузки hot- и cold-данных с правильными путями. + +**Остановитесь, когда** + +- План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. + +**Расширяйте, когда** + +- Оператору нужны уже более широкие nearcore bootstrap-guides, а не только FastNear snapshots. + +### Поднять архивные hot-данные в testnet + +**Начните здесь** + +- [Снапшоты testnet](/snapshots/testnet), раздел архивного режима. + +**Следующая страница при необходимости** + +- Получите последнюю высоту архивного снапшота testnet перед шагом загрузки. + +**Остановитесь, когда** + +- Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. + +**Расширяйте, когда** + +- Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. + +## Готовое расследование + +### Выбрать и выполнить правильный сценарий восстановления mainnet + +Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно быстро понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. + +**Цель** + +- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную безопасную последовательность команд. + +| Путь или команда | Как используем | Зачем используем | +| --- | --- | --- | +| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | +| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | +| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | +| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | +| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | + +**Что должен включать полезный ответ** + +- какой сценарий восстановления выбран и почему +- какие ключевые env vars важны для выбранного пути +- куда на диске должны попасть данные +- должен ли оператор оставаться в FastNear snapshot docs или переходить к более широкой документации nearcore + +## Частые ошибки + +- Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. +- Выбирать архивное восстановление, когда достаточно обычного или optimized RPC. +- Забывать про разделение hot/cold-данных для архивного режима. +- Переходить к командам до выбора сети и цели узла. + +## Полезные связанные страницы + +- [Обзор снапшотов](/snapshots) +- [Снапшоты mainnet](/snapshots/mainnet) +- [Снапшоты testnet](/snapshots/testnet) +- [RPC Reference](/rpc) +- [NEAR Data API](/neardata) 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..73ea72e 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/index.mdx @@ -63,6 +63,10 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash Требования к узлам смотрите в nearcore, а исходники скриптов загрузки, которые используются в этих руководствах, — в fastnear/static. +## Нужен сценарий? + +Используйте [Snapshot Examples](/snapshots/examples) для операторских сценариев: выбор между optimized `fast-rpc`, standard RPC recovery и archival hot/cold snapshot paths. + ## Выберите сеть
diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md new file mode 100644 index 0000000..3b6017f --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -0,0 +1,101 @@ +--- +sidebar_label: Examples +slug: /transfers/examples +title: "Примеры Transfers API" +description: "Пошаговые сценарии использования Transfers API для узкой истории переводов, пагинации и перехода к более широкому расследованию." +displayed_sidebar: transfersApiSidebar +page_actions: + - markdown +--- + +# Примеры Transfers API + +Используйте эту страницу, когда вопрос касается именно движения активов и нужен самый короткий путь по документации истории переводов. Эта поверхность специально узкая: начинайте с самого точного фильтра перевода, который отвечает на вопрос, держите фокус на отправках и получениях и расширяйтесь только тогда, когда вопрос перестаёт быть «только про переводы». + +## Когда начинать здесь + +- Пользователя интересуют входящие или исходящие переводы NEAR или FT. +- Нужна лента кошелька, представление для аудита или ответ для поддержки, сфокусированный на движении активов. +- Аккаунт уже известен, и пока не требуется полная история исполнения. +- Для задачи достаточно mainnet-истории переводов. + +## Минимальные входы + +- `account_id` +- выбора сети здесь нет: сегодня эта поверхность доступна только для mainnet +- опциональные фильтры по направлению, активу, сумме или времени +- нужен ли только короткий набор событий или длинный обзор истории +- может ли позже понадобиться более широкий контекст транзакций + +## Частые задачи + +### Найти исходящие переводы одного аккаунта в узком окне времени + +**Начните здесь** + +- [Запрос переводов](/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. + +**Следующая страница при необходимости** + +- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. + +**Остановитесь, когда** + +- Уже можно ответить, кто что отправил, когда и в каком активе. + +**Расширяйте, когда** + +- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](/tx). + +### Построить ленту переводов с пагинацией через `resume_token` + +**Начните здесь** + +- [Запрос переводов](/transfers/query) для первой страницы недавних событий. + +**Следующая страница при необходимости** + +- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. + +**Остановитесь, когда** + +- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. + +**Расширяйте, когда** + +- Пользователь просит метаданные транзакции сверх самих переводов. +- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](/api). + +### Перейти от истории переводов к полному расследованию транзакции + +**Начните здесь** + +- [Запрос переводов](/transfers/query), чтобы выделить конкретные интересующие переводы. + +**Следующая страница при необходимости** + +- [История аккаунта в Transactions API](/tx/account), если нужна окружающая история исполнения для того же аккаунта. +- [Transactions by Hash](/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. + +**Остановитесь, когда** + +- Уже определено правильное событие перевода и следующий подходящий раздел для расследования. + +**Расширяйте, когда** + +- Пользователю прямо нужны квитанции или каноническое подтверждение через RPC. Сначала переходите к [Transactions API](/tx), затем к [RPC Reference](/rpc), если потребуется. + +## Частые ошибки + +- Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. +- Считать историю переводов полной историей исполнения. +- Переиспользовать `resume_token` с другими фильтрами. +- Начинать здесь с вопросов про testnet, хотя эта поверхность сегодня работает только в mainnet. + +## Полезные связанные страницы + +- [Transfers API](/transfers) +- [Transactions API](/tx) +- [FastNear API](/api) +- [Choosing the Right Surface](/agents/choosing-surfaces) +- [Agent Playbooks](/agents/playbooks) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/index.md index 538b46c..f693b41 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/index.md @@ -66,6 +66,10 @@ https://transfers.main.fastnear.com - [Запрос переводов](/transfers/query) — лента по аккаунту с фильтрами по направлению, активу, сумме и времени +## Нужен сценарий? + +Используйте [Transfers API Examples](/transfers/examples) для простых пошаговых сценариев: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. + ## Устранение неполадок ### Нужны полные метаданные транзакции diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md new file mode 100644 index 0000000..79168e3 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -0,0 +1,181 @@ +--- +sidebar_label: Examples +slug: /tx/examples +title: "Примеры Transactions API" +description: "Пошаговые сценарии использования Transactions API для поиска транзакций, расследования квитанций, истории аккаунтов и анализа диапазонов блоков." +displayed_sidebar: transactionsApiSidebar +page_actions: + - markdown +--- + +# Примеры Transactions API + +Используйте эту страницу, когда вопрос звучит как «что произошло?» и нужен индексированный исторический слой до того, как вы перейдёте к каноническому подтверждению через RPC. Начинайте с того идентификатора, который уже есть на руках, объясняйте историю исполнения в читаемом порядке и расширяйте её только тогда, когда действительно понадобятся точные RPC-семантики. + +## Когда начинать здесь + +- У вас уже есть хеш транзакции, ID квитанции, ID аккаунта или ограниченный диапазон блоков. +- Пользователю нужен исторический контекст исполнения, разбор для поддержки или отладки либо читаемая временная шкала. +- Нужна индексированная история без ручной сборки из сырых RPC-вызовов. +- Первый ответ должен объяснить, что произошло, до углубления в протокольные детали. + +## Минимальные входные данные + +- сеть: mainnet или testnet +- основной идентификатор: хеш транзакции, ID квитанции, `account_id` или блок/диапазон блоков +- расследуете ли вы один объект или целое окно истории +- требуется ли точное каноническое подтверждение через RPC до завершения ответа + +## Частые задачи + +### Найти одну транзакцию + +**Начните здесь** + +- [Transactions by Hash](/tx/transactions), когда идентификатор транзакции уже известен. + +**Следующая страница при необходимости** + +- [Receipt Lookup](/tx/receipt), если важной стала последующая квитанция. +- [Block](/tx/block), если нужен контекст блока. +- [Transaction Status](/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. + +**Остановитесь, когда** + +- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. + +**Расширяйте, когда** + +- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. +- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. + +### Исследовать квитанцию + +**Начните здесь** + +- [Receipt Lookup](/tx/receipt), когда ID квитанции — лучший якорь для расследования. + +**Следующая страница при необходимости** + +- [Transactions by Hash](/tx/transactions), чтобы связать квитанцию с исходной транзакцией. +- [Account History](/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. + +**Остановитесь, когда** + +- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. + +**Расширяйте, когда** + +- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](/rpc). +- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. + +### Посмотреть недавнюю активность аккаунта + +**Начните здесь** + +- [Account History](/tx/account) для ленты активности по аккаунту. + +**Следующая страница при необходимости** + +- [Transactions by Hash](/tx/transactions) для конкретной транзакции из ленты. +- [Receipt Lookup](/tx/receipt), если фокус смещается на одну квитанцию. + +**Остановитесь, когда** + +- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. + +**Расширяйте, когда** + +- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](/transfers). +- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](/rpc) или [FastNear API](/api). + +### Восстановить ограниченное окно по блокам + +**Начните здесь** + +- [Blocks](/tx/blocks) для ограниченного просмотра диапазона блоков. +- [Block](/tx/block), когда известен точный блок, который нужно исследовать. + +**Следующая страница при необходимости** + +- [Transactions by Hash](/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. +- [Receipt Lookup](/tx/receipt), если одной квитанции достаточно для следующего шага расследования. + +**Остановитесь, когда** + +- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. + +**Расширяйте, когда** + +- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](/rpc). +- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](/neardata). + +## Готовые расследования + +### Доказать порядок callback-ов в staged/release-сценарии + +Используйте это расследование, когда сначала была стадия с асинхронной подготовкой работы, потом отдельный release, и нужно доказать не только успешность транзакций, но и конкретный порядок выполнения последующих обратных вызовов. + +**Цель** + +- Превратить два хеша транзакций в устойчивый артефакт для расследования, который включает граф квитанций, привязки к блокам и изменения состояния контракта. + +В staged/release-сценариях именно stage-транзакция обычно остаётся главной опорной транзакцией расследования, потому что отложенные callback-и живут на её исходном дереве транзакции, а не на дереве release-транзакции. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Захват трассы stage и release | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Запрашиваем хеш stage-транзакции и хеш release-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому дереву транзакции | +| Проверка материализации stage | RPC [`query(call_function)`](/rpc/contract/call-function) | Опрашиваем view-метод staging-контракта, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления ожидаемых отложенных шагов | Подтверждает, что callback-и действительно стали доступны до того, как release-транзакция попытается их разбудить | +| Обогащение транзакций | Transactions API [`POST /v0/transactions`](/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id` и индексированный статус исполнения | Даёт каждой транзакции устойчивую привязку к блоку, чтобы дальнейший анализ не зависел от памяти или ручных заметок | +| Снимки состояния контракта recorder | RPC [`query(call_function)`](/rpc/contract/call-function) | Читаем состояние контракта recorder до release, а затем опрашиваем его после release до появления ожидаемых записей | Доказывает реальный порядок последующих эффектов в состоянии контракта, а не только в метаданных дерева квитанций | +| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | +| Реконструкция по блокам | Transactions API [`POST /v0/block`](/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | +| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | +| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | + +**Что должен включать полезный ответ** + +- почему именно stage-транзакция, а не release-транзакция, обычно является главной опорной транзакцией расследования +- какой порядок callback-ов вы наблюдали +- в каких блоках стали видны изменения состояния +- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования + +### Начать с receipt ID и восстановить историю исполнения + +Используйте это расследование, когда на руках есть только receipt ID из трассы, лога ошибки или дерева callback-ов и нужно вернуться к понятной человеку истории того, что произошло. + +**Цель** + +- Перейти от одной квитанции к исходной транзакции, а затем расширить расследование ровно настолько, чтобы объяснить окружающее исполнение и эффекты в состоянии. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Якорь по квитанции | Transactions API [`POST /v0/receipt`](/tx/receipt) | Сначала ищем ID квитанции и определяем её содержимое, статус и связанный контекст транзакции | ID квитанции часто появляется в трассах и логах раньше, чем у человека складывается цельная история транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | После перехода из поиска квитанции забираем исходную транзакцию по хешу | Превращает одну квитанцию в читаемую историю исполнения с контекстом по получателю, блоку и статусу | +| Каноническое подтверждение | RPC [`tx`](/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Подтверждаем результат на уровне протокола, когда индексированного вида недостаточно или нужны точные RPC-семантики | Полезно, когда важно различить индексированную интерпретацию и точное поведение RPC | +| Контекст блока | Transactions API [`POST /v0/block`](/tx/block) | Загружаем содержащий блок и при необходимости расширяемся на соседние каскадные блоки, если исполнение растянулось по нескольким высотам | Помещает квитанцию во временную шкалу по блокам, которую проще объяснить | +| Окно активности аккаунта | Transactions API [`POST /v0/account`](/tx/account) | Забираем недавнюю активность аккаунтов, которых коснулась квитанция | Помогает связать квитанцию с окружающей историей по аккаунтам | +| Повторное чтение состояния | RPC [`query(call_function)`](/rpc/contract/call-function) | Повторно запускаем нужный view-метод с закреплённым `block_id`, если квитанция изменила видимое состояние контракта | Позволяет доказать, что квитанция не только существовала в метаданных, но и изменила устойчивое состояние контракта | + +**Что должен включать полезный ответ** + +- какую исходную транзакцию вы восстановили из квитанции +- была ли квитанция главным событием или только одним шагом в большом каскаде +- какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить +- был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым + +## Частые ошибки + +- Пытаться отправлять транзакцию через history API вместо сырого RPC. +- Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. +- Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». +- Повторно использовать непрозрачные токены пагинации с другим эндпоинтом или другим набором фильтров. + +## Полезные связанные страницы + +- [Transactions API](/tx) +- [RPC Reference](/rpc) +- [FastNear API](/api) +- [NEAR Data API](/neardata) +- [Choosing the Right Surface](/agents/choosing-surfaces) +- [Agent Playbooks](/agents/playbooks) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/index.md index 277799b..f9f626f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/index.md @@ -48,6 +48,10 @@ https://tx.test.fastnear.com - [Поиск квитанции](/tx/receipt) — для расследования цепочки исполнения. - [Диапазон блоков](/tx/blocks) — когда нужен ограниченный по диапазону просмотр истории. +## Нужен сценарий? + +Используйте [Transactions API Examples](/tx/examples) для простых пошаговых сценариев: поиск транзакций, расследование квитанций, история аккаунта и анализ диапазонов блоков. + ## Устранение неполадок ### Я ожидал, что здесь можно отправлять транзакции diff --git a/i18n/ru/translation-policy.yml b/i18n/ru/translation-policy.yml index 3740d10..66f68e8 100644 --- a/i18n/ru/translation-policy.yml +++ b/i18n/ru/translation-policy.yml @@ -114,10 +114,16 @@ waves: docs: - redocly-config.md - internationalization.md + - api/examples.md - api/reference.md + - fastdata/kv/examples.md + - neardata/examples.md + - snapshots/examples.mdx - transfers/index.md + - transfers/examples.md - snapshots/mainnet.mdx - snapshots/testnet.mdx + - rpc/examples.md - rpc/account/view-access-key-list.mdx - rpc/account/view-access-key.mdx - rpc/account/view-account.mdx @@ -147,6 +153,7 @@ waves: - rpc/transaction/send-tx.mdx - rpc/transaction/tx-status.mdx - rpc/validators/experimental-validators-ordered.mdx + - tx/examples.md pageModels: - rpc-view-global-contract-code-by-account-id - rpc-view-global-contract-code diff --git a/sidebars.js b/sidebars.js index 112192d..c5924f2 100644 --- a/sidebars.js +++ b/sidebars.js @@ -44,6 +44,7 @@ const nearcoreReleaseSidebarItems = const rpcSidebar = [ ...nearcoreReleaseSidebarItems, 'rpc/index', + 'rpc/examples', 'auth/index', { type: 'category', @@ -125,6 +126,7 @@ const rpcSidebar = [ const fastnearApiSidebar = [ 'api/index', + 'api/examples', { type: 'category', label: 'V0', @@ -159,6 +161,7 @@ const fastnearApiSidebar = [ const transactionsApiSidebar = [ 'tx/index', + 'tx/examples', 'tx/transactions', 'tx/account', 'tx/block', @@ -168,10 +171,11 @@ const transactionsApiSidebar = [ const transfersApiSidebar = hideEarlyApiFamilies ? [] - : ['transfers/index', 'transfers/transfers']; + : ['transfers/index', 'transfers/examples', 'transfers/transfers']; const nearDataApiSidebar = [ 'neardata/index', + 'neardata/examples', 'neardata/first-block', 'neardata/block', 'neardata/block-headers', @@ -187,6 +191,7 @@ const kvFastDataSidebar = hideEarlyApiFamilies ? [] : [ 'fastdata/kv/index', + 'fastdata/kv/examples', 'fastdata/kv/all-by-predecessor', 'fastdata/kv/history-by-predecessor', 'fastdata/kv/latest-by-predecessor', diff --git a/static/ru/api.md b/static/ru/api.md index fb220d4..19898e6 100644 --- a/static/ru/api.md +++ b/static/ru/api.md @@ -46,6 +46,10 @@ https://test.api.fastnear.com - [V1 поиск по публичному ключу](https://docs.fastnear.com/ru/api/v1/public-key) — когда нужно определить аккаунт по ключу. - [V1 топ держателей FT](https://docs.fastnear.com/ru/api/v1/ft-top) — для представлений распределения токенов. +## Нужен сценарий? + +Используйте [FastNear API Examples](https://docs.fastnear.com/ru/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, резолв ключа в аккаунт и переход к узким представлениям активов. + ## Устранение неполадок ### Мне нужно только одно низкоуровневое значение из состояния цепочки diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md new file mode 100644 index 0000000..f3cccc5 --- /dev/null +++ b/static/ru/api/examples.md @@ -0,0 +1,99 @@ +**Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) + +# Примеры FastNear API + +Используйте эту страницу, когда нужен читаемый ответ в форме сводки по аккаунту или активу и хочется пройти по документации FastNear API самым коротким путём. Начинайте с самого узкого эндпоинта, который уже может решить задачу, и расширяйтесь только тогда, когда понадобятся канонические детали RPC или история исполнения. + +## Когда начинать здесь + +- Пользователю нужны балансы, активы, стейкинг или общая сводка по аккаунту в формате кошелька. +- Нужно определить один или несколько аккаунтов по публичному ключу. +- Ответ должен выглядеть как прикладные данные, а не как сырой JSON-RPC. +- Нужен быстрый первый ответ до того, как станет понятно, требуется ли каноническое подтверждение через RPC. + +## Минимальные входные данные + +- сеть: mainnet или testnet +- основной идентификатор: `account_id` или публичный ключ +- нужен ли широкий обзор или одна конкретная категория активов +- понадобится ли затем точное каноническое подтверждение или история активности + +## Частые задачи + +### Получить сводку по аккаунту в формате кошелька + +**Начните здесь** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) для самого широкого снимка аккаунта. + +**Следующая страница при необходимости** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking) для более узкого продолжения. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?» + +**Остановитесь, когда** + +- Сводка уже отвечает на вопрос о портфеле или активах в нужной пользователю форме. + +**Расширяйте, когда** + +- Пользователь спрашивает о точной канонической семантике аккаунта или ключей доступа. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Определить аккаунты по публичному ключу + +**Начните здесь** + +- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. +- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. + +**Остановитесь, когда** + +- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. + +**Расширяйте, когда** + +- Пользователь спрашивает о точных правах ключа, nonce или каноническом состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). +- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Продолжить по одной категории активов, а не по всему аккаунту + +**Начните здесь** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft) для балансов FT-токенов. +- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) для владения NFT. +- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking) для позиций стейкинга. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если позже понадобится более широкий снимок аккаунта. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос смещается к тому, как активы менялись со временем. + +**Остановитесь, когда** + +- Эндпоинт по конкретной категории активов уже даёт готовый продуктовый ответ без дополнительной реконструкции. + +**Расширяйте, когда** + +- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +## Частые ошибки + +- Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. +- Использовать FastNear API, хотя пользователю прямо нужны канонические поля RPC или права доступа. +- Оставаться на страницах сводок по аккаунту, когда вопрос уже стал вопросом об истории транзакций. +- Забывать, что `?network=testnet` поддерживается только на совместимых страницах. + +## Полезные связанные страницы + +- [FastNear API](https://docs.fastnear.com/ru/api) +- [API Reference](https://docs.fastnear.com/ru/api/reference) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md new file mode 100644 index 0000000..f3cccc5 --- /dev/null +++ b/static/ru/api/examples/index.md @@ -0,0 +1,99 @@ +**Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) + +# Примеры FastNear API + +Используйте эту страницу, когда нужен читаемый ответ в форме сводки по аккаунту или активу и хочется пройти по документации FastNear API самым коротким путём. Начинайте с самого узкого эндпоинта, который уже может решить задачу, и расширяйтесь только тогда, когда понадобятся канонические детали RPC или история исполнения. + +## Когда начинать здесь + +- Пользователю нужны балансы, активы, стейкинг или общая сводка по аккаунту в формате кошелька. +- Нужно определить один или несколько аккаунтов по публичному ключу. +- Ответ должен выглядеть как прикладные данные, а не как сырой JSON-RPC. +- Нужен быстрый первый ответ до того, как станет понятно, требуется ли каноническое подтверждение через RPC. + +## Минимальные входные данные + +- сеть: mainnet или testnet +- основной идентификатор: `account_id` или публичный ключ +- нужен ли широкий обзор или одна конкретная категория активов +- понадобится ли затем точное каноническое подтверждение или история активности + +## Частые задачи + +### Получить сводку по аккаунту в формате кошелька + +**Начните здесь** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) для самого широкого снимка аккаунта. + +**Следующая страница при необходимости** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking) для более узкого продолжения. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?» + +**Остановитесь, когда** + +- Сводка уже отвечает на вопрос о портфеле или активах в нужной пользователю форме. + +**Расширяйте, когда** + +- Пользователь спрашивает о точной канонической семантике аккаунта или ключей доступа. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Определить аккаунты по публичному ключу + +**Начните здесь** + +- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. +- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. + +**Остановитесь, когда** + +- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. + +**Расширяйте, когда** + +- Пользователь спрашивает о точных правах ключа, nonce или каноническом состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). +- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Продолжить по одной категории активов, а не по всему аккаунту + +**Начните здесь** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft) для балансов FT-токенов. +- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) для владения NFT. +- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking) для позиций стейкинга. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если позже понадобится более широкий снимок аккаунта. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос смещается к тому, как активы менялись со временем. + +**Остановитесь, когда** + +- Эндпоинт по конкретной категории активов уже даёт готовый продуктовый ответ без дополнительной реконструкции. + +**Расширяйте, когда** + +- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +## Частые ошибки + +- Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. +- Использовать FastNear API, хотя пользователю прямо нужны канонические поля RPC или права доступа. +- Оставаться на страницах сводок по аккаунту, когда вопрос уже стал вопросом об истории транзакций. +- Забывать, что `?network=testnet` поддерживается только на совместимых страницах. + +## Полезные связанные страницы + +- [FastNear API](https://docs.fastnear.com/ru/api) +- [API Reference](https://docs.fastnear.com/ru/api/reference) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/api/index.md b/static/ru/api/index.md index fb220d4..19898e6 100644 --- a/static/ru/api/index.md +++ b/static/ru/api/index.md @@ -46,6 +46,10 @@ https://test.api.fastnear.com - [V1 поиск по публичному ключу](https://docs.fastnear.com/ru/api/v1/public-key) — когда нужно определить аккаунт по ключу. - [V1 топ держателей FT](https://docs.fastnear.com/ru/api/v1/ft-top) — для представлений распределения токенов. +## Нужен сценарий? + +Используйте [FastNear API Examples](https://docs.fastnear.com/ru/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, резолв ключа в аккаунт и переход к узким представлениям активов. + ## Устранение неполадок ### Мне нужно только одно низкоуровневое значение из состояния цепочки diff --git a/static/ru/fastdata/kv.md b/static/ru/fastdata/kv.md index 0f0a509..aabae60 100644 --- a/static/ru/fastdata/kv.md +++ b/static/ru/fastdata/kv.md @@ -43,6 +43,10 @@ https://kv.test.fastnear.com - [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) или [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) — когда правильная область — `predecessor_id` - [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi) — когда уже известно несколько точных ключей +## Нужен сценарий? + +Используйте [KV FastData Examples](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: exact-key lookups, история ключей, анализ по `predecessor_id` и переход к каноническому RPC. + ## Рабочий цикл по умолчанию 1. Выберите самую узкую область, подходящую под вопрос пользователя. diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md new file mode 100644 index 0000000..95c15da --- /dev/null +++ b/static/ru/fastdata/kv/examples.md @@ -0,0 +1,133 @@ +**Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) + +# Примеры KV FastData API + +Используйте эту страницу, когда вопрос касается индексированного хранилища контракта и уже есть точная область поиска. Главное решение на этой поверхности — выбрать самую узкую полезную область: точный ключ, аккаунт, `predecessor_id` или набор известных ключей. Оставайтесь внутри KV FastData, пока речь идёт об индексированных данных key-value, и переходите к RPC только тогда, когда нужно каноническое состояние в цепочке. + +## Когда начинать здесь + +- Нужны индексированные данные хранилища контракта, а не широкие сводки по аккаунтам или активам. +- Уже известен контракт, точный ключ, `predecessor_id` или область по аккаунту. +- Нужны последние индексированные записи или история изменений во времени. +- Нужен быстрый ответ по хранилищу до решения о дальнейшей проверке через RPC. + +## Минимальные входы + +- сеть +- ID контракта плюс одно из: точный ключ, область аккаунта, область по `predecessor_id` или известный набор ключей +- нужно ли последнее индексированное состояние или история изменений +- может ли позже понадобиться каноническая проверка + +## Частые задачи + +### Посмотреть один точный ключ прямо сейчас + +**Начните здесь** + +- [Последнее по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), когда точный ключ уже известен. + +**Следующая страница при необходимости** + +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если вопрос превращается в «как менялся этот ключ?» + +**Остановитесь, когда** + +- Последняя индексированная запись уже отвечает на вопрос о хранилище. + +**Расширяйте, когда** + +- Пользователю нужно точное текущее состояние в цепочке, а не индексированное хранилище. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). + +### Превратить один точный ключ в историю изменений + +**Начните здесь** + +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для поиска истории по пути. +- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. + +**Следующая страница при необходимости** + +- Возвращайтесь к [Последнему по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. + +**Остановитесь, когда** + +- Уже можно объяснить, как ключ менялся со временем. + +**Расширяйте, когда** + +- Пользователь спрашивает, совпадает ли последнее индексированное значение с каноническим состоянием цепочки прямо сейчас. + +### Проследить записи от одного `predecessor_id` + +**Начните здесь** + +- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. +- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. + +**Следующая страница при необходимости** + +- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. + +**Остановитесь, когда** + +- Уже можно ответить, что именно этот предшественник изменил и где. + +**Расширяйте, когда** + +- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее каноническое состояние. + +### Пакетно проверить несколько известных ключей + +**Начните здесь** + +- [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. + +**Следующая страница при необходимости** + +- Переведите один интересный ключ в [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. + +**Остановитесь, когда** + +- Пакетный ответ уже показывает, какие ключи действительно важны. + +**Расширяйте, когда** + +- Пользователь хочет более широкий анализ контракта вместо фиксированного набора ключей. + +## Готовое расследование + +### Начать с одного индексированного ключа, затем подтвердить историю и каноническое состояние + +Используйте это расследование, когда один ключ контракта выглядит подозрительно и нужно связать его последнее индексированное значение, историю изменений и каноническую проверку через `view_state` в одну ясную историю. + +**Цель** + +- Объяснить, как ключ контракта выглядит сейчас, как он пришёл к этому состоянию в индексированной истории и совпадает ли с этим каноническое состояние RPC. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | +| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | +| Расширение области | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Расширяемся до области аккаунта или предшественника, если один ключ — только часть более широкой картины изменений | Помогает понять, менялся ли ключ изолированно или как часть более широкого набора записей | +| Каноническое подтверждение | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированный паттерн уже понятен | Разделяет индексированную историю хранилища и точное текущее состояние в цепочке | + +**Что должен включать полезный ответ** + +- какой именно ключ и какая область контракта были исследованы +- как выглядит последнее индексированное значение и какие изменения видны в истории +- совпал ли `view_state` с текущим индексированным значением + +## Частые ошибки + +- Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. +- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. +- Путать индексированную историю с точным текущим состоянием в цепочке. +- Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. + +## Полезные связанные страницы + +- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md new file mode 100644 index 0000000..95c15da --- /dev/null +++ b/static/ru/fastdata/kv/examples/index.md @@ -0,0 +1,133 @@ +**Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) + +# Примеры KV FastData API + +Используйте эту страницу, когда вопрос касается индексированного хранилища контракта и уже есть точная область поиска. Главное решение на этой поверхности — выбрать самую узкую полезную область: точный ключ, аккаунт, `predecessor_id` или набор известных ключей. Оставайтесь внутри KV FastData, пока речь идёт об индексированных данных key-value, и переходите к RPC только тогда, когда нужно каноническое состояние в цепочке. + +## Когда начинать здесь + +- Нужны индексированные данные хранилища контракта, а не широкие сводки по аккаунтам или активам. +- Уже известен контракт, точный ключ, `predecessor_id` или область по аккаунту. +- Нужны последние индексированные записи или история изменений во времени. +- Нужен быстрый ответ по хранилищу до решения о дальнейшей проверке через RPC. + +## Минимальные входы + +- сеть +- ID контракта плюс одно из: точный ключ, область аккаунта, область по `predecessor_id` или известный набор ключей +- нужно ли последнее индексированное состояние или история изменений +- может ли позже понадобиться каноническая проверка + +## Частые задачи + +### Посмотреть один точный ключ прямо сейчас + +**Начните здесь** + +- [Последнее по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), когда точный ключ уже известен. + +**Следующая страница при необходимости** + +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если вопрос превращается в «как менялся этот ключ?» + +**Остановитесь, когда** + +- Последняя индексированная запись уже отвечает на вопрос о хранилище. + +**Расширяйте, когда** + +- Пользователю нужно точное текущее состояние в цепочке, а не индексированное хранилище. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). + +### Превратить один точный ключ в историю изменений + +**Начните здесь** + +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для поиска истории по пути. +- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. + +**Следующая страница при необходимости** + +- Возвращайтесь к [Последнему по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. + +**Остановитесь, когда** + +- Уже можно объяснить, как ключ менялся со временем. + +**Расширяйте, когда** + +- Пользователь спрашивает, совпадает ли последнее индексированное значение с каноническим состоянием цепочки прямо сейчас. + +### Проследить записи от одного `predecessor_id` + +**Начните здесь** + +- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. +- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. + +**Следующая страница при необходимости** + +- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. + +**Остановитесь, когда** + +- Уже можно ответить, что именно этот предшественник изменил и где. + +**Расширяйте, когда** + +- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее каноническое состояние. + +### Пакетно проверить несколько известных ключей + +**Начните здесь** + +- [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. + +**Следующая страница при необходимости** + +- Переведите один интересный ключ в [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. + +**Остановитесь, когда** + +- Пакетный ответ уже показывает, какие ключи действительно важны. + +**Расширяйте, когда** + +- Пользователь хочет более широкий анализ контракта вместо фиксированного набора ключей. + +## Готовое расследование + +### Начать с одного индексированного ключа, затем подтвердить историю и каноническое состояние + +Используйте это расследование, когда один ключ контракта выглядит подозрительно и нужно связать его последнее индексированное значение, историю изменений и каноническую проверку через `view_state` в одну ясную историю. + +**Цель** + +- Объяснить, как ключ контракта выглядит сейчас, как он пришёл к этому состоянию в индексированной истории и совпадает ли с этим каноническое состояние RPC. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | +| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | +| Расширение области | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Расширяемся до области аккаунта или предшественника, если один ключ — только часть более широкой картины изменений | Помогает понять, менялся ли ключ изолированно или как часть более широкого набора записей | +| Каноническое подтверждение | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированный паттерн уже понятен | Разделяет индексированную историю хранилища и точное текущее состояние в цепочке | + +**Что должен включать полезный ответ** + +- какой именно ключ и какая область контракта были исследованы +- как выглядит последнее индексированное значение и какие изменения видны в истории +- совпал ли `view_state` с текущим индексированным значением + +## Частые ошибки + +- Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. +- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. +- Путать индексированную историю с точным текущим состоянием в цепочке. +- Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. + +## Полезные связанные страницы + +- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/fastdata/kv/index.md b/static/ru/fastdata/kv/index.md index 0f0a509..aabae60 100644 --- a/static/ru/fastdata/kv/index.md +++ b/static/ru/fastdata/kv/index.md @@ -43,6 +43,10 @@ https://kv.test.fastnear.com - [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) или [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) — когда правильная область — `predecessor_id` - [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi) — когда уже известно несколько точных ключей +## Нужен сценарий? + +Используйте [KV FastData Examples](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: exact-key lookups, история ключей, анализ по `predecessor_id` и переход к каноническому RPC. + ## Рабочий цикл по умолчанию 1. Выберите самую узкую область, подходящую под вопрос пользователя. diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index b2bad97..b072383 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -13,8 +13,10 @@ - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. +- [Примеры KV FastData API](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии использования KV FastData для точных ключей, истории ключей, анализа по предшественнику и перехода к каноническому RPC. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. +- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. - [API переводов](https://docs.fastnear.com/ru/transfers.md): История переводов NEAR и FT-токенов по аккаунтам для продуктовых лент и инструментов расследования. - [Транзакции API](https://docs.fastnear.com/ru/tx.md): Индексированные запросы по транзакциям, квитанциям, истории аккаунтов и истории блоков для FastNear. @@ -27,11 +29,16 @@ ## Другие гайды +- [Примеры FastNear API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для сводок по аккаунтам, поиска по ключам и перехода к более узким представлениям активов. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. +- [Примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии использования NEAR Data API для опроса блоков, маршрутов перенаправления и перехода к каноническому RPC. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии использования Transfers API для узкой истории переводов, пагинации и перехода к более широкому расследованию. +- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые сценарии использования Transactions API для поиска транзакций, расследования квитанций, истории аккаунтов и анализа диапазонов блоков. ## Снапшоты - [Снапшоты для валидаторов](https://docs.fastnear.com/ru/snapshots.md): Пути загрузки снапшотов FastNear для подъёма и восстановления узлов NEAR. +- [Примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples.md): Пошаговые операторские сценарии для выбора правильного пути снапшотов FastNear и выполнения типовых задач запуска и восстановления. - [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. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index ae95642..799e243 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -967,6 +967,10 @@ https://test.api.fastnear.com - [V1 поиск по публичному ключу](https://docs.fastnear.com/ru/api/v1/public-key) — когда нужно определить аккаунт по ключу. - [V1 топ держателей FT](https://docs.fastnear.com/ru/api/v1/ft-top) — для представлений распределения токенов. +## Нужен сценарий? + +Используйте [FastNear API Examples](https://docs.fastnear.com/ru/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, резолв ключа в аккаунт и переход к узким представлениям активов. + ## Устранение неполадок ### Мне нужно только одно низкоуровневое значение из состояния цепочки @@ -983,6 +987,113 @@ https://test.api.fastnear.com --- +## Примеры FastNear API + +- HTML-маршрут: https://docs.fastnear.com/ru/api/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/api/examples.md + +**Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) + +# Примеры FastNear API + +Используйте эту страницу, когда нужен читаемый ответ в форме сводки по аккаунту или активу и хочется пройти по документации FastNear API самым коротким путём. Начинайте с самого узкого эндпоинта, который уже может решить задачу, и расширяйтесь только тогда, когда понадобятся канонические детали RPC или история исполнения. + +## Когда начинать здесь + +- Пользователю нужны балансы, активы, стейкинг или общая сводка по аккаунту в формате кошелька. +- Нужно определить один или несколько аккаунтов по публичному ключу. +- Ответ должен выглядеть как прикладные данные, а не как сырой JSON-RPC. +- Нужен быстрый первый ответ до того, как станет понятно, требуется ли каноническое подтверждение через RPC. + +## Минимальные входные данные + +- сеть: mainnet или testnet +- основной идентификатор: `account_id` или публичный ключ +- нужен ли широкий обзор или одна конкретная категория активов +- понадобится ли затем точное каноническое подтверждение или история активности + +## Частые задачи + +### Получить сводку по аккаунту в формате кошелька + +**Начните здесь** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) для самого широкого снимка аккаунта. + +**Следующая страница при необходимости** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking) для более узкого продолжения. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?» + +**Остановитесь, когда** + +- Сводка уже отвечает на вопрос о портфеле или активах в нужной пользователю форме. + +**Расширяйте, когда** + +- Пользователь спрашивает о точной канонической семантике аккаунта или ключей доступа. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Определить аккаунты по публичному ключу + +**Начните здесь** + +- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. +- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. + +**Остановитесь, когда** + +- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. + +**Расширяйте, когда** + +- Пользователь спрашивает о точных правах ключа, nonce или каноническом состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). +- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Продолжить по одной категории активов, а не по всему аккаунту + +**Начните здесь** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft) для балансов FT-токенов. +- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) для владения NFT. +- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking) для позиций стейкинга. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если позже понадобится более широкий снимок аккаунта. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос смещается к тому, как активы менялись со временем. + +**Остановитесь, когда** + +- Эндпоинт по конкретной категории активов уже даёт готовый продуктовый ответ без дополнительной реконструкции. + +**Расширяйте, когда** + +- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +## Частые ошибки + +- Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. +- Использовать FastNear API, хотя пользователю прямо нужны канонические поля RPC или права доступа. +- Оставаться на страницах сводок по аккаунту, когда вопрос уже стал вопросом об истории транзакций. +- Забывать, что `?network=testnet` поддерживается только на совместимых страницах. + +## Полезные связанные страницы + +- [FastNear API](https://docs.fastnear.com/ru/api) +- [API Reference](https://docs.fastnear.com/ru/api/reference) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) + +--- + ## Справочник API - HTML-маршрут: https://docs.fastnear.com/ru/api/reference @@ -1127,6 +1238,10 @@ https://kv.test.fastnear.com - [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) или [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) — когда правильная область — `predecessor_id` - [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi) — когда уже известно несколько точных ключей +## Нужен сценарий? + +Используйте [KV FastData Examples](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: exact-key lookups, история ключей, анализ по `predecessor_id` и переход к каноническому RPC. + ## Рабочий цикл по умолчанию 1. Выберите самую узкую область, подходящую под вопрос пользователя. @@ -1161,6 +1276,147 @@ https://kv.test.fastnear.com --- +## Примеры KV FastData API + +- HTML-маршрут: https://docs.fastnear.com/ru/fastdata/kv/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/fastdata/kv/examples.md + +**Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) + +# Примеры KV FastData API + +Используйте эту страницу, когда вопрос касается индексированного хранилища контракта и уже есть точная область поиска. Главное решение на этой поверхности — выбрать самую узкую полезную область: точный ключ, аккаунт, `predecessor_id` или набор известных ключей. Оставайтесь внутри KV FastData, пока речь идёт об индексированных данных key-value, и переходите к RPC только тогда, когда нужно каноническое состояние в цепочке. + +## Когда начинать здесь + +- Нужны индексированные данные хранилища контракта, а не широкие сводки по аккаунтам или активам. +- Уже известен контракт, точный ключ, `predecessor_id` или область по аккаунту. +- Нужны последние индексированные записи или история изменений во времени. +- Нужен быстрый ответ по хранилищу до решения о дальнейшей проверке через RPC. + +## Минимальные входы + +- сеть +- ID контракта плюс одно из: точный ключ, область аккаунта, область по `predecessor_id` или известный набор ключей +- нужно ли последнее индексированное состояние или история изменений +- может ли позже понадобиться каноническая проверка + +## Частые задачи + +### Посмотреть один точный ключ прямо сейчас + +**Начните здесь** + +- [Последнее по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), когда точный ключ уже известен. + +**Следующая страница при необходимости** + +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если вопрос превращается в «как менялся этот ключ?» + +**Остановитесь, когда** + +- Последняя индексированная запись уже отвечает на вопрос о хранилище. + +**Расширяйте, когда** + +- Пользователю нужно точное текущее состояние в цепочке, а не индексированное хранилище. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). + +### Превратить один точный ключ в историю изменений + +**Начните здесь** + +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для поиска истории по пути. +- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. + +**Следующая страница при необходимости** + +- Возвращайтесь к [Последнему по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. + +**Остановитесь, когда** + +- Уже можно объяснить, как ключ менялся со временем. + +**Расширяйте, когда** + +- Пользователь спрашивает, совпадает ли последнее индексированное значение с каноническим состоянием цепочки прямо сейчас. + +### Проследить записи от одного `predecessor_id` + +**Начните здесь** + +- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. +- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. + +**Следующая страница при необходимости** + +- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. + +**Остановитесь, когда** + +- Уже можно ответить, что именно этот предшественник изменил и где. + +**Расширяйте, когда** + +- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее каноническое состояние. + +### Пакетно проверить несколько известных ключей + +**Начните здесь** + +- [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. + +**Следующая страница при необходимости** + +- Переведите один интересный ключ в [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. + +**Остановитесь, когда** + +- Пакетный ответ уже показывает, какие ключи действительно важны. + +**Расширяйте, когда** + +- Пользователь хочет более широкий анализ контракта вместо фиксированного набора ключей. + +## Готовое расследование + +### Начать с одного индексированного ключа, затем подтвердить историю и каноническое состояние + +Используйте это расследование, когда один ключ контракта выглядит подозрительно и нужно связать его последнее индексированное значение, историю изменений и каноническую проверку через `view_state` в одну ясную историю. + +**Цель** + +- Объяснить, как ключ контракта выглядит сейчас, как он пришёл к этому состоянию в индексированной истории и совпадает ли с этим каноническое состояние RPC. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | +| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | +| Расширение области | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Расширяемся до области аккаунта или предшественника, если один ключ — только часть более широкой картины изменений | Помогает понять, менялся ли ключ изолированно или как часть более широкого набора записей | +| Каноническое подтверждение | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированный паттерн уже понятен | Разделяет индексированную историю хранилища и точное текущее состояние в цепочке | + +**Что должен включать полезный ответ** + +- какой именно ключ и какая область контракта были исследованы +- как выглядит последнее индексированное значение и какие изменения видны в истории +- совпал ли `view_state` с текущим индексированным значением + +## Частые ошибки + +- Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. +- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. +- Путать индексированную историю с точным текущим состоянием в цепочке. +- Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. + +## Полезные связанные страницы + +- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) + +--- + ## Руководство по интернационализации - HTML-маршрут: https://docs.fastnear.com/ru/internationalization @@ -1467,6 +1723,10 @@ https://testnet.neardata.xyz - [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block) и [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers) — для запросов по финализированным блокам. - [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) и [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) — когда нужны вспомогательные маршруты с перенаправлением. +## Нужен сценарий? + +Используйте [NEAR Data API Examples](https://docs.fastnear.com/ru/neardata/examples) для простых пошаговых сценариев: optimistic polling, finalized confirmation, redirect helpers и переход к каноническому RPC. + ## Устранение неполадок ### Некоторые эндпоинты перенаправляют на канонический URL вместо прямого возврата результата @@ -1483,6 +1743,148 @@ https://testnet.neardata.xyz --- +## Примеры NEAR Data API + +- HTML-маршрут: https://docs.fastnear.com/ru/neardata/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/neardata/examples.md + +**Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) + +# Примеры NEAR Data API + +Используйте эту страницу, когда свежесть важнее протокольной точности. NEAR Data API предназначен для опроса и чтения недавних данных по семейству блоков: начинайте с самого свежего или самого стабильного режима блоков, который подходит задаче, оставайтесь на этой поверхности для опроса, пока она отвечает на вопрос, и переходите к RPC только тогда, когда действительно нужна каноническая семантика блока или состояния. + +## Когда начинать здесь + +- Нужны недавние оптимистичные или финализированные данные по семейству блоков. +- Нужен клиент опроса, монитор или проверка свежести. +- Маршруты перенаправления приемлемы или полезны для клиентского сценария. +- Задача звучит как «что изменилось недавно?», а не как каноническое историческое подтверждение. + +## Минимальные входы + +- сеть +- режим свежести: optimistic или finalized +- есть ли конкретная высота или хеш, либо нужен самый свежий объект семейства блоков +- может ли клиент корректно следовать перенаправлениям +- потребуется ли позже проверка через RPC + +## Частые задачи + +### Отслеживать последний оптимистичный блок + +**Начните здесь** + +- [Оптимистичный блок](https://docs.fastnear.com/ru/neardata/block-optimistic) для самого свежего чтения по семейству блоков. + +**Следующая страница при необходимости** + +- [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic), если нужен маршрут перенаправления, который всегда ведёт к самому новому оптимистичному блоку. + +**Остановитесь, когда** + +- Уже можно сообщить о последнем оптимистичном блоке или зафиксировать отставание по свежести. + +**Расширяйте, когда** + +- Нужна finalized-стабильность вместо максимальной свежести. Переходите к [Финализированному блоку по высоте](https://docs.fastnear.com/ru/neardata/block) или [Перенаправлению на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final). + +### Безопасно отслеживать ход финализации блоков + +**Начните здесь** + +- [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block), когда уже известна нужная высота. +- [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers), когда достаточно чтения заголовков. + +**Следующая страница при необходимости** + +- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final), когда клиент должен следовать за самым новым финализированным блоком без предварительного вычисления высоты. + +**Остановитесь, когда** + +- Уже можно показывать движение финализированных блоков без перехода к более глубоким протокольным деталям. + +**Расширяйте, когда** + +- Пользователю нужны точные канонические поля блока или семантика транзакций. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). + +### Использовать маршруты перенаправления в клиенте опроса + +**Начните здесь** + +- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) или [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) в зависимости от требуемой свежести. + +**Следующая страница при необходимости** + +- Следуйте по канонической цели, которую вернул маршрут перенаправления, и уже там читайте нужные данные по семейству блоков. + +**Остановитесь, когда** + +- Клиент надёжно проходит по маршруту перенаправления и получает нужный ресурс блока. + +**Расширяйте, когда** + +- Само перенаправление мешает клиенту. Тогда переходите на прямые маршруты блоков. + +### Перейти от свежего опроса к каноническому разбору через RPC + +**Начните здесь** + +- Используйте подходящий маршрут NEAR Data, чтобы найти недавний блок или событие в семействе блоков, которое нужно исследовать. + +**Следующая страница при необходимости** + +- [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или другой RPC-метод, как только станет понятно, какой именно блок или следующий объект для проверки нужен. + +**Остановитесь, когда** + +- Уже можно чётко назвать недавний блок, который заслуживает канонической проверки. + +**Расширяйте, когда** + +- Пользователь просит точную структуру данных в терминах протокола, а не просто свежее чтение. + +## Готовое расследование + +### Начать с оптимистичного блока, затем подтвердить финализированную и каноническую картину + +Используйте это расследование, когда нужно раннее обнаружение по оптимистичному блоку, но финальный ответ всё равно должен опираться на стабильный финализированный вид и, при необходимости, на каноническое подтверждение через RPC. + +**Цель** + +- Быстро заметить недавнее изменение, а затем сузить его до финализированной и канонической истории блока без лишних запросов. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](https://docs.fastnear.com/ru/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | +| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](https://docs.fastnear.com/ru/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | +| Стабильное подтверждение | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) или [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | +| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | +| Каноническая проверка | RPC [Блок по ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Блок по высоте](https://docs.fastnear.com/ru/rpc/block/block-by-height) | Получаем точный канонический блок, как только понятно, какой именно блок важен | Переводит расследование от свежих данных к протокольно точному подтверждению только когда это действительно нужно | + +**Что должен включать полезный ответ** + +- какое наблюдение по оптимистичному блоку впервые запустило расследование +- когда то же наблюдение стало финализированным +- изменил ли канонический разбор через RPC интерпретацию + +## Частые ошибки + +- Воспринимать NEAR Data API как потоковый продукт, а не как поверхность для опроса. +- Начинать с канонического RPC, когда настоящая задача — мониторинг свежих блоков. +- Забывать, что невалидный ключ может вернуть `401` ещё до перенаправления, а сами перенаправления подходят не каждому HTTP-клиенту. +- Оставаться на NEAR Data после того, как пользователь уже попросил точные протокольные детали блока. + +## Полезные связанные страницы + +- [NEAR Data API](https://docs.fastnear.com/ru/neardata) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) + +--- + ## redocly-config - HTML-маршрут: https://docs.fastnear.com/ru/redocly-config @@ -1583,6 +1985,10 @@ https://archival-rpc.testnet.fastnear.com - [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) — отправка транзакций; [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) — статус исполнения. - [`validators`](https://docs.fastnear.com/ru/rpc/validators/validators-current) — валидаторы текущей эпохи. +## Нужен сценарий? + +Используйте [RPC Examples](https://docs.fastnear.com/ru/rpc/examples) для простых пошаговых сценариев: точные проверки состояния, анализ блоков, view-вызовы контрактов и отправка с подтверждением транзакций. + ## Используйте RPC, когда - нужны канонические формы запросов и ответов из протокола; @@ -1627,6 +2033,138 @@ https://archival-rpc.testnet.fastnear.com --- +## Примеры RPC + +- HTML-маршрут: https://docs.fastnear.com/ru/rpc/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/rpc/examples.md + +**Источник:** [https://docs.fastnear.com/ru/rpc/examples](https://docs.fastnear.com/ru/rpc/examples) + +# Примеры RPC + +Используйте эту страницу, когда уже понятно, что ответ должен опираться на каноническое поведение RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы выбрать правильную стартовую страницу, остановиться, как только RPC-ответ уже решает задачу, и расширять набор поверхностей только тогда, когда это действительно поможет. + +## Когда начинать здесь + +- Пользователь просит точное состояние в цепочке или поля в протокольной форме. +- Нужен прямой вызов view-метода контракта или сценарий отправки транзакции. +- Нужно исследовать блоки, чанки, валидаторов или метаданные протокола. +- Важна семантика узла, а не индексированное агрегированное представление. + +## Минимальные входные данные + +- сеть: mainnet или testnet +- основной идентификатор: `account_id`, публичный ключ, ID контракта плюс метод, хеш транзакции или высота/хеш блока +- нужно ли текущее состояние, историческое состояние или поведение отправки/финальности +- должен ли результат остаться каноническим или затем превратиться в более удобное для человека резюме + +## Частые задачи + +### Проверить точное состояние аккаунта или ключа доступа + +**Начните здесь** + +- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для канонических полей аккаунта. +- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. + +**Следующая страница при необходимости** + +- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после подтверждения канонического состояния нужна ещё и сводка в формате кошелька. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» + +**Остановитесь, когда** + +- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. + +**Расширяйте, когда** + +- Пользователю нужны балансы, NFT, стейкинг или другой продуктовый вид данных. +- Пользователя интересует не текущее каноническое состояние, а недавняя история активности. + +### Проверить блок или снимок состояния протокола + +**Начните здесь** + +- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height) для конкретного блока. +- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block) для текущей канонической головы цепочки. +- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info) для диагностики узла и сети. + +**Следующая страница при необходимости** + +- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если после поиска блока нужен контекст по изменениям состояния. +- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если нужна более читаемая картина исполнения в рамках блока или диапазона. + +**Остановитесь, когда** + +- Канонический ответ блока или протокола уже напрямую отвечает на вопрос. + +**Расширяйте, когда** + +- Нужны данные по свежим блокам в режиме опроса, а не один канонический снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +- Нужна история по нескольким транзакциям, а не только ответ одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Выполнить view-вызов контракта + +**Начните здесь** + +- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function) для view-метода контракта. +- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда вопрос касается сырого хранилища контракта. +- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда на самом деле нужно понять, есть ли код и каков его хеш. + +**Следующая страница при необходимости** + +- [FastNear API](https://docs.fastnear.com/ru/api), если после сырого вызова пользователю нужен продуктовый ответ, например по активам или сводке аккаунта. +- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующая задача связана с индексированной историей по ключам и значениям, а не с точным RPC-чтением. + +**Остановитесь, когда** + +- Результат view-вызова уже отвечает на вопрос в канонической форме. + +**Расширяйте, когда** + +- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. +- Вопрос смещается от «что метод возвращает сейчас?» к «что менялось со временем?» + +### Отправить транзакцию и подтвердить результат + +**Начните здесь** + +- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужно каноническое поведение отправки с явной семантикой ожидания. +- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить канонический результат. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. + +**Остановитесь, когда** + +- У вас уже есть результат отправки и нужный канонический финальный статус. + +**Расширяйте, когда** + +- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. +- Нужен уже не единичный статус, а более широкий сценарий расследования. + +## Частые ошибки + +- Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. +- Забывать переключаться с обычного RPC на архивный RPC для старого состояния. +- Воспринимать браузерную аутентификацию в интерфейсе документации как продовый паттерн для бэкенда. +- Продолжать пользоваться низкоуровневыми статусами транзакций, когда вопрос уже превратился в расследование или исторический разбор. + +## Полезные связанные страницы + +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [Auth & Access](https://docs.fastnear.com/ru/auth) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) + +--- + ## Снапшоты для валидаторов - HTML-маршрут: https://docs.fastnear.com/ru/snapshots @@ -1681,6 +2219,10 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash Требования к узлам смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near), а исходники скриптов загрузки, которые используются в этих руководствах, — в [fastnear/static](https://github.com/fastnear/static). +## Нужен сценарий? + +Используйте [Snapshot Examples](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбор между optimized `fast-rpc`, standard RPC recovery и archival hot/cold snapshot paths. + ## Выберите сеть - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) @@ -1688,6 +2230,148 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash --- +## Примеры снапшотов + +- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/examples.md + +**Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) + +# Примеры снапшотов + +Используйте эту страницу, когда вопрос звучит как «какой сценарий со снапшотом вообще запускать?», а не «какая команда существует?». Снапшоты — это операторские сценарии, а не API для данных: сначала выбирайте правильную цель узла, сеть и схему хранения, а затем запускайте самый узкий путь загрузки, который безопасно поднимет узел. + +## Когда начинать здесь + +- Нужно поднять или восстановить узел. +- Нужно выбрать между обычным RPC, optimized `fast-rpc` и архивным сценарием с hot/cold-данными. +- Нужен самый короткий путь от задачи оператора к правильной последовательности команд. +- Уже понятно, что это инфраструктурная работа, а не чтение прикладных данных. + +## Минимальные входы + +- сеть: mainnet или testnet +- цель узла: обычный RPC, optimized `fast-rpc` или архивный режим +- схема хранения, особенно возможность разделить hot- и cold-архивные данные +- используется ли стандартный каталог данных nearcore или отдельные каталоги для hot/cold +- целевой путь к данным и примерные ограничения по загрузке + +## Частые задачи + +### Поднять optimized `fast-rpc`-узел в mainnet + +**Начните здесь** + +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), конкретно путь для optimized `fast-rpc`. + +**Следующая страница при необходимости** + +- Вернитесь к обычному сценарию mainnet RPC, если узел не подходит для optimized profile. + +**Остановитесь, когда** + +- Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. + +**Расширяйте, когда** + +- На самом деле требуется архивное хранение, а не просто быстрый запуск. + +### Восстановить обычный RPC-узел в стандартный каталог nearcore + +**Начните здесь** + +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) или [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) в зависимости от сети и выберите обычный RPC-сценарий для нужного окружения. + +**Следующая страница при необходимости** + +- Настраивайте `DATA_PATH`, `THREADS` или ограничения по пропускной способности только после того, как понятен стандартный сценарий. + +**Остановитесь, когда** + +- Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. + +**Расширяйте, когда** + +- Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. + +### Правильно поднять архивные hot- и cold-данные mainnet + +**Начните здесь** + +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), раздел архивного режима. + +**Следующая страница при необходимости** + +- Сначала получите последнюю высоту архивного снапшота, затем запускайте отдельные загрузки hot- и cold-данных с правильными путями. + +**Остановитесь, когда** + +- План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. + +**Расширяйте, когда** + +- Оператору нужны уже более широкие nearcore bootstrap-guides, а не только FastNear snapshots. + +### Поднять архивные hot-данные в testnet + +**Начните здесь** + +- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet), раздел архивного режима. + +**Следующая страница при необходимости** + +- Получите последнюю высоту архивного снапшота testnet перед шагом загрузки. + +**Остановитесь, когда** + +- Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. + +**Расширяйте, когда** + +- Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. + +## Готовое расследование + +### Выбрать и выполнить правильный сценарий восстановления mainnet + +Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно быстро понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. + +**Цель** + +- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную безопасную последовательность команд. + +| Путь или команда | Как используем | Зачем используем | +| --- | --- | --- | +| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | +| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | +| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | +| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | +| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | + +**Что должен включать полезный ответ** + +- какой сценарий восстановления выбран и почему +- какие ключевые env vars важны для выбранного пути +- куда на диске должны попасть данные +- должен ли оператор оставаться в FastNear snapshot docs или переходить к более широкой документации nearcore + +## Частые ошибки + +- Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. +- Выбирать архивное восстановление, когда достаточно обычного или optimized RPC. +- Забывать про разделение hot/cold-данных для архивного режима. +- Переходить к командам до выбора сети и цели узла. + +## Полезные связанные страницы + +- [Обзор снапшотов](https://docs.fastnear.com/ru/snapshots) +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) +- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [NEAR Data API](https://docs.fastnear.com/ru/neardata) + +--- + ## mainnet - HTML-маршрут: https://docs.fastnear.com/ru/snapshots/mainnet @@ -1978,6 +2662,10 @@ https://transfers.main.fastnear.com - [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) — лента по аккаунту с фильтрами по направлению, активу, сумме и времени +## Нужен сценарий? + +Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для простых пошаговых сценариев: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. + ## Устранение неполадок ### Нужны полные метаданные транзакции @@ -1990,6 +2678,107 @@ https://transfers.main.fastnear.com --- +## Примеры Transfers API + +- HTML-маршрут: https://docs.fastnear.com/ru/transfers/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/transfers/examples.md + +**Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) + +# Примеры Transfers API + +Используйте эту страницу, когда вопрос касается именно движения активов и нужен самый короткий путь по документации истории переводов. Эта поверхность специально узкая: начинайте с самого точного фильтра перевода, который отвечает на вопрос, держите фокус на отправках и получениях и расширяйтесь только тогда, когда вопрос перестаёт быть «только про переводы». + +## Когда начинать здесь + +- Пользователя интересуют входящие или исходящие переводы NEAR или FT. +- Нужна лента кошелька, представление для аудита или ответ для поддержки, сфокусированный на движении активов. +- Аккаунт уже известен, и пока не требуется полная история исполнения. +- Для задачи достаточно mainnet-истории переводов. + +## Минимальные входы + +- `account_id` +- выбора сети здесь нет: сегодня эта поверхность доступна только для mainnet +- опциональные фильтры по направлению, активу, сумме или времени +- нужен ли только короткий набор событий или длинный обзор истории +- может ли позже понадобиться более широкий контекст транзакций + +## Частые задачи + +### Найти исходящие переводы одного аккаунта в узком окне времени + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. + +**Следующая страница при необходимости** + +- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. + +**Остановитесь, когда** + +- Уже можно ответить, кто что отправил, когда и в каком активе. + +**Расширяйте, когда** + +- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Построить ленту переводов с пагинацией через `resume_token` + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий. + +**Следующая страница при необходимости** + +- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. + +**Остановитесь, когда** + +- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. + +**Расширяйте, когда** + +- Пользователь просит метаданные транзакции сверх самих переводов. +- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). + +### Перейти от истории переводов к полному расследованию транзакции + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. + +**Следующая страница при необходимости** + +- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. + +**Остановитесь, когда** + +- Уже определено правильное событие перевода и следующий подходящий раздел для расследования. + +**Расширяйте, когда** + +- Пользователю прямо нужны квитанции или каноническое подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. + +## Частые ошибки + +- Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. +- Считать историю переводов полной историей исполнения. +- Переиспользовать `resume_token` с другими фильтрами. +- Начинать здесь с вопросов про testnet, хотя эта поверхность сегодня работает только в mainnet. + +## Полезные связанные страницы + +- [Transfers API](https://docs.fastnear.com/ru/transfers) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) + +--- + ## Транзакции API - HTML-маршрут: https://docs.fastnear.com/ru/tx @@ -2037,6 +2826,10 @@ https://tx.test.fastnear.com - [Поиск квитанции](https://docs.fastnear.com/ru/tx/receipt) — для расследования цепочки исполнения. - [Диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) — когда нужен ограниченный по диапазону просмотр истории. +## Нужен сценарий? + +Используйте [Transactions API Examples](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиск транзакций, расследование квитанций, история аккаунта и анализ диапазонов блоков. + ## Устранение неполадок ### Я ожидал, что здесь можно отправлять транзакции @@ -2053,6 +2846,187 @@ https://tx.test.fastnear.com --- +## Примеры Transactions API + +- HTML-маршрут: https://docs.fastnear.com/ru/tx/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples.md + +**Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) + +# Примеры Transactions API + +Используйте эту страницу, когда вопрос звучит как «что произошло?» и нужен индексированный исторический слой до того, как вы перейдёте к каноническому подтверждению через RPC. Начинайте с того идентификатора, который уже есть на руках, объясняйте историю исполнения в читаемом порядке и расширяйте её только тогда, когда действительно понадобятся точные RPC-семантики. + +## Когда начинать здесь + +- У вас уже есть хеш транзакции, ID квитанции, ID аккаунта или ограниченный диапазон блоков. +- Пользователю нужен исторический контекст исполнения, разбор для поддержки или отладки либо читаемая временная шкала. +- Нужна индексированная история без ручной сборки из сырых RPC-вызовов. +- Первый ответ должен объяснить, что произошло, до углубления в протокольные детали. + +## Минимальные входные данные + +- сеть: mainnet или testnet +- основной идентификатор: хеш транзакции, ID квитанции, `account_id` или блок/диапазон блоков +- расследуете ли вы один объект или целое окно истории +- требуется ли точное каноническое подтверждение через RPC до завершения ответа + +## Частые задачи + +### Найти одну транзакцию + +**Начните здесь** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. + +**Следующая страница при необходимости** + +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. +- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. + +**Остановитесь, когда** + +- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. + +**Расширяйте, когда** + +- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. +- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. + +### Исследовать квитанцию + +**Начните здесь** + +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. +- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. + +**Остановитесь, когда** + +- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. + +**Расширяйте, когда** + +- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. + +### Посмотреть недавнюю активность аккаунта + +**Начните здесь** + +- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. + +**Остановитесь, когда** + +- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. + +**Расширяйте, когда** + +- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). +- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). + +### Восстановить ограниченное окно по блокам + +**Начните здесь** + +- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. +- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. + +**Остановитесь, когда** + +- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. + +**Расширяйте, когда** + +- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). + +## Готовые расследования + +### Доказать порядок callback-ов в staged/release-сценарии + +Используйте это расследование, когда сначала была стадия с асинхронной подготовкой работы, потом отдельный release, и нужно доказать не только успешность транзакций, но и конкретный порядок выполнения последующих обратных вызовов. + +**Цель** + +- Превратить два хеша транзакций в устойчивый артефакт для расследования, который включает граф квитанций, привязки к блокам и изменения состояния контракта. + +В staged/release-сценариях именно stage-транзакция обычно остаётся главной опорной транзакцией расследования, потому что отложенные callback-и живут на её исходном дереве транзакции, а не на дереве release-транзакции. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Захват трассы stage и release | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш stage-транзакции и хеш release-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому дереву транзакции | +| Проверка материализации stage | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод staging-контракта, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления ожидаемых отложенных шагов | Подтверждает, что callback-и действительно стали доступны до того, как release-транзакция попытается их разбудить | +| Обогащение транзакций | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id` и индексированный статус исполнения | Даёт каждой транзакции устойчивую привязку к блоку, чтобы дальнейший анализ не зависел от памяти или ручных заметок | +| Снимки состояния контракта recorder | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до release, а затем опрашиваем его после release до появления ожидаемых записей | Доказывает реальный порядок последующих эффектов в состоянии контракта, а не только в метаданных дерева квитанций | +| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | +| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | +| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | +| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | + +**Что должен включать полезный ответ** + +- почему именно stage-транзакция, а не release-транзакция, обычно является главной опорной транзакцией расследования +- какой порядок callback-ов вы наблюдали +- в каких блоках стали видны изменения состояния +- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования + +### Начать с receipt ID и восстановить историю исполнения + +Используйте это расследование, когда на руках есть только receipt ID из трассы, лога ошибки или дерева callback-ов и нужно вернуться к понятной человеку истории того, что произошло. + +**Цель** + +- Перейти от одной квитанции к исходной транзакции, а затем расширить расследование ровно настолько, чтобы объяснить окружающее исполнение и эффекты в состоянии. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и определяем её содержимое, статус и связанный контекст транзакции | ID квитанции часто появляется в трассах и логах раньше, чем у человека складывается цельная история транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | После перехода из поиска квитанции забираем исходную транзакцию по хешу | Превращает одну квитанцию в читаемую историю исполнения с контекстом по получателю, блоку и статусу | +| Каноническое подтверждение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем результат на уровне протокола, когда индексированного вида недостаточно или нужны точные RPC-семантики | Полезно, когда важно различить индексированную интерпретацию и точное поведение RPC | +| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем содержащий блок и при необходимости расширяемся на соседние каскадные блоки, если исполнение растянулось по нескольким высотам | Помещает квитанцию во временную шкалу по блокам, которую проще объяснить | +| Окно активности аккаунта | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Забираем недавнюю активность аккаунтов, которых коснулась квитанция | Помогает связать квитанцию с окружающей историей по аккаунтам | +| Повторное чтение состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод с закреплённым `block_id`, если квитанция изменила видимое состояние контракта | Позволяет доказать, что квитанция не только существовала в метаданных, но и изменила устойчивое состояние контракта | + +**Что должен включать полезный ответ** + +- какую исходную транзакцию вы восстановили из квитанции +- была ли квитанция главным событием или только одним шагом в большом каскаде +- какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить +- был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым + +## Частые ошибки + +- Пытаться отправлять транзакцию через history API вместо сырого RPC. +- Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. +- Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». +- Повторно использовать непрозрачные токены пагинации с другим эндпоинтом или другим набором фильтров. + +## Полезные связанные страницы + +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [NEAR Data API](https://docs.fastnear.com/ru/neardata) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) + +--- + ## RPC протокола NEAR: Просмотр ключа доступа - HTML-маршрут: https://docs.fastnear.com/ru/rpcs/account/view_access_key diff --git a/static/ru/llms.txt b/static/ru/llms.txt index 985f6d7..9d60aa5 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -16,8 +16,10 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. +- [Примеры KV FastData API](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии использования KV FastData для точных ключей, истории ключей, анализа по предшественнику и перехода к каноническому RPC. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. +- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. - [API переводов](https://docs.fastnear.com/ru/transfers.md): История переводов NEAR и FT-токенов по аккаунтам для продуктовых лент и инструментов расследования. - [Транзакции API](https://docs.fastnear.com/ru/tx.md): Индексированные запросы по транзакциям, квитанциям, истории аккаунтов и истории блоков для FastNear. @@ -30,12 +32,17 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп ## Другие гайды +- [Примеры FastNear API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для сводок по аккаунтам, поиска по ключам и перехода к более узким представлениям активов. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. +- [Примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии использования NEAR Data API для опроса блоков, маршрутов перенаправления и перехода к каноническому RPC. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии использования Transfers API для узкой истории переводов, пагинации и перехода к более широкому расследованию. +- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые сценарии использования Transactions API для поиска транзакций, расследования квитанций, истории аккаунтов и анализа диапазонов блоков. ## Снапшоты - [Снапшоты для валидаторов](https://docs.fastnear.com/ru/snapshots.md): Пути загрузки снапшотов FastNear для подъёма и восстановления узлов NEAR. +- [Примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples.md): Пошаговые операторские сценарии для выбора правильного пути снапшотов FastNear и выполнения типовых задач запуска и восстановления. - [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. diff --git a/static/ru/neardata.md b/static/ru/neardata.md index 8e11085..7eaff28 100644 --- a/static/ru/neardata.md +++ b/static/ru/neardata.md @@ -37,6 +37,10 @@ https://testnet.neardata.xyz - [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block) и [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers) — для запросов по финализированным блокам. - [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) и [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) — когда нужны вспомогательные маршруты с перенаправлением. +## Нужен сценарий? + +Используйте [NEAR Data API Examples](https://docs.fastnear.com/ru/neardata/examples) для простых пошаговых сценариев: optimistic polling, finalized confirmation, redirect helpers и переход к каноническому RPC. + ## Устранение неполадок ### Некоторые эндпоинты перенаправляют на канонический URL вместо прямого возврата результата diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md new file mode 100644 index 0000000..9d971b5 --- /dev/null +++ b/static/ru/neardata/examples.md @@ -0,0 +1,134 @@ +**Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) + +# Примеры NEAR Data API + +Используйте эту страницу, когда свежесть важнее протокольной точности. NEAR Data API предназначен для опроса и чтения недавних данных по семейству блоков: начинайте с самого свежего или самого стабильного режима блоков, который подходит задаче, оставайтесь на этой поверхности для опроса, пока она отвечает на вопрос, и переходите к RPC только тогда, когда действительно нужна каноническая семантика блока или состояния. + +## Когда начинать здесь + +- Нужны недавние оптимистичные или финализированные данные по семейству блоков. +- Нужен клиент опроса, монитор или проверка свежести. +- Маршруты перенаправления приемлемы или полезны для клиентского сценария. +- Задача звучит как «что изменилось недавно?», а не как каноническое историческое подтверждение. + +## Минимальные входы + +- сеть +- режим свежести: optimistic или finalized +- есть ли конкретная высота или хеш, либо нужен самый свежий объект семейства блоков +- может ли клиент корректно следовать перенаправлениям +- потребуется ли позже проверка через RPC + +## Частые задачи + +### Отслеживать последний оптимистичный блок + +**Начните здесь** + +- [Оптимистичный блок](https://docs.fastnear.com/ru/neardata/block-optimistic) для самого свежего чтения по семейству блоков. + +**Следующая страница при необходимости** + +- [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic), если нужен маршрут перенаправления, который всегда ведёт к самому новому оптимистичному блоку. + +**Остановитесь, когда** + +- Уже можно сообщить о последнем оптимистичном блоке или зафиксировать отставание по свежести. + +**Расширяйте, когда** + +- Нужна finalized-стабильность вместо максимальной свежести. Переходите к [Финализированному блоку по высоте](https://docs.fastnear.com/ru/neardata/block) или [Перенаправлению на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final). + +### Безопасно отслеживать ход финализации блоков + +**Начните здесь** + +- [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block), когда уже известна нужная высота. +- [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers), когда достаточно чтения заголовков. + +**Следующая страница при необходимости** + +- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final), когда клиент должен следовать за самым новым финализированным блоком без предварительного вычисления высоты. + +**Остановитесь, когда** + +- Уже можно показывать движение финализированных блоков без перехода к более глубоким протокольным деталям. + +**Расширяйте, когда** + +- Пользователю нужны точные канонические поля блока или семантика транзакций. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). + +### Использовать маршруты перенаправления в клиенте опроса + +**Начните здесь** + +- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) или [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) в зависимости от требуемой свежести. + +**Следующая страница при необходимости** + +- Следуйте по канонической цели, которую вернул маршрут перенаправления, и уже там читайте нужные данные по семейству блоков. + +**Остановитесь, когда** + +- Клиент надёжно проходит по маршруту перенаправления и получает нужный ресурс блока. + +**Расширяйте, когда** + +- Само перенаправление мешает клиенту. Тогда переходите на прямые маршруты блоков. + +### Перейти от свежего опроса к каноническому разбору через RPC + +**Начните здесь** + +- Используйте подходящий маршрут NEAR Data, чтобы найти недавний блок или событие в семействе блоков, которое нужно исследовать. + +**Следующая страница при необходимости** + +- [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или другой RPC-метод, как только станет понятно, какой именно блок или следующий объект для проверки нужен. + +**Остановитесь, когда** + +- Уже можно чётко назвать недавний блок, который заслуживает канонической проверки. + +**Расширяйте, когда** + +- Пользователь просит точную структуру данных в терминах протокола, а не просто свежее чтение. + +## Готовое расследование + +### Начать с оптимистичного блока, затем подтвердить финализированную и каноническую картину + +Используйте это расследование, когда нужно раннее обнаружение по оптимистичному блоку, но финальный ответ всё равно должен опираться на стабильный финализированный вид и, при необходимости, на каноническое подтверждение через RPC. + +**Цель** + +- Быстро заметить недавнее изменение, а затем сузить его до финализированной и канонической истории блока без лишних запросов. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](https://docs.fastnear.com/ru/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | +| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](https://docs.fastnear.com/ru/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | +| Стабильное подтверждение | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) или [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | +| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | +| Каноническая проверка | RPC [Блок по ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Блок по высоте](https://docs.fastnear.com/ru/rpc/block/block-by-height) | Получаем точный канонический блок, как только понятно, какой именно блок важен | Переводит расследование от свежих данных к протокольно точному подтверждению только когда это действительно нужно | + +**Что должен включать полезный ответ** + +- какое наблюдение по оптимистичному блоку впервые запустило расследование +- когда то же наблюдение стало финализированным +- изменил ли канонический разбор через RPC интерпретацию + +## Частые ошибки + +- Воспринимать NEAR Data API как потоковый продукт, а не как поверхность для опроса. +- Начинать с канонического RPC, когда настоящая задача — мониторинг свежих блоков. +- Забывать, что невалидный ключ может вернуть `401` ещё до перенаправления, а сами перенаправления подходят не каждому HTTP-клиенту. +- Оставаться на NEAR Data после того, как пользователь уже попросил точные протокольные детали блока. + +## Полезные связанные страницы + +- [NEAR Data API](https://docs.fastnear.com/ru/neardata) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md new file mode 100644 index 0000000..9d971b5 --- /dev/null +++ b/static/ru/neardata/examples/index.md @@ -0,0 +1,134 @@ +**Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) + +# Примеры NEAR Data API + +Используйте эту страницу, когда свежесть важнее протокольной точности. NEAR Data API предназначен для опроса и чтения недавних данных по семейству блоков: начинайте с самого свежего или самого стабильного режима блоков, который подходит задаче, оставайтесь на этой поверхности для опроса, пока она отвечает на вопрос, и переходите к RPC только тогда, когда действительно нужна каноническая семантика блока или состояния. + +## Когда начинать здесь + +- Нужны недавние оптимистичные или финализированные данные по семейству блоков. +- Нужен клиент опроса, монитор или проверка свежести. +- Маршруты перенаправления приемлемы или полезны для клиентского сценария. +- Задача звучит как «что изменилось недавно?», а не как каноническое историческое подтверждение. + +## Минимальные входы + +- сеть +- режим свежести: optimistic или finalized +- есть ли конкретная высота или хеш, либо нужен самый свежий объект семейства блоков +- может ли клиент корректно следовать перенаправлениям +- потребуется ли позже проверка через RPC + +## Частые задачи + +### Отслеживать последний оптимистичный блок + +**Начните здесь** + +- [Оптимистичный блок](https://docs.fastnear.com/ru/neardata/block-optimistic) для самого свежего чтения по семейству блоков. + +**Следующая страница при необходимости** + +- [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic), если нужен маршрут перенаправления, который всегда ведёт к самому новому оптимистичному блоку. + +**Остановитесь, когда** + +- Уже можно сообщить о последнем оптимистичном блоке или зафиксировать отставание по свежести. + +**Расширяйте, когда** + +- Нужна finalized-стабильность вместо максимальной свежести. Переходите к [Финализированному блоку по высоте](https://docs.fastnear.com/ru/neardata/block) или [Перенаправлению на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final). + +### Безопасно отслеживать ход финализации блоков + +**Начните здесь** + +- [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block), когда уже известна нужная высота. +- [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers), когда достаточно чтения заголовков. + +**Следующая страница при необходимости** + +- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final), когда клиент должен следовать за самым новым финализированным блоком без предварительного вычисления высоты. + +**Остановитесь, когда** + +- Уже можно показывать движение финализированных блоков без перехода к более глубоким протокольным деталям. + +**Расширяйте, когда** + +- Пользователю нужны точные канонические поля блока или семантика транзакций. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). + +### Использовать маршруты перенаправления в клиенте опроса + +**Начните здесь** + +- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) или [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) в зависимости от требуемой свежести. + +**Следующая страница при необходимости** + +- Следуйте по канонической цели, которую вернул маршрут перенаправления, и уже там читайте нужные данные по семейству блоков. + +**Остановитесь, когда** + +- Клиент надёжно проходит по маршруту перенаправления и получает нужный ресурс блока. + +**Расширяйте, когда** + +- Само перенаправление мешает клиенту. Тогда переходите на прямые маршруты блоков. + +### Перейти от свежего опроса к каноническому разбору через RPC + +**Начните здесь** + +- Используйте подходящий маршрут NEAR Data, чтобы найти недавний блок или событие в семействе блоков, которое нужно исследовать. + +**Следующая страница при необходимости** + +- [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или другой RPC-метод, как только станет понятно, какой именно блок или следующий объект для проверки нужен. + +**Остановитесь, когда** + +- Уже можно чётко назвать недавний блок, который заслуживает канонической проверки. + +**Расширяйте, когда** + +- Пользователь просит точную структуру данных в терминах протокола, а не просто свежее чтение. + +## Готовое расследование + +### Начать с оптимистичного блока, затем подтвердить финализированную и каноническую картину + +Используйте это расследование, когда нужно раннее обнаружение по оптимистичному блоку, но финальный ответ всё равно должен опираться на стабильный финализированный вид и, при необходимости, на каноническое подтверждение через RPC. + +**Цель** + +- Быстро заметить недавнее изменение, а затем сузить его до финализированной и канонической истории блока без лишних запросов. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](https://docs.fastnear.com/ru/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | +| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](https://docs.fastnear.com/ru/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | +| Стабильное подтверждение | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) или [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | +| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | +| Каноническая проверка | RPC [Блок по ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Блок по высоте](https://docs.fastnear.com/ru/rpc/block/block-by-height) | Получаем точный канонический блок, как только понятно, какой именно блок важен | Переводит расследование от свежих данных к протокольно точному подтверждению только когда это действительно нужно | + +**Что должен включать полезный ответ** + +- какое наблюдение по оптимистичному блоку впервые запустило расследование +- когда то же наблюдение стало финализированным +- изменил ли канонический разбор через RPC интерпретацию + +## Частые ошибки + +- Воспринимать NEAR Data API как потоковый продукт, а не как поверхность для опроса. +- Начинать с канонического RPC, когда настоящая задача — мониторинг свежих блоков. +- Забывать, что невалидный ключ может вернуть `401` ещё до перенаправления, а сами перенаправления подходят не каждому HTTP-клиенту. +- Оставаться на NEAR Data после того, как пользователь уже попросил точные протокольные детали блока. + +## Полезные связанные страницы + +- [NEAR Data API](https://docs.fastnear.com/ru/neardata) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/neardata/index.md b/static/ru/neardata/index.md index 8e11085..7eaff28 100644 --- a/static/ru/neardata/index.md +++ b/static/ru/neardata/index.md @@ -37,6 +37,10 @@ https://testnet.neardata.xyz - [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block) и [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers) — для запросов по финализированным блокам. - [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) и [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) — когда нужны вспомогательные маршруты с перенаправлением. +## Нужен сценарий? + +Используйте [NEAR Data API Examples](https://docs.fastnear.com/ru/neardata/examples) для простых пошаговых сценариев: optimistic polling, finalized confirmation, redirect helpers и переход к каноническому RPC. + ## Устранение неполадок ### Некоторые эндпоинты перенаправляют на канонический URL вместо прямого возврата результата diff --git a/static/ru/rpc.md b/static/ru/rpc.md index 6bd09fc..2ac06a2 100644 --- a/static/ru/rpc.md +++ b/static/ru/rpc.md @@ -35,6 +35,10 @@ https://archival-rpc.testnet.fastnear.com - [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) — отправка транзакций; [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) — статус исполнения. - [`validators`](https://docs.fastnear.com/ru/rpc/validators/validators-current) — валидаторы текущей эпохи. +## Нужен сценарий? + +Используйте [RPC Examples](https://docs.fastnear.com/ru/rpc/examples) для простых пошаговых сценариев: точные проверки состояния, анализ блоков, view-вызовы контрактов и отправка с подтверждением транзакций. + ## Используйте RPC, когда - нужны канонические формы запросов и ответов из протокола; diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md new file mode 100644 index 0000000..a6165a4 --- /dev/null +++ b/static/ru/rpc/examples.md @@ -0,0 +1,124 @@ +**Источник:** [https://docs.fastnear.com/ru/rpc/examples](https://docs.fastnear.com/ru/rpc/examples) + +# Примеры RPC + +Используйте эту страницу, когда уже понятно, что ответ должен опираться на каноническое поведение RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы выбрать правильную стартовую страницу, остановиться, как только RPC-ответ уже решает задачу, и расширять набор поверхностей только тогда, когда это действительно поможет. + +## Когда начинать здесь + +- Пользователь просит точное состояние в цепочке или поля в протокольной форме. +- Нужен прямой вызов view-метода контракта или сценарий отправки транзакции. +- Нужно исследовать блоки, чанки, валидаторов или метаданные протокола. +- Важна семантика узла, а не индексированное агрегированное представление. + +## Минимальные входные данные + +- сеть: mainnet или testnet +- основной идентификатор: `account_id`, публичный ключ, ID контракта плюс метод, хеш транзакции или высота/хеш блока +- нужно ли текущее состояние, историческое состояние или поведение отправки/финальности +- должен ли результат остаться каноническим или затем превратиться в более удобное для человека резюме + +## Частые задачи + +### Проверить точное состояние аккаунта или ключа доступа + +**Начните здесь** + +- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для канонических полей аккаунта. +- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. + +**Следующая страница при необходимости** + +- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после подтверждения канонического состояния нужна ещё и сводка в формате кошелька. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» + +**Остановитесь, когда** + +- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. + +**Расширяйте, когда** + +- Пользователю нужны балансы, NFT, стейкинг или другой продуктовый вид данных. +- Пользователя интересует не текущее каноническое состояние, а недавняя история активности. + +### Проверить блок или снимок состояния протокола + +**Начните здесь** + +- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height) для конкретного блока. +- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block) для текущей канонической головы цепочки. +- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info) для диагностики узла и сети. + +**Следующая страница при необходимости** + +- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если после поиска блока нужен контекст по изменениям состояния. +- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если нужна более читаемая картина исполнения в рамках блока или диапазона. + +**Остановитесь, когда** + +- Канонический ответ блока или протокола уже напрямую отвечает на вопрос. + +**Расширяйте, когда** + +- Нужны данные по свежим блокам в режиме опроса, а не один канонический снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +- Нужна история по нескольким транзакциям, а не только ответ одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Выполнить view-вызов контракта + +**Начните здесь** + +- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function) для view-метода контракта. +- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда вопрос касается сырого хранилища контракта. +- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда на самом деле нужно понять, есть ли код и каков его хеш. + +**Следующая страница при необходимости** + +- [FastNear API](https://docs.fastnear.com/ru/api), если после сырого вызова пользователю нужен продуктовый ответ, например по активам или сводке аккаунта. +- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующая задача связана с индексированной историей по ключам и значениям, а не с точным RPC-чтением. + +**Остановитесь, когда** + +- Результат view-вызова уже отвечает на вопрос в канонической форме. + +**Расширяйте, когда** + +- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. +- Вопрос смещается от «что метод возвращает сейчас?» к «что менялось со временем?» + +### Отправить транзакцию и подтвердить результат + +**Начните здесь** + +- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужно каноническое поведение отправки с явной семантикой ожидания. +- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить канонический результат. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. + +**Остановитесь, когда** + +- У вас уже есть результат отправки и нужный канонический финальный статус. + +**Расширяйте, когда** + +- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. +- Нужен уже не единичный статус, а более широкий сценарий расследования. + +## Частые ошибки + +- Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. +- Забывать переключаться с обычного RPC на архивный RPC для старого состояния. +- Воспринимать браузерную аутентификацию в интерфейсе документации как продовый паттерн для бэкенда. +- Продолжать пользоваться низкоуровневыми статусами транзакций, когда вопрос уже превратился в расследование или исторический разбор. + +## Полезные связанные страницы + +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [Auth & Access](https://docs.fastnear.com/ru/auth) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md new file mode 100644 index 0000000..a6165a4 --- /dev/null +++ b/static/ru/rpc/examples/index.md @@ -0,0 +1,124 @@ +**Источник:** [https://docs.fastnear.com/ru/rpc/examples](https://docs.fastnear.com/ru/rpc/examples) + +# Примеры RPC + +Используйте эту страницу, когда уже понятно, что ответ должен опираться на каноническое поведение RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы выбрать правильную стартовую страницу, остановиться, как только RPC-ответ уже решает задачу, и расширять набор поверхностей только тогда, когда это действительно поможет. + +## Когда начинать здесь + +- Пользователь просит точное состояние в цепочке или поля в протокольной форме. +- Нужен прямой вызов view-метода контракта или сценарий отправки транзакции. +- Нужно исследовать блоки, чанки, валидаторов или метаданные протокола. +- Важна семантика узла, а не индексированное агрегированное представление. + +## Минимальные входные данные + +- сеть: mainnet или testnet +- основной идентификатор: `account_id`, публичный ключ, ID контракта плюс метод, хеш транзакции или высота/хеш блока +- нужно ли текущее состояние, историческое состояние или поведение отправки/финальности +- должен ли результат остаться каноническим или затем превратиться в более удобное для человека резюме + +## Частые задачи + +### Проверить точное состояние аккаунта или ключа доступа + +**Начните здесь** + +- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для канонических полей аккаунта. +- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. + +**Следующая страница при необходимости** + +- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после подтверждения канонического состояния нужна ещё и сводка в формате кошелька. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» + +**Остановитесь, когда** + +- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. + +**Расширяйте, когда** + +- Пользователю нужны балансы, NFT, стейкинг или другой продуктовый вид данных. +- Пользователя интересует не текущее каноническое состояние, а недавняя история активности. + +### Проверить блок или снимок состояния протокола + +**Начните здесь** + +- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height) для конкретного блока. +- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block) для текущей канонической головы цепочки. +- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info) для диагностики узла и сети. + +**Следующая страница при необходимости** + +- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если после поиска блока нужен контекст по изменениям состояния. +- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если нужна более читаемая картина исполнения в рамках блока или диапазона. + +**Остановитесь, когда** + +- Канонический ответ блока или протокола уже напрямую отвечает на вопрос. + +**Расширяйте, когда** + +- Нужны данные по свежим блокам в режиме опроса, а не один канонический снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +- Нужна история по нескольким транзакциям, а не только ответ одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Выполнить view-вызов контракта + +**Начните здесь** + +- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function) для view-метода контракта. +- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда вопрос касается сырого хранилища контракта. +- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда на самом деле нужно понять, есть ли код и каков его хеш. + +**Следующая страница при необходимости** + +- [FastNear API](https://docs.fastnear.com/ru/api), если после сырого вызова пользователю нужен продуктовый ответ, например по активам или сводке аккаунта. +- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующая задача связана с индексированной историей по ключам и значениям, а не с точным RPC-чтением. + +**Остановитесь, когда** + +- Результат view-вызова уже отвечает на вопрос в канонической форме. + +**Расширяйте, когда** + +- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. +- Вопрос смещается от «что метод возвращает сейчас?» к «что менялось со временем?» + +### Отправить транзакцию и подтвердить результат + +**Начните здесь** + +- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужно каноническое поведение отправки с явной семантикой ожидания. +- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить канонический результат. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. + +**Остановитесь, когда** + +- У вас уже есть результат отправки и нужный канонический финальный статус. + +**Расширяйте, когда** + +- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. +- Нужен уже не единичный статус, а более широкий сценарий расследования. + +## Частые ошибки + +- Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. +- Забывать переключаться с обычного RPC на архивный RPC для старого состояния. +- Воспринимать браузерную аутентификацию в интерфейсе документации как продовый паттерн для бэкенда. +- Продолжать пользоваться низкоуровневыми статусами транзакций, когда вопрос уже превратился в расследование или исторический разбор. + +## Полезные связанные страницы + +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [Auth & Access](https://docs.fastnear.com/ru/auth) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/rpc/index.md b/static/ru/rpc/index.md index 6bd09fc..2ac06a2 100644 --- a/static/ru/rpc/index.md +++ b/static/ru/rpc/index.md @@ -35,6 +35,10 @@ https://archival-rpc.testnet.fastnear.com - [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) — отправка транзакций; [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) — статус исполнения. - [`validators`](https://docs.fastnear.com/ru/rpc/validators/validators-current) — валидаторы текущей эпохи. +## Нужен сценарий? + +Используйте [RPC Examples](https://docs.fastnear.com/ru/rpc/examples) для простых пошаговых сценариев: точные проверки состояния, анализ блоков, view-вызовы контрактов и отправка с подтверждением транзакций. + ## Используйте RPC, когда - нужны канонические формы запросов и ответов из протокола; diff --git a/static/ru/snapshots.md b/static/ru/snapshots.md index 39c4d48..9fdbaba 100644 --- a/static/ru/snapshots.md +++ b/static/ru/snapshots.md @@ -47,6 +47,10 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash Требования к узлам смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near), а исходники скриптов загрузки, которые используются в этих руководствах, — в [fastnear/static](https://github.com/fastnear/static). +## Нужен сценарий? + +Используйте [Snapshot Examples](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбор между optimized `fast-rpc`, standard RPC recovery и archival hot/cold snapshot paths. + ## Выберите сеть - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) diff --git a/static/ru/snapshots/examples.md b/static/ru/snapshots/examples.md new file mode 100644 index 0000000..85b8409 --- /dev/null +++ b/static/ru/snapshots/examples.md @@ -0,0 +1,134 @@ +**Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) + +# Примеры снапшотов + +Используйте эту страницу, когда вопрос звучит как «какой сценарий со снапшотом вообще запускать?», а не «какая команда существует?». Снапшоты — это операторские сценарии, а не API для данных: сначала выбирайте правильную цель узла, сеть и схему хранения, а затем запускайте самый узкий путь загрузки, который безопасно поднимет узел. + +## Когда начинать здесь + +- Нужно поднять или восстановить узел. +- Нужно выбрать между обычным RPC, optimized `fast-rpc` и архивным сценарием с hot/cold-данными. +- Нужен самый короткий путь от задачи оператора к правильной последовательности команд. +- Уже понятно, что это инфраструктурная работа, а не чтение прикладных данных. + +## Минимальные входы + +- сеть: mainnet или testnet +- цель узла: обычный RPC, optimized `fast-rpc` или архивный режим +- схема хранения, особенно возможность разделить hot- и cold-архивные данные +- используется ли стандартный каталог данных nearcore или отдельные каталоги для hot/cold +- целевой путь к данным и примерные ограничения по загрузке + +## Частые задачи + +### Поднять optimized `fast-rpc`-узел в mainnet + +**Начните здесь** + +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), конкретно путь для optimized `fast-rpc`. + +**Следующая страница при необходимости** + +- Вернитесь к обычному сценарию mainnet RPC, если узел не подходит для optimized profile. + +**Остановитесь, когда** + +- Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. + +**Расширяйте, когда** + +- На самом деле требуется архивное хранение, а не просто быстрый запуск. + +### Восстановить обычный RPC-узел в стандартный каталог nearcore + +**Начните здесь** + +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) или [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) в зависимости от сети и выберите обычный RPC-сценарий для нужного окружения. + +**Следующая страница при необходимости** + +- Настраивайте `DATA_PATH`, `THREADS` или ограничения по пропускной способности только после того, как понятен стандартный сценарий. + +**Остановитесь, когда** + +- Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. + +**Расширяйте, когда** + +- Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. + +### Правильно поднять архивные hot- и cold-данные mainnet + +**Начните здесь** + +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), раздел архивного режима. + +**Следующая страница при необходимости** + +- Сначала получите последнюю высоту архивного снапшота, затем запускайте отдельные загрузки hot- и cold-данных с правильными путями. + +**Остановитесь, когда** + +- План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. + +**Расширяйте, когда** + +- Оператору нужны уже более широкие nearcore bootstrap-guides, а не только FastNear snapshots. + +### Поднять архивные hot-данные в testnet + +**Начните здесь** + +- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet), раздел архивного режима. + +**Следующая страница при необходимости** + +- Получите последнюю высоту архивного снапшота testnet перед шагом загрузки. + +**Остановитесь, когда** + +- Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. + +**Расширяйте, когда** + +- Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. + +## Готовое расследование + +### Выбрать и выполнить правильный сценарий восстановления mainnet + +Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно быстро понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. + +**Цель** + +- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную безопасную последовательность команд. + +| Путь или команда | Как используем | Зачем используем | +| --- | --- | --- | +| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | +| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | +| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | +| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | +| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | + +**Что должен включать полезный ответ** + +- какой сценарий восстановления выбран и почему +- какие ключевые env vars важны для выбранного пути +- куда на диске должны попасть данные +- должен ли оператор оставаться в FastNear snapshot docs или переходить к более широкой документации nearcore + +## Частые ошибки + +- Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. +- Выбирать архивное восстановление, когда достаточно обычного или optimized RPC. +- Забывать про разделение hot/cold-данных для архивного режима. +- Переходить к командам до выбора сети и цели узла. + +## Полезные связанные страницы + +- [Обзор снапшотов](https://docs.fastnear.com/ru/snapshots) +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) +- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [NEAR Data API](https://docs.fastnear.com/ru/neardata) diff --git a/static/ru/snapshots/examples/index.md b/static/ru/snapshots/examples/index.md new file mode 100644 index 0000000..85b8409 --- /dev/null +++ b/static/ru/snapshots/examples/index.md @@ -0,0 +1,134 @@ +**Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) + +# Примеры снапшотов + +Используйте эту страницу, когда вопрос звучит как «какой сценарий со снапшотом вообще запускать?», а не «какая команда существует?». Снапшоты — это операторские сценарии, а не API для данных: сначала выбирайте правильную цель узла, сеть и схему хранения, а затем запускайте самый узкий путь загрузки, который безопасно поднимет узел. + +## Когда начинать здесь + +- Нужно поднять или восстановить узел. +- Нужно выбрать между обычным RPC, optimized `fast-rpc` и архивным сценарием с hot/cold-данными. +- Нужен самый короткий путь от задачи оператора к правильной последовательности команд. +- Уже понятно, что это инфраструктурная работа, а не чтение прикладных данных. + +## Минимальные входы + +- сеть: mainnet или testnet +- цель узла: обычный RPC, optimized `fast-rpc` или архивный режим +- схема хранения, особенно возможность разделить hot- и cold-архивные данные +- используется ли стандартный каталог данных nearcore или отдельные каталоги для hot/cold +- целевой путь к данным и примерные ограничения по загрузке + +## Частые задачи + +### Поднять optimized `fast-rpc`-узел в mainnet + +**Начните здесь** + +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), конкретно путь для optimized `fast-rpc`. + +**Следующая страница при необходимости** + +- Вернитесь к обычному сценарию mainnet RPC, если узел не подходит для optimized profile. + +**Остановитесь, когда** + +- Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. + +**Расширяйте, когда** + +- На самом деле требуется архивное хранение, а не просто быстрый запуск. + +### Восстановить обычный RPC-узел в стандартный каталог nearcore + +**Начните здесь** + +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) или [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) в зависимости от сети и выберите обычный RPC-сценарий для нужного окружения. + +**Следующая страница при необходимости** + +- Настраивайте `DATA_PATH`, `THREADS` или ограничения по пропускной способности только после того, как понятен стандартный сценарий. + +**Остановитесь, когда** + +- Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. + +**Расширяйте, когда** + +- Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. + +### Правильно поднять архивные hot- и cold-данные mainnet + +**Начните здесь** + +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), раздел архивного режима. + +**Следующая страница при необходимости** + +- Сначала получите последнюю высоту архивного снапшота, затем запускайте отдельные загрузки hot- и cold-данных с правильными путями. + +**Остановитесь, когда** + +- План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. + +**Расширяйте, когда** + +- Оператору нужны уже более широкие nearcore bootstrap-guides, а не только FastNear snapshots. + +### Поднять архивные hot-данные в testnet + +**Начните здесь** + +- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet), раздел архивного режима. + +**Следующая страница при необходимости** + +- Получите последнюю высоту архивного снапшота testnet перед шагом загрузки. + +**Остановитесь, когда** + +- Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. + +**Расширяйте, когда** + +- Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. + +## Готовое расследование + +### Выбрать и выполнить правильный сценарий восстановления mainnet + +Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно быстро понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. + +**Цель** + +- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную безопасную последовательность команд. + +| Путь или команда | Как используем | Зачем используем | +| --- | --- | --- | +| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | +| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | +| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | +| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | +| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | + +**Что должен включать полезный ответ** + +- какой сценарий восстановления выбран и почему +- какие ключевые env vars важны для выбранного пути +- куда на диске должны попасть данные +- должен ли оператор оставаться в FastNear snapshot docs или переходить к более широкой документации nearcore + +## Частые ошибки + +- Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. +- Выбирать архивное восстановление, когда достаточно обычного или optimized RPC. +- Забывать про разделение hot/cold-данных для архивного режима. +- Переходить к командам до выбора сети и цели узла. + +## Полезные связанные страницы + +- [Обзор снапшотов](https://docs.fastnear.com/ru/snapshots) +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) +- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [NEAR Data API](https://docs.fastnear.com/ru/neardata) diff --git a/static/ru/snapshots/index.md b/static/ru/snapshots/index.md index 39c4d48..9fdbaba 100644 --- a/static/ru/snapshots/index.md +++ b/static/ru/snapshots/index.md @@ -47,6 +47,10 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash Требования к узлам смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near), а исходники скриптов загрузки, которые используются в этих руководствах, — в [fastnear/static](https://github.com/fastnear/static). +## Нужен сценарий? + +Используйте [Snapshot Examples](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбор между optimized `fast-rpc`, standard RPC recovery и archival hot/cold snapshot paths. + ## Выберите сеть - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) diff --git a/static/ru/structured-data/site-graph.json b/static/ru/structured-data/site-graph.json index b6b609e..ff8dc10 100644 --- a/static/ru/structured-data/site-graph.json +++ b/static/ru/structured-data/site-graph.json @@ -3882,6 +3882,19 @@ "routeType": "docs", "url": "https://docs.fastnear.com/ru/api" }, + { + "entityIds": { + "familyIds": [], + "mainEntityId": null, + "pageId": "https://docs.fastnear.com/ru/api/examples#page" + }, + "indexable": true, + "markdownMirrorUrl": "https://docs.fastnear.com/ru/api/examples.md", + "pageSchemaType": "TechArticle", + "route": "/ru/api/examples", + "routeType": "docs", + "url": "https://docs.fastnear.com/ru/api/examples" + }, { "entityIds": { "familyIds": [], @@ -4718,6 +4731,19 @@ "routeType": "docs", "url": "https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor" }, + { + "entityIds": { + "familyIds": [], + "mainEntityId": null, + "pageId": "https://docs.fastnear.com/ru/fastdata/kv/examples#page" + }, + "indexable": true, + "markdownMirrorUrl": "https://docs.fastnear.com/ru/fastdata/kv/examples.md", + "pageSchemaType": "TechArticle", + "route": "/ru/fastdata/kv/examples", + "routeType": "docs", + "url": "https://docs.fastnear.com/ru/fastdata/kv/examples" + }, { "entityIds": { "familyIds": [ @@ -4941,6 +4967,19 @@ "routeType": "docs", "url": "https://docs.fastnear.com/ru/neardata/block-shard" }, + { + "entityIds": { + "familyIds": [], + "mainEntityId": null, + "pageId": "https://docs.fastnear.com/ru/neardata/examples#page" + }, + "indexable": true, + "markdownMirrorUrl": "https://docs.fastnear.com/ru/neardata/examples.md", + "pageSchemaType": "TechArticle", + "route": "/ru/neardata/examples", + "routeType": "docs", + "url": "https://docs.fastnear.com/ru/neardata/examples" + }, { "entityIds": { "familyIds": [ @@ -5199,6 +5238,19 @@ "routeType": "docs", "url": "https://docs.fastnear.com/ru/rpc/contract/view-state" }, + { + "entityIds": { + "familyIds": [], + "mainEntityId": null, + "pageId": "https://docs.fastnear.com/ru/rpc/examples#page" + }, + "indexable": true, + "markdownMirrorUrl": "https://docs.fastnear.com/ru/rpc/examples.md", + "pageSchemaType": "TechArticle", + "route": "/ru/rpc/examples", + "routeType": "docs", + "url": "https://docs.fastnear.com/ru/rpc/examples" + }, { "entityIds": { "familyIds": [ @@ -6247,6 +6299,19 @@ "routeType": "docs", "url": "https://docs.fastnear.com/ru/snapshots" }, + { + "entityIds": { + "familyIds": [], + "mainEntityId": null, + "pageId": "https://docs.fastnear.com/ru/snapshots/examples#page" + }, + "indexable": true, + "markdownMirrorUrl": "https://docs.fastnear.com/ru/snapshots/examples.md", + "pageSchemaType": "TechArticle", + "route": "/ru/snapshots/examples", + "routeType": "docs", + "url": "https://docs.fastnear.com/ru/snapshots/examples" + }, { "entityIds": { "familyIds": [], @@ -6288,6 +6353,19 @@ "routeType": "docs", "url": "https://docs.fastnear.com/ru/transfers" }, + { + "entityIds": { + "familyIds": [], + "mainEntityId": null, + "pageId": "https://docs.fastnear.com/ru/transfers/examples#page" + }, + "indexable": true, + "markdownMirrorUrl": "https://docs.fastnear.com/ru/transfers/examples.md", + "pageSchemaType": "TechArticle", + "route": "/ru/transfers/examples", + "routeType": "docs", + "url": "https://docs.fastnear.com/ru/transfers/examples" + }, { "entityIds": { "familyIds": [ @@ -6363,6 +6441,19 @@ "routeType": "docs", "url": "https://docs.fastnear.com/ru/tx/blocks" }, + { + "entityIds": { + "familyIds": [], + "mainEntityId": null, + "pageId": "https://docs.fastnear.com/ru/tx/examples#page" + }, + "indexable": true, + "markdownMirrorUrl": "https://docs.fastnear.com/ru/tx/examples.md", + "pageSchemaType": "TechArticle", + "route": "/ru/tx/examples", + "routeType": "docs", + "url": "https://docs.fastnear.com/ru/tx/examples" + }, { "entityIds": { "familyIds": [ diff --git a/static/ru/transfers.md b/static/ru/transfers.md index b69a6be..6c36fff 100644 --- a/static/ru/transfers.md +++ b/static/ru/transfers.md @@ -58,6 +58,10 @@ https://transfers.main.fastnear.com - [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) — лента по аккаунту с фильтрами по направлению, активу, сумме и времени +## Нужен сценарий? + +Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для простых пошаговых сценариев: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. + ## Устранение неполадок ### Нужны полные метаданные транзакции diff --git a/static/ru/transfers/examples.md b/static/ru/transfers/examples.md new file mode 100644 index 0000000..078903e --- /dev/null +++ b/static/ru/transfers/examples.md @@ -0,0 +1,93 @@ +**Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) + +# Примеры Transfers API + +Используйте эту страницу, когда вопрос касается именно движения активов и нужен самый короткий путь по документации истории переводов. Эта поверхность специально узкая: начинайте с самого точного фильтра перевода, который отвечает на вопрос, держите фокус на отправках и получениях и расширяйтесь только тогда, когда вопрос перестаёт быть «только про переводы». + +## Когда начинать здесь + +- Пользователя интересуют входящие или исходящие переводы NEAR или FT. +- Нужна лента кошелька, представление для аудита или ответ для поддержки, сфокусированный на движении активов. +- Аккаунт уже известен, и пока не требуется полная история исполнения. +- Для задачи достаточно mainnet-истории переводов. + +## Минимальные входы + +- `account_id` +- выбора сети здесь нет: сегодня эта поверхность доступна только для mainnet +- опциональные фильтры по направлению, активу, сумме или времени +- нужен ли только короткий набор событий или длинный обзор истории +- может ли позже понадобиться более широкий контекст транзакций + +## Частые задачи + +### Найти исходящие переводы одного аккаунта в узком окне времени + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. + +**Следующая страница при необходимости** + +- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. + +**Остановитесь, когда** + +- Уже можно ответить, кто что отправил, когда и в каком активе. + +**Расширяйте, когда** + +- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Построить ленту переводов с пагинацией через `resume_token` + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий. + +**Следующая страница при необходимости** + +- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. + +**Остановитесь, когда** + +- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. + +**Расширяйте, когда** + +- Пользователь просит метаданные транзакции сверх самих переводов. +- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). + +### Перейти от истории переводов к полному расследованию транзакции + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. + +**Следующая страница при необходимости** + +- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. + +**Остановитесь, когда** + +- Уже определено правильное событие перевода и следующий подходящий раздел для расследования. + +**Расширяйте, когда** + +- Пользователю прямо нужны квитанции или каноническое подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. + +## Частые ошибки + +- Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. +- Считать историю переводов полной историей исполнения. +- Переиспользовать `resume_token` с другими фильтрами. +- Начинать здесь с вопросов про testnet, хотя эта поверхность сегодня работает только в mainnet. + +## Полезные связанные страницы + +- [Transfers API](https://docs.fastnear.com/ru/transfers) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/transfers/examples/index.md b/static/ru/transfers/examples/index.md new file mode 100644 index 0000000..078903e --- /dev/null +++ b/static/ru/transfers/examples/index.md @@ -0,0 +1,93 @@ +**Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) + +# Примеры Transfers API + +Используйте эту страницу, когда вопрос касается именно движения активов и нужен самый короткий путь по документации истории переводов. Эта поверхность специально узкая: начинайте с самого точного фильтра перевода, который отвечает на вопрос, держите фокус на отправках и получениях и расширяйтесь только тогда, когда вопрос перестаёт быть «только про переводы». + +## Когда начинать здесь + +- Пользователя интересуют входящие или исходящие переводы NEAR или FT. +- Нужна лента кошелька, представление для аудита или ответ для поддержки, сфокусированный на движении активов. +- Аккаунт уже известен, и пока не требуется полная история исполнения. +- Для задачи достаточно mainnet-истории переводов. + +## Минимальные входы + +- `account_id` +- выбора сети здесь нет: сегодня эта поверхность доступна только для mainnet +- опциональные фильтры по направлению, активу, сумме или времени +- нужен ли только короткий набор событий или длинный обзор истории +- может ли позже понадобиться более широкий контекст транзакций + +## Частые задачи + +### Найти исходящие переводы одного аккаунта в узком окне времени + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. + +**Следующая страница при необходимости** + +- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. + +**Остановитесь, когда** + +- Уже можно ответить, кто что отправил, когда и в каком активе. + +**Расширяйте, когда** + +- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Построить ленту переводов с пагинацией через `resume_token` + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий. + +**Следующая страница при необходимости** + +- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. + +**Остановитесь, когда** + +- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. + +**Расширяйте, когда** + +- Пользователь просит метаданные транзакции сверх самих переводов. +- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). + +### Перейти от истории переводов к полному расследованию транзакции + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. + +**Следующая страница при необходимости** + +- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. + +**Остановитесь, когда** + +- Уже определено правильное событие перевода и следующий подходящий раздел для расследования. + +**Расширяйте, когда** + +- Пользователю прямо нужны квитанции или каноническое подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. + +## Частые ошибки + +- Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. +- Считать историю переводов полной историей исполнения. +- Переиспользовать `resume_token` с другими фильтрами. +- Начинать здесь с вопросов про testnet, хотя эта поверхность сегодня работает только в mainnet. + +## Полезные связанные страницы + +- [Transfers API](https://docs.fastnear.com/ru/transfers) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/transfers/index.md b/static/ru/transfers/index.md index b69a6be..6c36fff 100644 --- a/static/ru/transfers/index.md +++ b/static/ru/transfers/index.md @@ -58,6 +58,10 @@ https://transfers.main.fastnear.com - [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) — лента по аккаунту с фильтрами по направлению, активу, сумме и времени +## Нужен сценарий? + +Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для простых пошаговых сценариев: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. + ## Устранение неполадок ### Нужны полные метаданные транзакции diff --git a/static/ru/tx.md b/static/ru/tx.md index bf3aadc..0562e02 100644 --- a/static/ru/tx.md +++ b/static/ru/tx.md @@ -40,6 +40,10 @@ https://tx.test.fastnear.com - [Поиск квитанции](https://docs.fastnear.com/ru/tx/receipt) — для расследования цепочки исполнения. - [Диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) — когда нужен ограниченный по диапазону просмотр истории. +## Нужен сценарий? + +Используйте [Transactions API Examples](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиск транзакций, расследование квитанций, история аккаунта и анализ диапазонов блоков. + ## Устранение неполадок ### Я ожидал, что здесь можно отправлять транзакции diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md new file mode 100644 index 0000000..726aedd --- /dev/null +++ b/static/ru/tx/examples.md @@ -0,0 +1,173 @@ +**Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) + +# Примеры Transactions API + +Используйте эту страницу, когда вопрос звучит как «что произошло?» и нужен индексированный исторический слой до того, как вы перейдёте к каноническому подтверждению через RPC. Начинайте с того идентификатора, который уже есть на руках, объясняйте историю исполнения в читаемом порядке и расширяйте её только тогда, когда действительно понадобятся точные RPC-семантики. + +## Когда начинать здесь + +- У вас уже есть хеш транзакции, ID квитанции, ID аккаунта или ограниченный диапазон блоков. +- Пользователю нужен исторический контекст исполнения, разбор для поддержки или отладки либо читаемая временная шкала. +- Нужна индексированная история без ручной сборки из сырых RPC-вызовов. +- Первый ответ должен объяснить, что произошло, до углубления в протокольные детали. + +## Минимальные входные данные + +- сеть: mainnet или testnet +- основной идентификатор: хеш транзакции, ID квитанции, `account_id` или блок/диапазон блоков +- расследуете ли вы один объект или целое окно истории +- требуется ли точное каноническое подтверждение через RPC до завершения ответа + +## Частые задачи + +### Найти одну транзакцию + +**Начните здесь** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. + +**Следующая страница при необходимости** + +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. +- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. + +**Остановитесь, когда** + +- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. + +**Расширяйте, когда** + +- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. +- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. + +### Исследовать квитанцию + +**Начните здесь** + +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. +- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. + +**Остановитесь, когда** + +- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. + +**Расширяйте, когда** + +- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. + +### Посмотреть недавнюю активность аккаунта + +**Начните здесь** + +- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. + +**Остановитесь, когда** + +- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. + +**Расширяйте, когда** + +- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). +- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). + +### Восстановить ограниченное окно по блокам + +**Начните здесь** + +- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. +- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. + +**Остановитесь, когда** + +- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. + +**Расширяйте, когда** + +- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). + +## Готовые расследования + +### Доказать порядок callback-ов в staged/release-сценарии + +Используйте это расследование, когда сначала была стадия с асинхронной подготовкой работы, потом отдельный release, и нужно доказать не только успешность транзакций, но и конкретный порядок выполнения последующих обратных вызовов. + +**Цель** + +- Превратить два хеша транзакций в устойчивый артефакт для расследования, который включает граф квитанций, привязки к блокам и изменения состояния контракта. + +В staged/release-сценариях именно stage-транзакция обычно остаётся главной опорной транзакцией расследования, потому что отложенные callback-и живут на её исходном дереве транзакции, а не на дереве release-транзакции. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Захват трассы stage и release | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш stage-транзакции и хеш release-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому дереву транзакции | +| Проверка материализации stage | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод staging-контракта, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления ожидаемых отложенных шагов | Подтверждает, что callback-и действительно стали доступны до того, как release-транзакция попытается их разбудить | +| Обогащение транзакций | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id` и индексированный статус исполнения | Даёт каждой транзакции устойчивую привязку к блоку, чтобы дальнейший анализ не зависел от памяти или ручных заметок | +| Снимки состояния контракта recorder | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до release, а затем опрашиваем его после release до появления ожидаемых записей | Доказывает реальный порядок последующих эффектов в состоянии контракта, а не только в метаданных дерева квитанций | +| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | +| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | +| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | +| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | + +**Что должен включать полезный ответ** + +- почему именно stage-транзакция, а не release-транзакция, обычно является главной опорной транзакцией расследования +- какой порядок callback-ов вы наблюдали +- в каких блоках стали видны изменения состояния +- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования + +### Начать с receipt ID и восстановить историю исполнения + +Используйте это расследование, когда на руках есть только receipt ID из трассы, лога ошибки или дерева callback-ов и нужно вернуться к понятной человеку истории того, что произошло. + +**Цель** + +- Перейти от одной квитанции к исходной транзакции, а затем расширить расследование ровно настолько, чтобы объяснить окружающее исполнение и эффекты в состоянии. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и определяем её содержимое, статус и связанный контекст транзакции | ID квитанции часто появляется в трассах и логах раньше, чем у человека складывается цельная история транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | После перехода из поиска квитанции забираем исходную транзакцию по хешу | Превращает одну квитанцию в читаемую историю исполнения с контекстом по получателю, блоку и статусу | +| Каноническое подтверждение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем результат на уровне протокола, когда индексированного вида недостаточно или нужны точные RPC-семантики | Полезно, когда важно различить индексированную интерпретацию и точное поведение RPC | +| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем содержащий блок и при необходимости расширяемся на соседние каскадные блоки, если исполнение растянулось по нескольким высотам | Помещает квитанцию во временную шкалу по блокам, которую проще объяснить | +| Окно активности аккаунта | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Забираем недавнюю активность аккаунтов, которых коснулась квитанция | Помогает связать квитанцию с окружающей историей по аккаунтам | +| Повторное чтение состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод с закреплённым `block_id`, если квитанция изменила видимое состояние контракта | Позволяет доказать, что квитанция не только существовала в метаданных, но и изменила устойчивое состояние контракта | + +**Что должен включать полезный ответ** + +- какую исходную транзакцию вы восстановили из квитанции +- была ли квитанция главным событием или только одним шагом в большом каскаде +- какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить +- был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым + +## Частые ошибки + +- Пытаться отправлять транзакцию через history API вместо сырого RPC. +- Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. +- Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». +- Повторно использовать непрозрачные токены пагинации с другим эндпоинтом или другим набором фильтров. + +## Полезные связанные страницы + +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [NEAR Data API](https://docs.fastnear.com/ru/neardata) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md new file mode 100644 index 0000000..726aedd --- /dev/null +++ b/static/ru/tx/examples/index.md @@ -0,0 +1,173 @@ +**Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) + +# Примеры Transactions API + +Используйте эту страницу, когда вопрос звучит как «что произошло?» и нужен индексированный исторический слой до того, как вы перейдёте к каноническому подтверждению через RPC. Начинайте с того идентификатора, который уже есть на руках, объясняйте историю исполнения в читаемом порядке и расширяйте её только тогда, когда действительно понадобятся точные RPC-семантики. + +## Когда начинать здесь + +- У вас уже есть хеш транзакции, ID квитанции, ID аккаунта или ограниченный диапазон блоков. +- Пользователю нужен исторический контекст исполнения, разбор для поддержки или отладки либо читаемая временная шкала. +- Нужна индексированная история без ручной сборки из сырых RPC-вызовов. +- Первый ответ должен объяснить, что произошло, до углубления в протокольные детали. + +## Минимальные входные данные + +- сеть: mainnet или testnet +- основной идентификатор: хеш транзакции, ID квитанции, `account_id` или блок/диапазон блоков +- расследуете ли вы один объект или целое окно истории +- требуется ли точное каноническое подтверждение через RPC до завершения ответа + +## Частые задачи + +### Найти одну транзакцию + +**Начните здесь** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. + +**Следующая страница при необходимости** + +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. +- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. + +**Остановитесь, когда** + +- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. + +**Расширяйте, когда** + +- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. +- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. + +### Исследовать квитанцию + +**Начните здесь** + +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. +- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. + +**Остановитесь, когда** + +- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. + +**Расширяйте, когда** + +- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. + +### Посмотреть недавнюю активность аккаунта + +**Начните здесь** + +- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. + +**Остановитесь, когда** + +- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. + +**Расширяйте, когда** + +- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). +- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). + +### Восстановить ограниченное окно по блокам + +**Начните здесь** + +- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. +- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. + +**Остановитесь, когда** + +- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. + +**Расширяйте, когда** + +- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). + +## Готовые расследования + +### Доказать порядок callback-ов в staged/release-сценарии + +Используйте это расследование, когда сначала была стадия с асинхронной подготовкой работы, потом отдельный release, и нужно доказать не только успешность транзакций, но и конкретный порядок выполнения последующих обратных вызовов. + +**Цель** + +- Превратить два хеша транзакций в устойчивый артефакт для расследования, который включает граф квитанций, привязки к блокам и изменения состояния контракта. + +В staged/release-сценариях именно stage-транзакция обычно остаётся главной опорной транзакцией расследования, потому что отложенные callback-и живут на её исходном дереве транзакции, а не на дереве release-транзакции. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Захват трассы stage и release | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш stage-транзакции и хеш release-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому дереву транзакции | +| Проверка материализации stage | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод staging-контракта, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления ожидаемых отложенных шагов | Подтверждает, что callback-и действительно стали доступны до того, как release-транзакция попытается их разбудить | +| Обогащение транзакций | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id` и индексированный статус исполнения | Даёт каждой транзакции устойчивую привязку к блоку, чтобы дальнейший анализ не зависел от памяти или ручных заметок | +| Снимки состояния контракта recorder | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до release, а затем опрашиваем его после release до появления ожидаемых записей | Доказывает реальный порядок последующих эффектов в состоянии контракта, а не только в метаданных дерева квитанций | +| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | +| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | +| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | +| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | + +**Что должен включать полезный ответ** + +- почему именно stage-транзакция, а не release-транзакция, обычно является главной опорной транзакцией расследования +- какой порядок callback-ов вы наблюдали +- в каких блоках стали видны изменения состояния +- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования + +### Начать с receipt ID и восстановить историю исполнения + +Используйте это расследование, когда на руках есть только receipt ID из трассы, лога ошибки или дерева callback-ов и нужно вернуться к понятной человеку истории того, что произошло. + +**Цель** + +- Перейти от одной квитанции к исходной транзакции, а затем расширить расследование ровно настолько, чтобы объяснить окружающее исполнение и эффекты в состоянии. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и определяем её содержимое, статус и связанный контекст транзакции | ID квитанции часто появляется в трассах и логах раньше, чем у человека складывается цельная история транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | После перехода из поиска квитанции забираем исходную транзакцию по хешу | Превращает одну квитанцию в читаемую историю исполнения с контекстом по получателю, блоку и статусу | +| Каноническое подтверждение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем результат на уровне протокола, когда индексированного вида недостаточно или нужны точные RPC-семантики | Полезно, когда важно различить индексированную интерпретацию и точное поведение RPC | +| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем содержащий блок и при необходимости расширяемся на соседние каскадные блоки, если исполнение растянулось по нескольким высотам | Помещает квитанцию во временную шкалу по блокам, которую проще объяснить | +| Окно активности аккаунта | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Забираем недавнюю активность аккаунтов, которых коснулась квитанция | Помогает связать квитанцию с окружающей историей по аккаунтам | +| Повторное чтение состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод с закреплённым `block_id`, если квитанция изменила видимое состояние контракта | Позволяет доказать, что квитанция не только существовала в метаданных, но и изменила устойчивое состояние контракта | + +**Что должен включать полезный ответ** + +- какую исходную транзакцию вы восстановили из квитанции +- была ли квитанция главным событием или только одним шагом в большом каскаде +- какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить +- был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым + +## Частые ошибки + +- Пытаться отправлять транзакцию через history API вместо сырого RPC. +- Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. +- Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». +- Повторно использовать непрозрачные токены пагинации с другим эндпоинтом или другим набором фильтров. + +## Полезные связанные страницы + +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [NEAR Data API](https://docs.fastnear.com/ru/neardata) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/tx/index.md b/static/ru/tx/index.md index bf3aadc..0562e02 100644 --- a/static/ru/tx/index.md +++ b/static/ru/tx/index.md @@ -40,6 +40,10 @@ https://tx.test.fastnear.com - [Поиск квитанции](https://docs.fastnear.com/ru/tx/receipt) — для расследования цепочки исполнения. - [Диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) — когда нужен ограниченный по диапазону просмотр истории. +## Нужен сценарий? + +Используйте [Transactions API Examples](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиск транзакций, расследование квитанций, история аккаунта и анализ диапазонов блоков. + ## Устранение неполадок ### Я ожидал, что здесь можно отправлять транзакции From b61f3fe621c12a316ea74a1eb4d1f36c17c5d875 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 10:36:16 -0700 Subject: [PATCH 02/35] docs(i18n): fix Russian examples landing copy --- .../current/api/index.md | 2 +- .../current/fastdata/kv/index.md | 2 +- .../current/neardata/index.md | 2 +- .../current/rpc/index.md | 2 +- .../current/snapshots/index.mdx | 2 +- .../current/tx/index.md | 2 +- static/ru/api.md | 2 +- static/ru/api/index.md | 2 +- static/ru/fastdata/kv.md | 2 +- static/ru/fastdata/kv/index.md | 2 +- static/ru/llms-full.txt | 12 ++++++------ static/ru/neardata.md | 2 +- static/ru/neardata/index.md | 2 +- static/ru/rpc.md | 2 +- static/ru/rpc/index.md | 2 +- static/ru/snapshots.md | 2 +- static/ru/snapshots/index.md | 2 +- static/ru/tx.md | 2 +- static/ru/tx/index.md | 2 +- 19 files changed, 24 insertions(+), 24 deletions(-) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/index.md index 52f699c..eb8a2ff 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/index.md @@ -56,7 +56,7 @@ https://test.api.fastnear.com ## Нужен сценарий? -Используйте [FastNear API Examples](/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, резолв ключа в аккаунт и переход к узким представлениям активов. +Используйте [примеры FastNear API](/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, определения аккаунта по ключу и перехода к узким представлениям активов. ## Устранение неполадок diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md index c785e79..9025cdf 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md @@ -53,7 +53,7 @@ https://kv.test.fastnear.com ## Нужен сценарий? -Используйте [KV FastData Examples](/fastdata/kv/examples) для простых пошаговых сценариев: exact-key lookups, история ключей, анализ по `predecessor_id` и переход к каноническому RPC. +Используйте [примеры KV FastData](/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. ## Рабочий цикл по умолчанию diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md index 9148700..7e709e4 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md @@ -47,7 +47,7 @@ https://testnet.neardata.xyz ## Нужен сценарий? -Используйте [NEAR Data API Examples](/neardata/examples) для простых пошаговых сценариев: optimistic polling, finalized confirmation, redirect helpers и переход к каноническому RPC. +Используйте [примеры NEAR Data API](/neardata/examples) для простых пошаговых сценариев: опроса оптимистичных блоков, подтверждения финализированных блоков, работы с перенаправлениями и перехода к каноническому RPC. ## Устранение неполадок diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/index.md index fcfcd3d..a5c4446 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/index.md @@ -45,7 +45,7 @@ https://archival-rpc.testnet.fastnear.com ## Нужен сценарий? -Используйте [RPC Examples](/rpc/examples) для простых пошаговых сценариев: точные проверки состояния, анализ блоков, view-вызовы контрактов и отправка с подтверждением транзакций. +Используйте [примеры RPC](/rpc/examples) для простых пошаговых сценариев: точных проверок состояния, анализа блоков, view-вызовов контрактов и отправки транзакций с подтверждением. ## Используйте RPC, когда 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 73ea72e..b689a3f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/index.mdx @@ -65,7 +65,7 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash ## Нужен сценарий? -Используйте [Snapshot Examples](/snapshots/examples) для операторских сценариев: выбор между optimized `fast-rpc`, standard RPC recovery и archival hot/cold snapshot paths. +Используйте [примеры снапшотов](/snapshots/examples) для операторских сценариев: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. ## Выберите сеть diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/index.md index f9f626f..34d185b 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/index.md @@ -50,7 +50,7 @@ https://tx.test.fastnear.com ## Нужен сценарий? -Используйте [Transactions API Examples](/tx/examples) для простых пошаговых сценариев: поиск транзакций, расследование квитанций, история аккаунта и анализ диапазонов блоков. +Используйте [примеры API транзакций](/tx/examples) для простых пошаговых сценариев: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. ## Устранение неполадок diff --git a/static/ru/api.md b/static/ru/api.md index 19898e6..e390245 100644 --- a/static/ru/api.md +++ b/static/ru/api.md @@ -48,7 +48,7 @@ https://test.api.fastnear.com ## Нужен сценарий? -Используйте [FastNear API Examples](https://docs.fastnear.com/ru/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, резолв ключа в аккаунт и переход к узким представлениям активов. +Используйте [примеры FastNear API](https://docs.fastnear.com/ru/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, определения аккаунта по ключу и перехода к узким представлениям активов. ## Устранение неполадок diff --git a/static/ru/api/index.md b/static/ru/api/index.md index 19898e6..e390245 100644 --- a/static/ru/api/index.md +++ b/static/ru/api/index.md @@ -48,7 +48,7 @@ https://test.api.fastnear.com ## Нужен сценарий? -Используйте [FastNear API Examples](https://docs.fastnear.com/ru/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, резолв ключа в аккаунт и переход к узким представлениям активов. +Используйте [примеры FastNear API](https://docs.fastnear.com/ru/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, определения аккаунта по ключу и перехода к узким представлениям активов. ## Устранение неполадок diff --git a/static/ru/fastdata/kv.md b/static/ru/fastdata/kv.md index aabae60..af02a0e 100644 --- a/static/ru/fastdata/kv.md +++ b/static/ru/fastdata/kv.md @@ -45,7 +45,7 @@ https://kv.test.fastnear.com ## Нужен сценарий? -Используйте [KV FastData Examples](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: exact-key lookups, история ключей, анализ по `predecessor_id` и переход к каноническому RPC. +Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. ## Рабочий цикл по умолчанию diff --git a/static/ru/fastdata/kv/index.md b/static/ru/fastdata/kv/index.md index aabae60..af02a0e 100644 --- a/static/ru/fastdata/kv/index.md +++ b/static/ru/fastdata/kv/index.md @@ -45,7 +45,7 @@ https://kv.test.fastnear.com ## Нужен сценарий? -Используйте [KV FastData Examples](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: exact-key lookups, история ключей, анализ по `predecessor_id` и переход к каноническому RPC. +Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. ## Рабочий цикл по умолчанию diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 799e243..037053b 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -969,7 +969,7 @@ https://test.api.fastnear.com ## Нужен сценарий? -Используйте [FastNear API Examples](https://docs.fastnear.com/ru/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, резолв ключа в аккаунт и переход к узким представлениям активов. +Используйте [примеры FastNear API](https://docs.fastnear.com/ru/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, определения аккаунта по ключу и перехода к узким представлениям активов. ## Устранение неполадок @@ -1240,7 +1240,7 @@ https://kv.test.fastnear.com ## Нужен сценарий? -Используйте [KV FastData Examples](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: exact-key lookups, история ключей, анализ по `predecessor_id` и переход к каноническому RPC. +Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. ## Рабочий цикл по умолчанию @@ -1725,7 +1725,7 @@ https://testnet.neardata.xyz ## Нужен сценарий? -Используйте [NEAR Data API Examples](https://docs.fastnear.com/ru/neardata/examples) для простых пошаговых сценариев: optimistic polling, finalized confirmation, redirect helpers и переход к каноническому RPC. +Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для простых пошаговых сценариев: опроса оптимистичных блоков, подтверждения финализированных блоков, работы с перенаправлениями и перехода к каноническому RPC. ## Устранение неполадок @@ -1987,7 +1987,7 @@ https://archival-rpc.testnet.fastnear.com ## Нужен сценарий? -Используйте [RPC Examples](https://docs.fastnear.com/ru/rpc/examples) для простых пошаговых сценариев: точные проверки состояния, анализ блоков, view-вызовы контрактов и отправка с подтверждением транзакций. +Используйте [примеры RPC](https://docs.fastnear.com/ru/rpc/examples) для простых пошаговых сценариев: точных проверок состояния, анализа блоков, view-вызовов контрактов и отправки транзакций с подтверждением. ## Используйте RPC, когда @@ -2221,7 +2221,7 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash ## Нужен сценарий? -Используйте [Snapshot Examples](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбор между optimized `fast-rpc`, standard RPC recovery и archival hot/cold snapshot paths. +Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. ## Выберите сеть @@ -2828,7 +2828,7 @@ https://tx.test.fastnear.com ## Нужен сценарий? -Используйте [Transactions API Examples](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиск транзакций, расследование квитанций, история аккаунта и анализ диапазонов блоков. +Используйте [примеры API транзакций](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. ## Устранение неполадок diff --git a/static/ru/neardata.md b/static/ru/neardata.md index 7eaff28..f46f3cf 100644 --- a/static/ru/neardata.md +++ b/static/ru/neardata.md @@ -39,7 +39,7 @@ https://testnet.neardata.xyz ## Нужен сценарий? -Используйте [NEAR Data API Examples](https://docs.fastnear.com/ru/neardata/examples) для простых пошаговых сценариев: optimistic polling, finalized confirmation, redirect helpers и переход к каноническому RPC. +Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для простых пошаговых сценариев: опроса оптимистичных блоков, подтверждения финализированных блоков, работы с перенаправлениями и перехода к каноническому RPC. ## Устранение неполадок diff --git a/static/ru/neardata/index.md b/static/ru/neardata/index.md index 7eaff28..f46f3cf 100644 --- a/static/ru/neardata/index.md +++ b/static/ru/neardata/index.md @@ -39,7 +39,7 @@ https://testnet.neardata.xyz ## Нужен сценарий? -Используйте [NEAR Data API Examples](https://docs.fastnear.com/ru/neardata/examples) для простых пошаговых сценариев: optimistic polling, finalized confirmation, redirect helpers и переход к каноническому RPC. +Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для простых пошаговых сценариев: опроса оптимистичных блоков, подтверждения финализированных блоков, работы с перенаправлениями и перехода к каноническому RPC. ## Устранение неполадок diff --git a/static/ru/rpc.md b/static/ru/rpc.md index 2ac06a2..4940382 100644 --- a/static/ru/rpc.md +++ b/static/ru/rpc.md @@ -37,7 +37,7 @@ https://archival-rpc.testnet.fastnear.com ## Нужен сценарий? -Используйте [RPC Examples](https://docs.fastnear.com/ru/rpc/examples) для простых пошаговых сценариев: точные проверки состояния, анализ блоков, view-вызовы контрактов и отправка с подтверждением транзакций. +Используйте [примеры RPC](https://docs.fastnear.com/ru/rpc/examples) для простых пошаговых сценариев: точных проверок состояния, анализа блоков, view-вызовов контрактов и отправки транзакций с подтверждением. ## Используйте RPC, когда diff --git a/static/ru/rpc/index.md b/static/ru/rpc/index.md index 2ac06a2..4940382 100644 --- a/static/ru/rpc/index.md +++ b/static/ru/rpc/index.md @@ -37,7 +37,7 @@ https://archival-rpc.testnet.fastnear.com ## Нужен сценарий? -Используйте [RPC Examples](https://docs.fastnear.com/ru/rpc/examples) для простых пошаговых сценариев: точные проверки состояния, анализ блоков, view-вызовы контрактов и отправка с подтверждением транзакций. +Используйте [примеры RPC](https://docs.fastnear.com/ru/rpc/examples) для простых пошаговых сценариев: точных проверок состояния, анализа блоков, view-вызовов контрактов и отправки транзакций с подтверждением. ## Используйте RPC, когда diff --git a/static/ru/snapshots.md b/static/ru/snapshots.md index 9fdbaba..0402df5 100644 --- a/static/ru/snapshots.md +++ b/static/ru/snapshots.md @@ -49,7 +49,7 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash ## Нужен сценарий? -Используйте [Snapshot Examples](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбор между optimized `fast-rpc`, standard RPC recovery и archival hot/cold snapshot paths. +Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. ## Выберите сеть diff --git a/static/ru/snapshots/index.md b/static/ru/snapshots/index.md index 9fdbaba..0402df5 100644 --- a/static/ru/snapshots/index.md +++ b/static/ru/snapshots/index.md @@ -49,7 +49,7 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash ## Нужен сценарий? -Используйте [Snapshot Examples](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбор между optimized `fast-rpc`, standard RPC recovery и archival hot/cold snapshot paths. +Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. ## Выберите сеть diff --git a/static/ru/tx.md b/static/ru/tx.md index 0562e02..1941084 100644 --- a/static/ru/tx.md +++ b/static/ru/tx.md @@ -42,7 +42,7 @@ https://tx.test.fastnear.com ## Нужен сценарий? -Используйте [Transactions API Examples](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиск транзакций, расследование квитанций, история аккаунта и анализ диапазонов блоков. +Используйте [примеры API транзакций](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. ## Устранение неполадок diff --git a/static/ru/tx/index.md b/static/ru/tx/index.md index 0562e02..1941084 100644 --- a/static/ru/tx/index.md +++ b/static/ru/tx/index.md @@ -42,7 +42,7 @@ https://tx.test.fastnear.com ## Нужен сценарий? -Используйте [Transactions API Examples](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиск транзакций, расследование квитанций, история аккаунта и анализ диапазонов блоков. +Используйте [примеры API транзакций](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. ## Устранение неполадок From 71fd4968242c4cf1ea381ca804a0ea676120a291 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 12:18:56 -0700 Subject: [PATCH 03/35] docs: expand examples guides and replay flows --- docs/api/examples.md | 288 ++++ docs/fastdata/kv/examples.md | 58 + docs/neardata/examples.md | 46 + docs/rpc/examples.md | 546 +++++++ docs/snapshots/examples.mdx | 28 + docs/transfers/examples.md | 67 + docs/tx/examples.md | 218 +++ .../current/api/examples.md | 288 ++++ .../current/fastdata/kv/examples.md | 58 + .../current/neardata/examples.md | 46 + .../current/rpc/examples.md | 546 +++++++ .../current/snapshots/examples.mdx | 28 + .../current/transfers/examples.md | 67 + .../current/tx/examples.md | 218 +++ sidebars.js | 349 +++-- src/css/custom.css | 24 + static/ru/api/examples.md | 288 ++++ static/ru/api/examples/index.md | 288 ++++ static/ru/fastdata/kv/examples.md | 58 + static/ru/fastdata/kv/examples/index.md | 58 + static/ru/llms-full.txt | 1249 +++++++++++++++++ static/ru/neardata/examples.md | 46 + static/ru/neardata/examples/index.md | 46 + static/ru/rpc/examples.md | 544 +++++++ static/ru/rpc/examples/index.md | 544 +++++++ static/ru/snapshots/examples.md | 28 + static/ru/snapshots/examples/index.md | 28 + static/ru/transfers/examples.md | 67 + static/ru/transfers/examples/index.md | 67 + static/ru/tx/examples.md | 218 +++ static/ru/tx/examples/index.md | 218 +++ 31 files changed, 6466 insertions(+), 156 deletions(-) diff --git a/docs/api/examples.md b/docs/api/examples.md index 96a7ed1..6e9f822 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -90,6 +90,294 @@ Use this page when the user wants a readable account- or asset-shaped answer and - The indexed view is not enough and the user needs exact on-chain semantics. Move to [RPC Reference](/rpc). - The question becomes historical or execution-oriented instead of "what does this account hold now?" Move to [Transactions API](/tx). +## Worked walkthroughs + +### Resolve a public key, then fetch the account snapshot + +Use this when you have a public key first and the next user-facing question is “which account is this?” followed immediately by “what does that account look like right now?” + +**What you're doing** + +- Resolve the public key to one or more account IDs. +- Extract the first matching account ID with `jq`. +- Reuse that value in the broad account snapshot endpoint. + +```bash +API_BASE_URL=https://api.fastnear.com +PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' +# Example public key from the docs page model: +# PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' + +ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" + +ACCOUNT_ID="$( + curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ + | tee /tmp/fastnear-public-key.json \ + | jq -r '.account_ids[0]' +)" + +jq '{account_ids}' /tmp/fastnear-public-key.json + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +``` + +**Why this next step?** + +The public-key lookup answers identity. The full account snapshot answers the next practical question with product-shaped data. If the key maps to multiple accounts instead of one, widen to [V1 Public Key Lookup All](/api/v1/public-key-all) or loop through each returned `account_id`. + +### Check collection membership, then mint a derivative NFT + +Use this when the user story is “if this account already owns at least one NFT from collection X, mint one more NFT whose metadata records that relationship.” + +**Network** + +- testnet + +**Official references** + +- [Pre-deployed NFT contract](https://docs.near.org/tutorials/nfts/js/predeployed-contract) +- [NEP-171 NFT standard](https://docs.near.org/primitives/nft/standard) + +Before you start, make sure the account already holds at least one token from `nft.examples.testnet`. If it does not, mint one first with the pre-deployed contract guide above and then come back to this workflow. + +**What you're doing** + +- Use FastNear API to answer the gate question quickly. +- Widen to RPC `nft_tokens_for_owner` to recover exact token IDs and metadata from the source collection. +- Build deterministic derived metadata from that source set. +- Mint the derivative token, then verify it with the same NFT view method. + +```bash +API_BASE_URL=https://test.api.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SOURCE_COLLECTION_ID=nft.examples.testnet +DESTINATION_COLLECTION_ID=nft.examples.testnet +SIGNER_ACCOUNT_ID="$ACCOUNT_ID" +TOKEN_ID="derivative-$(date +%s)" +``` + +1. Use FastNear API to check whether the account holds any NFT from the source collection. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | tee /tmp/testnet-account-nfts.json >/dev/null + +jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + holds_collection: any(.tokens[]?; .contract_id == $source_collection_id), + matching_contracts: [ + .tokens[]? + | select(.contract_id == $source_collection_id) + ] +}' /tmp/testnet-account-nfts.json +``` + +2. Widen to canonical RPC so you can recover exact token IDs and source metadata from that collection. + +```bash +NFT_TOKENS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + from_index: "0", + limit: 50 + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOURCE_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_tokens_for_owner", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/source-collection-tokens.json >/dev/null + +jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + source_collection_id: $source_collection_id, + source_count: length, + source_token_ids: (map(.token_id) | sort | .[:5]) +}' /tmp/source-collection-tokens.json +``` + +3. Build deterministic derivative metadata from that source set. + +```bash +DERIVATIVE_METADATA_JSON="$( + jq -c --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + title: ("Derivative witness for " + $source_collection_id), + description: + ("Minted because the holder currently owns " + + (length | tostring) + + " token(s) from " + + $source_collection_id), + media: ( + map(.metadata.media) + | map(select(. != null)) + | .[0] + ), + copies: 1, + extra: ({ + source_collection_id: $source_collection_id, + source_count: length, + source_token_ids: (map(.token_id) | sort | .[:5]) + } | @json) + }' /tmp/source-collection-tokens.json +)" + +printf '%s\n' "$DERIVATIVE_METADATA_JSON" | jq '.' +``` + +4. Mint the derivative token on the destination collection. + +```bash +near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ + --arg token_id "$TOKEN_ID" \ + --arg receiver_id "$ACCOUNT_ID" \ + --argjson metadata "$DERIVATIVE_METADATA_JSON" '{ + token_id: $token_id, + receiver_id: $receiver_id, + metadata: $metadata + }')" \ + --accountId "$SIGNER_ACCOUNT_ID" \ + --deposit 0.1 \ + --networkId testnet +``` + +5. Verify the new token with the same canonical NFT view method. + +Poll a few times instead of assuming failure if the token does not appear immediately after the mint transaction returns. + +```bash +for attempt in 1 2 3 4 5; do + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$DESTINATION_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_tokens_for_owner", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | jq --arg token_id "$TOKEN_ID" ' + map(select(.token_id == $token_id)) + ' \ + | tee /tmp/derivative-token-verification.json >/dev/null + + if jq -e 'length > 0' /tmp/derivative-token-verification.json >/dev/null; then + break + fi + + sleep 1 +done + +jq '.' /tmp/derivative-token-verification.json +``` + +**Why this next step?** + +FastNear API is the fastest way to answer the gate question. Once the user qualifies, RPC becomes the right surface for exact token-level inspection and verification because it exposes the source collection’s canonical NFT view methods directly. + +### Am I locked or liquid? + +Use this when the user story is “show me whether this wallet is exposed through direct staking pools, liquid staking tokens, or both.” + +**Network** + +- mainnet + +**Official references** + +- [Validator staking](https://docs.near.org/concepts/basics/staking) +- [Using liquid staking](https://docs.near.org/primitives/liquid-staking) + +This example is intentionally observational. It classifies what FastNear can see from staking positions and FT balances today. It does not prove every possible synthetic or off-platform staking exposure. + +**What you're doing** + +- Read indexed direct staking positions from the account staking endpoint. +- Read indexed FT balances from the account FT endpoint. +- Classify the account into `direct_only`, `liquid_only`, `mixed`, or `no_visible_staking_position`. +- Print the direct pool list and the liquid staking token list that informed the classification. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +``` + +1. Fetch the direct staking view. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' +``` + +2. Fetch fungible token balances so you can detect liquid staking positions. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` + +3. Classify the account from those two indexed views. + +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' +``` + +**Why this next step?** + +If the classification is `direct_only`, the next operational question is usually about unstake and withdraw timing. If it is `liquid_only`, the next question is usually about redeeming or swapping the liquid token. If it is `mixed`, you should treat those as two separate exit paths rather than assuming one flow covers both. + ## Common mistakes - Leading with the broad account snapshot when the user only asked about one asset family. diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index b319c48..a7f8aaa 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -125,6 +125,64 @@ Use this investigation when one contract key looks suspicious and you need to co - the latest indexed value and what changed in history - whether canonical `view_state` matched the indexed current value +### Shell walkthrough + +Use this when one fully qualified key is already known and you want to move cleanly from “what is the latest indexed row?” to “what is the broader indexed history for this key?” + +**What you're doing** + +- Read one latest indexed key with the exact contract, predecessor, and key path. +- Extract the exact `key` with `jq`. +- Reuse that key in `POST /v0/history` to widen into history. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +EXACT_KEY="$( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | tee /tmp/kv-latest.json \ + | jq -r '.entries[0].key' +)" + +jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' /tmp/kv-latest.json + +curl -s "$KV_BASE_URL/v0/history" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ + | jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + value + } + ] + }' +``` + +**Why this next step?** + +The latest lookup gives the narrowest possible answer. Reusing the exact `key` in `POST /v0/history` shows whether that key also appears in a wider indexed pattern. If that result is too broad, narrow back down with [GET History by Exact Key](/fastdata/kv/get-history-key). + ## Common mistakes - Starting with broad account or predecessor scans when an exact key is already known. diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index ff9f16c..d12d47d 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -126,6 +126,52 @@ Use this investigation when you need early detection from the optimistic head, b - when the same observation became finalized - whether canonical RPC inspection changed the interpretation +### Shell walkthrough + +Use this when you want the polling helper to choose the latest finalized block for you, but the follow-up still needs canonical RPC confirmation. + +**What you're doing** + +- Inspect the redirect returned by `GET /v0/last_block/final`. +- Fetch the resolved block document. +- Extract `block.header.height` with `jq`. +- Reuse that height in RPC `block` by height. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +RPC_URL=https://rpc.mainnet.fastnear.com + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +printf 'Redirect target: %s\n' "$FINAL_LOCATION" + +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | tee /tmp/neardata-final-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + +BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "block", + params: { + block_id: ($block_height | tonumber) + } + }')" \ + | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +``` + +**Why this next step?** + +The redirect helper is the easiest polling surface for “latest finalized.” Once it tells you the exact block height, RPC becomes the right place to ask for canonical block semantics without guessing which block to inspect. + ## Common mistakes - Treating NEAR Data API as a streaming product instead of a polling surface. diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index f415be9..29eda05 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -115,6 +115,552 @@ Use this page when you already know the answer needs canonical RPC behavior and - The next question is about receipts, affected accounts, or execution history in a human-friendly order. - You need a broader investigation workflow instead of one canonical status check. +## Worked walkthroughs + +### Audit and remove old Near Social function-call keys + +Use this when you know an account has accumulated older `social.near` function-call keys and you want to inspect them, choose one intentionally, and remove it with raw RPC submission. + +**What you're doing** + +- Use canonical RPC to list every access key on the account. +- Narrow that list to function-call keys scoped to `social.near`. +- Inspect one candidate key exactly before you delete it. +- Build and sign a `DeleteKey` transaction with a full-access key, then submit it through RPC and verify the key is gone. + +Two caveats matter up front: + +- The deleting key must be a full-access key. A function-call key cannot sign a `DeleteKey` action. +- This flow is about exact key state and cleanup. The optional Transactions API step below gives account-level context, not authoritative per-key “last used” forensics. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export SOCIAL_RECEIVER_ID=social.near +export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' +export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' +export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' +``` + +1. List all access keys on the account, then narrow to `social.near` function-call keys. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/fastnear-access-keys.json >/dev/null + +jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + nonce: .access_key.nonce, + receiver_id: .access_key.permission.FunctionCall.receiver_id, + method_names: .access_key.permission.FunctionCall.method_names, + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } +' /tmp/fastnear-access-keys.json +``` + +Pick one `public_key` from that filtered list and set `DELETE_PUBLIC_KEY` to it. + +2. Inspect the specific candidate key one more time before deleting it. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$DELETE_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" + } + }')" \ + | jq '{nonce: .result.nonce, permission: .result.permission}' +``` + +3. Optional: pull recent function-call activity for the account to decide whether you want to investigate more before cleanup. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + is_function_call: true, + limit: 10 + }')" \ + | jq '{ + account_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_success + } + ] + }' +``` + +That query helps answer “has this account still been doing function-call work recently?”, but it does not prove that a specific access key was the one used. + +4. Sign a `DeleteKey` transaction for `DELETE_PUBLIC_KEY` with a full-access key. + +Run this in a directory where `near-api-js@5` is installed. The command reads the environment variables above, fetches the latest nonce for `FULL_ACCESS_PUBLIC_KEY`, fetches a fresh final block hash, signs a `DeleteKey` action, and stores the resulting `signed_tx_base64` in `SIGNED_TX_BASE64`. + +```bash +SIGNED_TX_BASE64="$( + node --input-type=module <<'EOF' +import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; + +const { + ACCOUNT_ID, + NETWORK_ID = 'mainnet', + RPC_URL = 'https://rpc.mainnet.fastnear.com', + DELETE_PUBLIC_KEY, + FULL_ACCESS_PUBLIC_KEY, + FULL_ACCESS_PRIVATE_KEY, +} = process.env; + +for (const name of [ + 'ACCOUNT_ID', + 'DELETE_PUBLIC_KEY', + 'FULL_ACCESS_PUBLIC_KEY', + 'FULL_ACCESS_PRIVATE_KEY', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); +const derivedPublicKey = keyPair.getPublicKey().toString(); + +if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { + throw new Error( + `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: ACCOUNT_ID, + public_key: FULL_ACCESS_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const transaction = transactions.createTransaction( + ACCOUNT_ID, + utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), + ACCOUNT_ID, + BigInt(accessKey.nonce) + 1n, + [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +)" +``` + +5. Submit the signed transaction through raw RPC and wait for `FINAL`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Re-run the access-key list and verify that the deleted key is gone. + +```bash +if curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' + .result.keys[] + | select(.public_key == $public_key) + ' >/dev/null; then + echo "Key is still present: $DELETE_PUBLIC_KEY" +else + echo "Key deleted: $DELETE_PUBLIC_KEY" +fi +``` + +**Why this next step?** + +Re-running `view_access_key_list` closes the loop on the same canonical surface you used for discovery. If the delete succeeded there, you do not need a higher-level indexed summary to prove the cleanup. + +### Register FT storage if needed, then transfer tokens + +Use this when the user story is “send fungible tokens safely, but first prove whether the receiver is already registered for storage on that FT contract.” + +**Network** + +- testnet + +**Official references** + +- [FT storage and transfer](https://docs.near.org/integrations/fungible-tokens) +- [Pre-deployed FT contract](https://docs.near.org/tutorials/fts/predeployed-contract) + +This walkthrough uses the safe public contract `ft.predeployed.examples.testnet`. Before you start, make sure the sender already holds some `gtNEAR` there. If not, mint a small balance first with the pre-deployed contract guide above and then come back to this flow. + +**What you're doing** + +- Use canonical RPC view calls to check whether the receiver already has FT storage on the contract. +- If needed, fetch the minimum storage requirement. +- Sign and submit `storage_deposit`, then `ft_transfer`. +- Verify the receiver balance with the same contract’s canonical view method. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet +export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' +export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' +export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' +``` + +1. Check whether the receiver is already registered on the FT contract. + +```bash +STORAGE_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$STORAGE_BALANCE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "storage_balance_of", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/ft-storage-balance.json >/dev/null + +jq '{ + registered: ((.result.result | implode | fromjson) != null), + storage_balance: (.result.result | implode | fromjson) +}' /tmp/ft-storage-balance.json +``` + +2. If the receiver is not registered yet, fetch the minimum storage deposit. + +```bash +MIN_STORAGE_YOCTO="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_CONTRACT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "storage_balance_bounds", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/ft-storage-bounds.json \ + | jq -r '.result.result | implode | fromjson | .min' +)" + +printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" +``` + +3. Define one reusable signer for contract function calls. + +Run this in a directory where `near-api-js@5` is installed. The function below reads the exported shell variables above and turns each function call into a signed payload for raw RPC submission. + +```bash +sign_function_call() { + METHOD_NAME="$1" \ + ARGS_JSON="$2" \ + DEPOSIT_YOCTO="$3" \ + GAS_TGAS="$4" \ + node --input-type=module <<'EOF' +import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; + +const { + NETWORK_ID = 'testnet', + RPC_URL = 'https://rpc.testnet.fastnear.com', + TOKEN_CONTRACT_ID, + SENDER_ACCOUNT_ID, + SENDER_PUBLIC_KEY, + SENDER_PRIVATE_KEY, + METHOD_NAME, + ARGS_JSON, + DEPOSIT_YOCTO = '0', + GAS_TGAS = '100', +} = process.env; + +for (const name of [ + 'TOKEN_CONTRACT_ID', + 'SENDER_ACCOUNT_ID', + 'SENDER_PUBLIC_KEY', + 'SENDER_PRIVATE_KEY', + 'METHOD_NAME', + 'ARGS_JSON', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); +const signer = await InMemorySigner.fromKeyPair( + NETWORK_ID, + SENDER_ACCOUNT_ID, + keyPair +); + +const derivedPublicKey = keyPair.getPublicKey().toString(); +if (derivedPublicKey !== SENDER_PUBLIC_KEY) { + throw new Error( + `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: SENDER_ACCOUNT_ID, + public_key: SENDER_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const action = transactions.functionCall( + METHOD_NAME, + Buffer.from(ARGS_JSON), + BigInt(GAS_TGAS) * 10n ** 12n, + BigInt(DEPOSIT_YOCTO) +); + +const transaction = transactions.createTransaction( + SENDER_ACCOUNT_ID, + utils.PublicKey.fromString(SENDER_PUBLIC_KEY), + TOKEN_CONTRACT_ID, + BigInt(accessKey.nonce) + 1n, + [action], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + SENDER_ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +} +``` + +4. If needed, register the receiver for storage first. + +```bash +if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then + SIGNED_TX_BASE64="$( + sign_function_call \ + storage_deposit \ + "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id, + registration_only: true + }')" \ + "$MIN_STORAGE_YOCTO" \ + 100 + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash + }' +fi +``` + +5. Transfer the FT after storage is ready. + +```bash +SIGNED_TX_BASE64="$( + sign_function_call \ + ft_transfer \ + "$(jq -nc \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ + receiver_id: $receiver_id, + amount: $amount, + memo: "FastNear RPC example" + }')" \ + 1 \ + 100 +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Verify the receiver’s FT balance with the contract’s canonical view method. + +```bash +RECEIVER_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_balance_of", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + receiver_balance: (.result.result | implode | fromjson) + }' +``` + +**Why this next step?** + +This is a canonical RPC example because every step stays on exact contract state and exact transaction submission semantics: first prove storage state, then submit the minimum required change calls, then verify the post-transfer state directly on the contract. + ## Common mistakes - Starting in RPC when the user really wants a holdings summary or indexed history. diff --git a/docs/snapshots/examples.mdx b/docs/snapshots/examples.mdx index 28e69c4..83c4cae 100644 --- a/docs/snapshots/examples.mdx +++ b/docs/snapshots/examples.mdx @@ -127,6 +127,34 @@ Use this investigation when an operator says “I need this node back online” - where data should land on disk - whether the operator should stay in FastNear snapshot docs or move to broader nearcore guidance +### Shell walkthrough + +Use this when you have already decided that archival mainnet is the right path and now need the exact command sequence with one shared block anchor. + +**What you're doing** + +- Fetch the latest archival mainnet snapshot height once. +- Store it in `LATEST`. +- Reuse that exact block height for both the hot-data and cold-data downloads. + +```bash +HOT_DATA_PATH=~/.near/data +COLD_DATA_PATH=/mnt/hdds/cold-data + +LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" +echo "Latest archival mainnet snapshot block: $LATEST" + +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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash +``` + +**Why this next step?** + +Hot and cold archival data need to come from the same snapshot cut. Reusing one captured `LATEST` value across both commands keeps the archival dataset internally consistent and makes later nearcore configuration much less surprising. + ## Common mistakes - Using snapshot docs when the task is really about reading chain data. diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index 9e93064..0a29029 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -85,6 +85,73 @@ Use this page when the question is specifically about asset movement and you wan - The user explicitly needs receipt-level or canonical RPC confirmation. Move to [Transactions API](/tx) first, then [RPC Reference](/rpc) if needed. +## Worked walkthrough + +### Query a narrow transfer window, then pivot by receipt + +Use this when the first question is still transfer-only, but you know you may need one precise execution pivot after you isolate the relevant movement. + +**What you're doing** + +- Query a bounded outgoing transfer window for one account on mainnet. +- Extract the first `receipt_id` with `jq`. +- Reuse that receipt ID in Transactions API to move from asset movement into execution context. + +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +FROM_TIMESTAMP_MS=1711929600000 +TO_TIMESTAMP_MS=1712016000000 + +RECEIPT_ID="$( + curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | tee /tmp/transfers-window.json \ + | jq -r '.transfers[0].receipt_id' +)" + +jq '{ + resume_token, + transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + asset_id, + amount, + other_account_id, + block_height + } + ] +}' /tmp/transfers-window.json + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' +``` + +**Why this next step?** + +The transfer query keeps the first pass narrow and pagination-friendly. Pivoting by `receipt_id` gives you one exact execution anchor without immediately widening into full account history. If you still need more rows afterward, continue paginating with the same `resume_token` and unchanged filters. + ## Common mistakes - Using Transfers API when the user really wants balances, holdings, or account summaries. diff --git a/docs/tx/examples.md b/docs/tx/examples.md index f2c0b74..e0efc1f 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -164,6 +164,224 @@ Use this investigation when the only thing you have is a receipt ID from a trace - the minimum block and account context needed to explain it - whether the state effect was durable and at which block height it became visible +### Understand a two-party `token_diff` match, then trace a live NEAR Intents settlement + +Use this investigation when the user story is “show me what NEAR Intents is doing under the hood, but keep the trace anchored in public data I can inspect myself.” + +**Goal** + +- Explain the matching model first, then turn one real `intents.near` settlement into a readable execution story across Transactions API and canonical RPC. + +**Official references** + +- [NEAR Intents overview](https://docs.near.org/chain-abstraction/intents/overview) +- [Intent types and execution](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) +- [Account abstraction](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) + +#### Part 1: protocol anatomy + +The core matching shape is the `token_diff` intent. One side declares which assets it is willing to give and receive, and the matching side declares the opposite diff. In the official verifier docs, a two-party USDC/USDT swap is expressed as one signed payload that says “I will give `-10` USDC and receive `+10` USDT” and another that says the reverse. Those signed intents can be bundled through the Message Bus or through any other off-chain coordination channel, then submitted together to `intents.near`. + +That conceptual part is useful for understanding the protocol, but the signed examples in the official docs are illustrative and time-bound. For an operational FastNear workflow, it is better to trace one real mainnet settlement than to pretend the documentation sample is a reusable live transaction. + +#### Part 2: live FastNear trace + +For the live trace below, use this fixed settlement anchor captured on **April 18, 2026**: + +- transaction hash: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` +- signer and receiver: `intents.near` +- included block height: `194573310` + +The public FastNear surfaces are enough to reconstruct a lot: + +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Settlement anchor | Transactions API [`POST /v0/transactions`](/tx/transactions) | Start from the fixed transaction hash and recover the main transaction plus the downstream receipt list | Gives you one readable settlement skeleton without decoding raw receipts first | +| Included block context | Transactions API [`POST /v0/block`](/tx/block) | Fetch the included block with receipts enabled and filter it back down to the same transaction hash | Places the settlement into the surrounding block window and shows which receipts appeared there | +| Canonical receipt DAG | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Ask for the same transaction with `wait_until: "FINAL"` and inspect `receipts_outcome` | Gives you the protocol-native DAG, executor IDs, and raw event logs | +| Event classification | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Pull event names such as `token_diff`, `intents_executed`, `mt_transfer`, and `mt_withdraw` out of the logged `EVENT_JSON` lines | Lets you explain the settlement by event type instead of by opaque receipt IDs alone | + +**What a useful answer should include** + +- how the conceptual two-party `token_diff` model maps onto the real `execute_intents` settlement +- which downstream contracts and methods appeared after `intents.near` +- which event families the trace emitted +- which block heights formed the main cascade + +This example intentionally stays on public FastNear surfaces. NEAR Intents Explorer and the 1Click Explorer are useful too, but their Explorer API is JWT-gated and not the right default for a public docs walkthrough. + +### Live NEAR Intents trace shell walkthrough + +Use this when you want one concrete `intents.near` settlement that you can inspect immediately with public FastNear endpoints. + +**What you're doing** + +- Pull the transaction story from Transactions API. +- Reuse the included block hash in `POST /v0/block` to inspect the containing block. +- Confirm the canonical receipt DAG and event log families with `EXPERIMENTAL_tx_status`. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 +INTENTS_SIGNER_ID=intents.near +``` + +1. Start with the settlement transaction itself. + +```bash +INTENTS_BLOCK_HASH="$( + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$INTENTS_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/intents-transaction.json \ + | jq -r '.transactions[0].execution_outcome.block_hash' +)" + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + receipt_flow: [ + .transactions[0].receipts[:6][] + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + block_height: .execution_outcome.block_height, + methods: ( + [.receipt.receipt.Action.actions[]?.FunctionCall.method_name] + | map(select(. != null)) + ), + first_log: (.execution_outcome.outcome.logs[0] // null) + } + ] +}' /tmp/intents-transaction.json +``` + +2. Reuse the block hash to inspect the containing block with receipts enabled. + +```bash +curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_id "$INTENTS_BLOCK_HASH" '{ + block_id: $block_id, + with_receipts: true, + with_transactions: false + }')" \ + | tee /tmp/intents-block.json >/dev/null + +jq --arg tx_hash "$INTENTS_TX_HASH" '{ + block_height: .block.block_height, + block_hash: .block.block_hash, + tx_receipts: [ + .block_receipts[] + | select(.transaction_hash == $tx_hash) + | { + receipt_id, + predecessor_id, + receiver_id, + block_height + } + ] +}' /tmp/intents-block.json +``` + +3. Confirm the canonical receipt DAG and extract the event families from RPC. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$INTENTS_TX_HASH" \ + --arg sender_account_id "$INTENTS_SIGNER_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $sender_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/intents-rpc.json >/dev/null + +jq '{ + final_execution_status: .result.final_execution_status, + receipts_outcome: [ + .result.receipts_outcome[:6][] + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + first_log: (.outcome.logs[0] // null) + } + ] +}' /tmp/intents-rpc.json + +jq -r ' + .result.receipts_outcome[] + | .outcome.logs[] + | select(startswith("EVENT_JSON:")) + | capture("event\":\"(?[^\"]+)\"").event +' /tmp/intents-rpc.json | sort -u +``` + +**Why this next step?** + +`POST /v0/transactions` gives you the readable settlement skeleton. `POST /v0/block` shows how that settlement sits inside the containing block. `EXPERIMENTAL_tx_status` is the canonical follow-up when you need executor IDs, receipt-DAG structure, and raw event logs instead of just indexed summaries. + +### Receipt pivot shell walkthrough + +Use this when you already have one `receipt_id` and want the shortest path back to a readable transaction story. + +**What you're doing** + +- Resolve the receipt first. +- Extract `receipt.transaction_hash` with `jq`. +- Reuse that transaction hash in `POST /v0/transactions`. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RECEIPT_ID=YOUR_RECEIPT_ID +# Example receipt ID from a recent mainnet transfer: +# RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' + +TX_HASH="$( + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | tee /tmp/receipt-lookup.json \ + | jq -r '.receipt.transaction_hash' +)" + +jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash, + tx_block_height: .receipt.tx_block_height + } +}' /tmp/receipt-lookup.json + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + receipt_count: (.transactions[0].receipts | length) + }' +``` + +**Why this next step?** + +`POST /v0/receipt` gives you the pivot. `POST /v0/transactions` turns that pivot into a readable story with signer, receiver, block, and receipt context. Only after that should you widen to block or account windows. + ## Common mistakes - Trying to submit a transaction from the history API instead of raw RPC. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index e996789..122eb82 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -90,6 +90,294 @@ page_actions: - Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](/rpc). - Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](/tx). +## Готовые сценарии + +### Определить аккаунт по публичному ключу, а затем получить сводку по нему + +Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» + +**Что вы делаете** + +- Ищете по публичному ключу один или несколько `account_id`. +- Извлекаете первый найденный `account_id` через `jq`. +- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. + +```bash +API_BASE_URL=https://api.fastnear.com +PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' +# Пример публичного ключа из модели страницы в документации: +# PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' + +ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" + +ACCOUNT_ID="$( + curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ + | tee /tmp/fastnear-public-key.json \ + | jq -r '.account_ids[0]' +)" + +jq '{account_ids}' /tmp/fastnear-public-key.json + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +``` + +**Зачем нужен следующий шаг?** + +Поиск по публичному ключу отвечает на вопрос об идентификации. Полный снимок аккаунта отвечает на следующий прикладной вопрос уже в продуктовой форме. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, расширяйтесь до [V1 Public Key Lookup All](/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. + +### Проверить владение коллекцией, а затем выпустить производный NFT + +Используйте этот сценарий, когда история звучит так: «если аккаунт уже владеет хотя бы одним NFT из коллекции X, выпустить ещё один NFT, в чьих метаданных будет зафиксирована эта связь». + +**Сеть** + +- testnet + +**Официальные ссылки** + +- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) +- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) + +Перед началом убедитесь, что аккаунт уже владеет хотя бы одним токеном из `nft.examples.testnet`. Если такого токена ещё нет, сначала выпустите его по гайду с предразвёрнутым контрактом, а затем вернитесь к этому сценарию. + +**Что вы делаете** + +- Используете FastNear API, чтобы быстро ответить на вопрос о допуске. +- Расширяетесь до RPC `nft_tokens_for_owner`, чтобы получить точные `token_id` и метаданные из исходной коллекции. +- Строите детерминированные производные метаданные на основе этого набора токенов. +- Выпускаете производный токен и затем подтверждаете его тем же view-методом NFT. + +```bash +API_BASE_URL=https://test.api.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SOURCE_COLLECTION_ID=nft.examples.testnet +DESTINATION_COLLECTION_ID=nft.examples.testnet +SIGNER_ACCOUNT_ID="$ACCOUNT_ID" +TOKEN_ID="derivative-$(date +%s)" +``` + +1. Через FastNear API проверьте, есть ли у аккаунта хоть один NFT из исходной коллекции. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | tee /tmp/testnet-account-nfts.json >/dev/null + +jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + holds_collection: any(.tokens[]?; .contract_id == $source_collection_id), + matching_contracts: [ + .tokens[]? + | select(.contract_id == $source_collection_id) + ] +}' /tmp/testnet-account-nfts.json +``` + +2. Расширьтесь до канонического RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. + +```bash +NFT_TOKENS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + from_index: "0", + limit: 50 + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOURCE_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_tokens_for_owner", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/source-collection-tokens.json >/dev/null + +jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + source_collection_id: $source_collection_id, + source_count: length, + source_token_ids: (map(.token_id) | sort | .[:5]) +}' /tmp/source-collection-tokens.json +``` + +3. Постройте детерминированные производные метаданные из этого набора токенов. + +```bash +DERIVATIVE_METADATA_JSON="$( + jq -c --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + title: ("Derivative witness for " + $source_collection_id), + description: + ("Minted because the holder currently owns " + + (length | tostring) + + " token(s) from " + + $source_collection_id), + media: ( + map(.metadata.media) + | map(select(. != null)) + | .[0] + ), + copies: 1, + extra: ({ + source_collection_id: $source_collection_id, + source_count: length, + source_token_ids: (map(.token_id) | sort | .[:5]) + } | @json) + }' /tmp/source-collection-tokens.json +)" + +printf '%s\n' "$DERIVATIVE_METADATA_JSON" | jq '.' +``` + +4. Выпустите производный токен в целевой коллекции. + +```bash +near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ + --arg token_id "$TOKEN_ID" \ + --arg receiver_id "$ACCOUNT_ID" \ + --argjson metadata "$DERIVATIVE_METADATA_JSON" '{ + token_id: $token_id, + receiver_id: $receiver_id, + metadata: $metadata + }')" \ + --accountId "$SIGNER_ACCOUNT_ID" \ + --deposit 0.1 \ + --networkId testnet +``` + +5. Подтвердите новый токен тем же каноническим NFT view-методом. + +Если сразу после возврата mint-транзакции токен ещё не виден, не считайте это ошибкой сразу же: опросите view-метод несколько раз. + +```bash +for attempt in 1 2 3 4 5; do + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$DESTINATION_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_tokens_for_owner", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | jq --arg token_id "$TOKEN_ID" ' + map(select(.token_id == $token_id)) + ' \ + | tee /tmp/derivative-token-verification.json >/dev/null + + if jq -e 'length > 0' /tmp/derivative-token-verification.json >/dev/null; then + break + fi + + sleep 1 +done + +jq '.' /tmp/derivative-token-verification.json +``` + +**Зачем нужен следующий шаг?** + +FastNear API быстрее всего отвечает на вопрос о допуске. Как только аккаунт проходит условие, RPC становится правильной поверхностью для точной проверки токенов и подтверждения результата, потому что напрямую открывает канонические NFT view-методы коллекции. + +### У меня обычный стейкинг или liquid staking? + +Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». + +**Сеть** + +- mainnet + +**Официальные ссылки** + +- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) +- [Liquid staking](https://docs.near.org/primitives/liquid-staking) + +Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. + +**Что вы делаете** + +- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. +- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. +- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. +- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +``` + +1. Получите представление по прямому стейкингу. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' +``` + +2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` + +3. Классифицируйте аккаунт на основе этих двух индексированных представлений. + +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' +``` + +**Зачем нужен следующий шаг?** + +Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. + ## Частые ошибки - Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index 1dca291..89670f2 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -125,6 +125,64 @@ page_actions: - как выглядит последнее индексированное значение и какие изменения видны в истории - совпал ли `view_state` с текущим индексированным значением +### Shell-сценарий + +Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «какая у этого ключа более широкая индексированная история?» + +**Что вы делаете** + +- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. +- Извлекаете точный `key` через `jq`. +- Переиспользуете этот ключ в `POST /v0/history`, чтобы расшириться до истории. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +EXACT_KEY="$( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | tee /tmp/kv-latest.json \ + | jq -r '.entries[0].key' +)" + +jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' /tmp/kv-latest.json + +curl -s "$KV_BASE_URL/v0/history" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ + | jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + value + } + ] + }' +``` + +**Зачем нужен следующий шаг?** + +Поиск последней записи даёт максимально узкий ответ. Повторное использование точного `key` в `POST /v0/history` показывает, не является ли этот ключ частью более широкой индексированной картины. Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](/fastdata/kv/get-history-key). + ## Частые ошибки - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index 2eb4083..3378a7e 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -126,6 +126,52 @@ page_actions: - когда то же наблюдение стало финализированным - изменил ли канонический разбор через RPC интерпретацию +### Shell-сценарий + +Используйте этот сценарий, когда вспомогательный маршрут должен сам выбирать для вас последний финализированный блок, но следующий шаг всё равно требует канонического подтверждения через RPC. + +**Что вы делаете** + +- Смотрите redirect, который возвращает `GET /v0/last_block/final`. +- Загружаете итоговый документ блока. +- Извлекаете `block.header.height` через `jq`. +- Переиспользуете эту высоту в RPC `block` по высоте. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +RPC_URL=https://rpc.mainnet.fastnear.com + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +printf 'Redirect target: %s\n' "$FINAL_LOCATION" + +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | tee /tmp/neardata-final-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + +BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "block", + params: { + block_id: ($block_height | tonumber) + } + }')" \ + | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +``` + +**Зачем нужен следующий шаг?** + +Вспомогательный маршрут — самый простой путь опроса для сценария «последний финализированный блок». Как только он сообщил точную высоту блока, правильной следующей поверхностью становится RPC, если нужны канонические семантики этого блока без догадок о том, какой блок проверять. + ## Частые ошибки - Воспринимать NEAR Data API как потоковый продукт, а не как поверхность для опроса. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index 7708ea4..be965ec 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -115,6 +115,552 @@ page_actions: - Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. - Нужен уже не единичный статус, а более широкий сценарий расследования. +## Готовые сценарии + +### Проверить и удалить старые function-call-ключи Near Social + +Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. + +**Что вы делаете** + +- Через канонический RPC получаете полный список access key аккаунта. +- Сужаете этот список до function-call-ключей, привязанных к `social.near`. +- Точно проверяете один выбранный ключ перед удалением. +- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. + +Сразу важны два ограничения: + +- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. +- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export SOCIAL_RECEIVER_ID=social.near +export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' +export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' +export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' +``` + +1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/fastnear-access-keys.json >/dev/null + +jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + nonce: .access_key.nonce, + receiver_id: .access_key.permission.FunctionCall.receiver_id, + method_names: .access_key.permission.FunctionCall.method_names, + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } +' /tmp/fastnear-access-keys.json +``` + +Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. + +2. Ещё раз проверьте конкретный ключ перед удалением. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$DELETE_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" + } + }')" \ + | jq '{nonce: .result.nonce, permission: .result.permission}' +``` + +3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + is_function_call: true, + limit: 10 + }')" \ + | jq '{ + account_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_success + } + ] + }' +``` + +Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. + +4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. + +Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. + +```bash +SIGNED_TX_BASE64="$( + node --input-type=module <<'EOF' +import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; + +const { + ACCOUNT_ID, + NETWORK_ID = 'mainnet', + RPC_URL = 'https://rpc.mainnet.fastnear.com', + DELETE_PUBLIC_KEY, + FULL_ACCESS_PUBLIC_KEY, + FULL_ACCESS_PRIVATE_KEY, +} = process.env; + +for (const name of [ + 'ACCOUNT_ID', + 'DELETE_PUBLIC_KEY', + 'FULL_ACCESS_PUBLIC_KEY', + 'FULL_ACCESS_PRIVATE_KEY', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); +const derivedPublicKey = keyPair.getPublicKey().toString(); + +if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { + throw new Error( + `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: ACCOUNT_ID, + public_key: FULL_ACCESS_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const transaction = transactions.createTransaction( + ACCOUNT_ID, + utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), + ACCOUNT_ID, + BigInt(accessKey.nonce) + 1n, + [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +)" +``` + +5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. + +```bash +if curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' + .result.keys[] + | select(.public_key == $public_key) + ' >/dev/null; then + echo "Key is still present: $DELETE_PUBLIC_KEY" +else + echo "Key deleted: $DELETE_PUBLIC_KEY" +fi +``` + +**Зачем нужен следующий шаг?** + +Повторный вызов `view_access_key_list` замыкает сценарий на той же канонической поверхности, с которой вы начинали поиск. Если ключ исчез именно там, дополнительная индексированная сводка уже не нужна, чтобы подтвердить удаление. + +### Проверить регистрацию FT storage и затем перевести токены + +Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». + +**Сеть** + +- testnet + +**Официальные ссылки** + +- [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) +- [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) + +В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. + +**Что вы делаете** + +- Через канонические RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. +- При необходимости получаете минимальный размер storage deposit. +- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. +- Подтверждаете баланс получателя тем же каноническим view-методом контракта. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet +export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' +export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' +export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' +``` + +1. Проверьте, зарегистрирован ли получатель на FT-контракте. + +```bash +STORAGE_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$STORAGE_BALANCE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "storage_balance_of", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/ft-storage-balance.json >/dev/null + +jq '{ + registered: ((.result.result | implode | fromjson) != null), + storage_balance: (.result.result | implode | fromjson) +}' /tmp/ft-storage-balance.json +``` + +2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. + +```bash +MIN_STORAGE_YOCTO="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_CONTRACT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "storage_balance_bounds", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/ft-storage-bounds.json \ + | jq -r '.result.result | implode | fromjson | .min' +)" + +printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" +``` + +3. Определите одну переиспользуемую функцию подписи для function-call к контракту. + +Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. + +```bash +sign_function_call() { + METHOD_NAME="$1" \ + ARGS_JSON="$2" \ + DEPOSIT_YOCTO="$3" \ + GAS_TGAS="$4" \ + node --input-type=module <<'EOF' +import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; + +const { + NETWORK_ID = 'testnet', + RPC_URL = 'https://rpc.testnet.fastnear.com', + TOKEN_CONTRACT_ID, + SENDER_ACCOUNT_ID, + SENDER_PUBLIC_KEY, + SENDER_PRIVATE_KEY, + METHOD_NAME, + ARGS_JSON, + DEPOSIT_YOCTO = '0', + GAS_TGAS = '100', +} = process.env; + +for (const name of [ + 'TOKEN_CONTRACT_ID', + 'SENDER_ACCOUNT_ID', + 'SENDER_PUBLIC_KEY', + 'SENDER_PRIVATE_KEY', + 'METHOD_NAME', + 'ARGS_JSON', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); +const signer = await InMemorySigner.fromKeyPair( + NETWORK_ID, + SENDER_ACCOUNT_ID, + keyPair +); + +const derivedPublicKey = keyPair.getPublicKey().toString(); +if (derivedPublicKey !== SENDER_PUBLIC_KEY) { + throw new Error( + `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: SENDER_ACCOUNT_ID, + public_key: SENDER_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const action = transactions.functionCall( + METHOD_NAME, + Buffer.from(ARGS_JSON), + BigInt(GAS_TGAS) * 10n ** 12n, + BigInt(DEPOSIT_YOCTO) +); + +const transaction = transactions.createTransaction( + SENDER_ACCOUNT_ID, + utils.PublicKey.fromString(SENDER_PUBLIC_KEY), + TOKEN_CONTRACT_ID, + BigInt(accessKey.nonce) + 1n, + [action], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + SENDER_ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +} +``` + +4. При необходимости сначала зарегистрируйте storage для получателя. + +```bash +if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then + SIGNED_TX_BASE64="$( + sign_function_call \ + storage_deposit \ + "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id, + registration_only: true + }')" \ + "$MIN_STORAGE_YOCTO" \ + 100 + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash + }' +fi +``` + +5. После готовности storage переведите FT. + +```bash +SIGNED_TX_BASE64="$( + sign_function_call \ + ft_transfer \ + "$(jq -nc \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ + receiver_id: $receiver_id, + amount: $amount, + memo: "FastNear RPC example" + }')" \ + 1 \ + 100 +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Подтвердите FT-баланс получателя каноническим view-методом контракта. + +```bash +RECEIVER_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_balance_of", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + receiver_balance: (.result.result | implode | fromjson) + }' +``` + +**Зачем нужен следующий шаг?** + +Это канонический RPC-сценарий, потому что каждый шаг остаётся на точном состоянии контракта и точной семантике отправки транзакций: сначала вы доказываете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. + ## Частые ошибки - Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx index cfabb52..47a5f34 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx @@ -127,6 +127,34 @@ page_actions: - куда на диске должны попасть данные - должен ли оператор оставаться в FastNear snapshot docs или переходить к более широкой документации nearcore +### Shell-сценарий + +Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. + +**Что вы делаете** + +- Один раз получаете последнюю высоту архивного снапшота mainnet. +- Сохраняете её в `LATEST`. +- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. + +```bash +HOT_DATA_PATH=~/.near/data +COLD_DATA_PATH=/mnt/hdds/cold-data + +LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" +echo "Latest archival mainnet snapshot block: $LATEST" + +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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash +``` + +**Зачем нужен следующий шаг?** + +Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. + ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md index 3b6017f..f6c3747 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -85,6 +85,73 @@ page_actions: - Пользователю прямо нужны квитанции или каноническое подтверждение через RPC. Сначала переходите к [Transactions API](/tx), затем к [RPC Reference](/rpc), если потребуется. +## Готовый сценарий + +### Запросить узкое окно переводов, а затем перейти по receipt + +Используйте этот сценарий, когда первый вопрос всё ещё касается только переводов, но вы уже понимаете, что потом может понадобиться одна точная точка перехода в контекст исполнения. + +**Что вы делаете** + +- Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. +- Извлекаете первый `receipt_id` через `jq`. +- Переиспользуете этот receipt ID в Transactions API, чтобы перейти от движения актива к контексту исполнения. + +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +FROM_TIMESTAMP_MS=1711929600000 +TO_TIMESTAMP_MS=1712016000000 + +RECEIPT_ID="$( + curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | tee /tmp/transfers-window.json \ + | jq -r '.transfers[0].receipt_id' +)" + +jq '{ + resume_token, + transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + asset_id, + amount, + other_account_id, + block_height + } + ] +}' /tmp/transfers-window.json + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' +``` + +**Зачем нужен следующий шаг?** + +Запрос переводов позволяет держать первый проход узким и удобным для пагинации. Переход по `receipt_id` даёт одну точную опорную точку в исполнении, не заставляя сразу расширяться до полной истории аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. + ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index 79168e3..fb97ff8 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -164,6 +164,224 @@ page_actions: - какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить - был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым +### Понять двухстороннее сопоставление `token_diff`, а затем проследить живой расчёт NEAR Intents + +Используйте это расследование, когда история звучит так: «покажи, что именно NEAR Intents делает под капотом, но привяжи разбор к публичным данным, которые можно проверить самостоятельно». + +**Цель** + +- Сначала объяснить модель сопоставления, а затем превратить один реальный расчёт через `intents.near` в читаемую историю исполнения на базе Transactions API и канонического RPC. + +**Официальные ссылки** + +- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) +- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) +- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) + +#### Часть 1: анатомия протокола + +Базовая форма сопоставления здесь — это `token_diff` intent. Одна сторона объявляет, какие активы она готова отдать и получить, а вторая сторона объявляет противоположную разницу. В официальной документации verifier двухсторонний обмен USDC и USDT показан как один подписанный intent со смыслом «я отдам `-10` USDC и получу `+10` USDT» и второй intent, который описывает обратную сторону сделки. Такие подписанные intent можно собрать через Message Bus или через любой другой внешний канал координации и затем отправить вместе в `intents.near`. + +Эта концептуальная часть полезна, чтобы понять сам протокол, но подписанные примеры в официальной документации носят иллюстративный и привязанный ко времени характер. Для рабочего FastNear-сценария полезнее разбирать один реальный расчёт из mainnet, чем делать вид, будто пример из документации является готовой живой транзакцией. + +#### Часть 2: живая FastNear-трассировка + +Для живой трассировки ниже используйте этот фиксированный якорь расчёта, зафиксированный **18 апреля 2026 года**: + +- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` +- аккаунт `signer` и `receiver`: `intents.near` +- высота включающего блока: `194573310` + +Публичных FastNear-поверхностей уже достаточно, чтобы восстановить многое: + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Якорь расчёта | Transactions API [`POST /v0/transactions`](/tx/transactions) | Начинаем с фиксированного хеша транзакции и получаем саму транзакцию плюс список последующих receipt | Даёт читаемый каркас расчёта без необходимости сразу декодировать сырые receipt | +| Контекст включающего блока | Transactions API [`POST /v0/block`](/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Помещает расчёт в контекст блока и показывает, какие receipt появились там | +| Канонический DAG по receipt | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` | Даёт протокольно-канонический DAG, `executor_id` и сырые логи событий | +| Классификация событий | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Извлекаем имена событий вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` из строк `EVENT_JSON` | Позволяет объяснять расчёт по семействам событий, а не по непрозрачным `receipt_id` | + +**Что должен включать полезный ответ** + +- как концептуальная двухсторонняя модель `token_diff` отображается на реальный расчёт через `execute_intents` +- какие последующие контракты и методы появились после `intents.near` +- какие семейства событий выпустила трассировка +- какие высоты блоков сформировали основной каскад + +Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. + +### Shell-сценарий для живой трассировки NEAR Intents + +Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. + +**Что вы делаете** + +- Получаете историю транзакции через Transactions API. +- Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. +- Подтверждаете канонический DAG по receipt и семейства логов событий через `EXPERIMENTAL_tx_status`. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 +INTENTS_SIGNER_ID=intents.near +``` + +1. Начните с самой транзакции расчёта. + +```bash +INTENTS_BLOCK_HASH="$( + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$INTENTS_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/intents-transaction.json \ + | jq -r '.transactions[0].execution_outcome.block_hash' +)" + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + receipt_flow: [ + .transactions[0].receipts[:6][] + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + block_height: .execution_outcome.block_height, + methods: ( + [.receipt.receipt.Action.actions[]?.FunctionCall.method_name] + | map(select(. != null)) + ), + first_log: (.execution_outcome.outcome.logs[0] // null) + } + ] +}' /tmp/intents-transaction.json +``` + +2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. + +```bash +curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_id "$INTENTS_BLOCK_HASH" '{ + block_id: $block_id, + with_receipts: true, + with_transactions: false + }')" \ + | tee /tmp/intents-block.json >/dev/null + +jq --arg tx_hash "$INTENTS_TX_HASH" '{ + block_height: .block.block_height, + block_hash: .block.block_hash, + tx_receipts: [ + .block_receipts[] + | select(.transaction_hash == $tx_hash) + | { + receipt_id, + predecessor_id, + receiver_id, + block_height + } + ] +}' /tmp/intents-block.json +``` + +3. Подтвердите канонический DAG по receipt и извлеките семейства событий через RPC. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$INTENTS_TX_HASH" \ + --arg sender_account_id "$INTENTS_SIGNER_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $sender_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/intents-rpc.json >/dev/null + +jq '{ + final_execution_status: .result.final_execution_status, + receipts_outcome: [ + .result.receipts_outcome[:6][] + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + first_log: (.outcome.logs[0] // null) + } + ] +}' /tmp/intents-rpc.json + +jq -r ' + .result.receipts_outcome[] + | .outcome.logs[] + | select(startswith("EVENT_JSON:")) + | capture("event\":\"(?[^\"]+)\"").event +' /tmp/intents-rpc.json | sort -u +``` + +**Зачем нужен следующий шаг?** + +`POST /v0/transactions` даёт читаемый каркас расчёта. `POST /v0/block` показывает, как этот расчёт расположен внутри включающего блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и сырые логи событий, а не только индексированное резюме. + +### Shell-сценарий для pivot по receipt + +Используйте этот сценарий, когда у вас уже есть один `receipt_id` и нужен самый короткий путь обратно к читаемой истории транзакции. + +**Что вы делаете** + +- Сначала разрешаете receipt. +- Извлекаете `receipt.transaction_hash` через `jq`. +- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RECEIPT_ID=YOUR_RECEIPT_ID +# Пример receipt ID из недавнего mainnet-перевода: +# RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' + +TX_HASH="$( + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | tee /tmp/receipt-lookup.json \ + | jq -r '.receipt.transaction_hash' +)" + +jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash, + tx_block_height: .receipt.tx_block_height + } +}' /tmp/receipt-lookup.json + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + receipt_count: (.transactions[0].receipts | length) + }' +``` + +**Зачем нужен следующий шаг?** + +`POST /v0/receipt` даёт точку перехода. `POST /v0/transactions` превращает эту точку в читаемую историю с контекстом по отправителю, получателю, блоку и связанным receipt-ам. И только после этого обычно стоит расширяться до окон по блоку или аккаунту. + ## Частые ошибки - Пытаться отправлять транзакцию через history API вместо сырого RPC. diff --git a/sidebars.js b/sidebars.js index c5924f2..357b957 100644 --- a/sidebars.js +++ b/sidebars.js @@ -41,167 +41,198 @@ const nearcoreReleaseSidebarItems = ] : []; -const rpcSidebar = [ - ...nearcoreReleaseSidebarItems, - 'rpc/index', - 'rpc/examples', - 'auth/index', - { - type: 'category', - label: 'Account', - items: [ - 'rpc/account/view-account', - 'rpc/account/view-access-key', - 'rpc/account/view-access-key-list', - ], - }, - { - type: 'category', - label: 'Block', - items: [ - 'rpc/block/block-by-height', - 'rpc/block/block-by-id', - 'rpc/block/block-effects', - ], - }, - { - type: 'category', - label: 'Contract', - items: [ - 'rpc/contract/call-function', - 'rpc/contract/view-code', - 'rpc/contract/view-state', - 'rpc/contract/view-global-contract-code', - 'rpc/contract/view-global-contract-code-by-account-id', - ], - }, - { - type: 'category', - label: 'Protocol', - items: [ - 'rpc/protocol/status', - 'rpc/protocol/health', - 'rpc/protocol/gas-price', - 'rpc/protocol/gas-price-by-block', - 'rpc/protocol/genesis-config', - 'rpc/protocol/changes', - 'rpc/protocol/chunk-by-hash', - 'rpc/protocol/chunk-by-block-shard', - 'rpc/protocol/client-config', - 'rpc/protocol/network-info', - 'rpc/protocol/latest-block', - 'rpc/protocol/metrics', - 'rpc/protocol/maintenance-windows', - 'rpc/protocol/light-client-proof', - 'rpc/protocol/next-light-client-block', - 'rpc/protocol/experimental-protocol-config', - 'rpc/protocol/experimental-congestion-level', - 'rpc/protocol/experimental-light-client-block-proof', - 'rpc/protocol/experimental-light-client-proof', - 'rpc/protocol/experimental-split-storage-info', - ], - }, - { - type: 'category', - label: 'Transaction', - items: [ - 'rpc/transaction/tx-status', - 'rpc/transaction/send-tx', - 'rpc/transaction/broadcast-tx-async', - 'rpc/transaction/broadcast-tx-commit', - 'rpc/transaction/experimental-receipt', - 'rpc/transaction/experimental-tx-status', - ], - }, - { - type: 'category', - label: 'Validators', - items: [ - 'rpc/validators/validators-current', - 'rpc/validators/validators-by-epoch', - 'rpc/validators/experimental-validators-ordered', - ], - }, -]; - -const fastnearApiSidebar = [ - 'api/index', - 'api/examples', - { - type: 'category', - label: 'V0', - collapsible: false, - collapsed: false, - items: [ - 'api/v0-public-key', - 'api/v0-public-key-all', - 'api/v0-account-staking', - 'api/v0-account-ft', - 'api/v0-account-nft', - ], - }, - { - type: 'category', - label: 'V1', - collapsible: false, - collapsed: false, - items: [ - 'api/v1-public-key', - 'api/v1-public-key-all', - 'api/v1-account-staking', - 'api/v1-account-ft', - 'api/v1-account-nft', - 'api/v1-ft-top', - 'api/v1-account-full', - ], - }, - 'api/status', - 'api/health', -]; - -const transactionsApiSidebar = [ - 'tx/index', - 'tx/examples', - 'tx/transactions', - 'tx/account', - 'tx/block', - 'tx/blocks', - 'tx/receipt', -]; +function makeExamplesDivider() { + return { + type: 'html', + value: '', + defaultStyle: false, + className: 'fastnear-sidebar-examples-divider', + }; +} + +function makeExamplesDoc(id) { + return { + type: 'doc', + id, + className: 'fastnear-sidebar-examples-link', + }; +} + +function withExamplesFooter(items, examplesId) { + return [...items, makeExamplesDivider(), makeExamplesDoc(examplesId)]; +} + +const rpcSidebar = withExamplesFooter( + [ + ...nearcoreReleaseSidebarItems, + 'rpc/index', + 'auth/index', + { + type: 'category', + label: 'Account', + items: [ + 'rpc/account/view-account', + 'rpc/account/view-access-key', + 'rpc/account/view-access-key-list', + ], + }, + { + type: 'category', + label: 'Block', + items: [ + 'rpc/block/block-by-height', + 'rpc/block/block-by-id', + 'rpc/block/block-effects', + ], + }, + { + type: 'category', + label: 'Contract', + items: [ + 'rpc/contract/call-function', + 'rpc/contract/view-code', + 'rpc/contract/view-state', + 'rpc/contract/view-global-contract-code', + 'rpc/contract/view-global-contract-code-by-account-id', + ], + }, + { + type: 'category', + label: 'Protocol', + items: [ + 'rpc/protocol/status', + 'rpc/protocol/health', + 'rpc/protocol/gas-price', + 'rpc/protocol/gas-price-by-block', + 'rpc/protocol/genesis-config', + 'rpc/protocol/changes', + 'rpc/protocol/chunk-by-hash', + 'rpc/protocol/chunk-by-block-shard', + 'rpc/protocol/client-config', + 'rpc/protocol/network-info', + 'rpc/protocol/latest-block', + 'rpc/protocol/metrics', + 'rpc/protocol/maintenance-windows', + 'rpc/protocol/light-client-proof', + 'rpc/protocol/next-light-client-block', + 'rpc/protocol/experimental-protocol-config', + 'rpc/protocol/experimental-congestion-level', + 'rpc/protocol/experimental-light-client-block-proof', + 'rpc/protocol/experimental-light-client-proof', + 'rpc/protocol/experimental-split-storage-info', + ], + }, + { + type: 'category', + label: 'Transaction', + items: [ + 'rpc/transaction/tx-status', + 'rpc/transaction/send-tx', + 'rpc/transaction/broadcast-tx-async', + 'rpc/transaction/broadcast-tx-commit', + 'rpc/transaction/experimental-receipt', + 'rpc/transaction/experimental-tx-status', + ], + }, + { + type: 'category', + label: 'Validators', + items: [ + 'rpc/validators/validators-current', + 'rpc/validators/validators-by-epoch', + 'rpc/validators/experimental-validators-ordered', + ], + }, + ], + 'rpc/examples' +); + +const fastnearApiSidebar = withExamplesFooter( + [ + 'api/index', + { + type: 'category', + label: 'V0', + collapsible: false, + collapsed: false, + items: [ + 'api/v0-public-key', + 'api/v0-public-key-all', + 'api/v0-account-staking', + 'api/v0-account-ft', + 'api/v0-account-nft', + ], + }, + { + type: 'category', + label: 'V1', + collapsible: false, + collapsed: false, + items: [ + 'api/v1-public-key', + 'api/v1-public-key-all', + 'api/v1-account-staking', + 'api/v1-account-ft', + 'api/v1-account-nft', + 'api/v1-ft-top', + 'api/v1-account-full', + ], + }, + 'api/status', + 'api/health', + ], + 'api/examples' +); + +const transactionsApiSidebar = withExamplesFooter( + [ + 'tx/index', + 'tx/transactions', + 'tx/account', + 'tx/block', + 'tx/blocks', + 'tx/receipt', + ], + 'tx/examples' +); const transfersApiSidebar = hideEarlyApiFamilies ? [] - : ['transfers/index', 'transfers/examples', 'transfers/transfers']; - -const nearDataApiSidebar = [ - 'neardata/index', - 'neardata/examples', - 'neardata/first-block', - 'neardata/block', - 'neardata/block-headers', - 'neardata/block-chunk', - 'neardata/block-shard', - 'neardata/block-optimistic', - 'neardata/last-block-final', - 'neardata/last-block-optimistic', - 'neardata/health', -]; + : withExamplesFooter(['transfers/index', 'transfers/transfers'], 'transfers/examples'); + +const nearDataApiSidebar = withExamplesFooter( + [ + 'neardata/index', + 'neardata/first-block', + 'neardata/block', + 'neardata/block-headers', + 'neardata/block-chunk', + 'neardata/block-shard', + 'neardata/block-optimistic', + 'neardata/last-block-final', + 'neardata/last-block-optimistic', + 'neardata/health', + ], + 'neardata/examples' +); const kvFastDataSidebar = hideEarlyApiFamilies ? [] - : [ - 'fastdata/kv/index', - 'fastdata/kv/examples', - 'fastdata/kv/all-by-predecessor', - 'fastdata/kv/history-by-predecessor', - 'fastdata/kv/latest-by-predecessor', - 'fastdata/kv/history-by-account', - 'fastdata/kv/latest-by-account', - 'fastdata/kv/history-by-key', - 'fastdata/kv/multi', - 'fastdata/kv/get-history-key', - 'fastdata/kv/get-latest-key', - ]; + : withExamplesFooter( + [ + 'fastdata/kv/index', + 'fastdata/kv/all-by-predecessor', + 'fastdata/kv/history-by-predecessor', + 'fastdata/kv/latest-by-predecessor', + 'fastdata/kv/history-by-account', + 'fastdata/kv/latest-by-account', + 'fastdata/kv/history-by-key', + 'fastdata/kv/multi', + 'fastdata/kv/get-history-key', + 'fastdata/kv/get-latest-key', + ], + 'fastdata/kv/examples' + ); const sidebars = { rpcSidebar, @@ -211,7 +242,13 @@ const sidebars = { nearDataApiSidebar, kvFastDataSidebar, transactionFlowSidebar: [{ type: 'autogenerated', dirName: 'transaction-flow' }], - snapshotsSidebars: [{ type: 'autogenerated', dirName: 'snapshots' }], + snapshotsSidebars: [ + 'snapshots/index', + 'snapshots/mainnet', + 'snapshots/testnet', + makeExamplesDivider(), + makeExamplesDoc('snapshots/examples'), + ], }; export default sidebars; diff --git a/src/css/custom.css b/src/css/custom.css index 1b65d21..c86b5b2 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -477,6 +477,30 @@ pre code { background: rgba(243, 245, 255, 0.12); } +.theme-doc-sidebar-item-link.fastnear-sidebar-examples-divider, +.navbar-sidebar .fastnear-sidebar-examples-divider { + list-style: none; + margin: 1rem 0 0; + padding: 0; + pointer-events: none; +} + +.theme-doc-sidebar-item-link.fastnear-sidebar-examples-divider > div, +.navbar-sidebar .fastnear-sidebar-examples-divider > div { + border-top: 1px solid rgba(17, 17, 17, 0.08); + margin: 0 1rem; +} + +.theme-doc-sidebar-item-link.fastnear-sidebar-examples-link, +.navbar-sidebar .fastnear-sidebar-examples-link { + margin-top: 0.35rem; +} + +[data-theme='dark'] .theme-doc-sidebar-item-link.fastnear-sidebar-examples-divider > div, +[data-theme='dark'] .navbar-sidebar .fastnear-sidebar-examples-divider > div { + border-top-color: rgba(243, 245, 255, 0.12); +} + /* Swap toggle icons: show destination mode instead of current mode */ [data-theme-choice='light'] [class*='lightToggleIcon'] { display: none !important; diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index f3cccc5..2a1441d 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -82,6 +82,294 @@ - Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). - Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). +## Готовые сценарии + +### Определить аккаунт по публичному ключу, а затем получить сводку по нему + +Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» + +**Что вы делаете** + +- Ищете по публичному ключу один или несколько `account_id`. +- Извлекаете первый найденный `account_id` через `jq`. +- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. + +```bash +API_BASE_URL=https://api.fastnear.com +PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' +# Пример публичного ключа из модели страницы в документации: +# PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' + +ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" + +ACCOUNT_ID="$( + curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ + | tee /tmp/fastnear-public-key.json \ + | jq -r '.account_ids[0]' +)" + +jq '{account_ids}' /tmp/fastnear-public-key.json + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +``` + +**Зачем нужен следующий шаг?** + +Поиск по публичному ключу отвечает на вопрос об идентификации. Полный снимок аккаунта отвечает на следующий прикладной вопрос уже в продуктовой форме. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, расширяйтесь до [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. + +### Проверить владение коллекцией, а затем выпустить производный NFT + +Используйте этот сценарий, когда история звучит так: «если аккаунт уже владеет хотя бы одним NFT из коллекции X, выпустить ещё один NFT, в чьих метаданных будет зафиксирована эта связь». + +**Сеть** + +- testnet + +**Официальные ссылки** + +- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) +- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) + +Перед началом убедитесь, что аккаунт уже владеет хотя бы одним токеном из `nft.examples.testnet`. Если такого токена ещё нет, сначала выпустите его по гайду с предразвёрнутым контрактом, а затем вернитесь к этому сценарию. + +**Что вы делаете** + +- Используете FastNear API, чтобы быстро ответить на вопрос о допуске. +- Расширяетесь до RPC `nft_tokens_for_owner`, чтобы получить точные `token_id` и метаданные из исходной коллекции. +- Строите детерминированные производные метаданные на основе этого набора токенов. +- Выпускаете производный токен и затем подтверждаете его тем же view-методом NFT. + +```bash +API_BASE_URL=https://test.api.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SOURCE_COLLECTION_ID=nft.examples.testnet +DESTINATION_COLLECTION_ID=nft.examples.testnet +SIGNER_ACCOUNT_ID="$ACCOUNT_ID" +TOKEN_ID="derivative-$(date +%s)" +``` + +1. Через FastNear API проверьте, есть ли у аккаунта хоть один NFT из исходной коллекции. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | tee /tmp/testnet-account-nfts.json >/dev/null + +jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + holds_collection: any(.tokens[]?; .contract_id == $source_collection_id), + matching_contracts: [ + .tokens[]? + | select(.contract_id == $source_collection_id) + ] +}' /tmp/testnet-account-nfts.json +``` + +2. Расширьтесь до канонического RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. + +```bash +NFT_TOKENS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + from_index: "0", + limit: 50 + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOURCE_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_tokens_for_owner", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/source-collection-tokens.json >/dev/null + +jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + source_collection_id: $source_collection_id, + source_count: length, + source_token_ids: (map(.token_id) | sort | .[:5]) +}' /tmp/source-collection-tokens.json +``` + +3. Постройте детерминированные производные метаданные из этого набора токенов. + +```bash +DERIVATIVE_METADATA_JSON="$( + jq -c --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + title: ("Derivative witness for " + $source_collection_id), + description: + ("Minted because the holder currently owns " + + (length | tostring) + + " token(s) from " + + $source_collection_id), + media: ( + map(.metadata.media) + | map(select(. != null)) + | .[0] + ), + copies: 1, + extra: ({ + source_collection_id: $source_collection_id, + source_count: length, + source_token_ids: (map(.token_id) | sort | .[:5]) + } | @json) + }' /tmp/source-collection-tokens.json +)" + +printf '%s\n' "$DERIVATIVE_METADATA_JSON" | jq '.' +``` + +4. Выпустите производный токен в целевой коллекции. + +```bash +near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ + --arg token_id "$TOKEN_ID" \ + --arg receiver_id "$ACCOUNT_ID" \ + --argjson metadata "$DERIVATIVE_METADATA_JSON" '{ + token_id: $token_id, + receiver_id: $receiver_id, + metadata: $metadata + }')" \ + --accountId "$SIGNER_ACCOUNT_ID" \ + --deposit 0.1 \ + --networkId testnet +``` + +5. Подтвердите новый токен тем же каноническим NFT view-методом. + +Если сразу после возврата mint-транзакции токен ещё не виден, не считайте это ошибкой сразу же: опросите view-метод несколько раз. + +```bash +for attempt in 1 2 3 4 5; do + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$DESTINATION_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_tokens_for_owner", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | jq --arg token_id "$TOKEN_ID" ' + map(select(.token_id == $token_id)) + ' \ + | tee /tmp/derivative-token-verification.json >/dev/null + + if jq -e 'length > 0' /tmp/derivative-token-verification.json >/dev/null; then + break + fi + + sleep 1 +done + +jq '.' /tmp/derivative-token-verification.json +``` + +**Зачем нужен следующий шаг?** + +FastNear API быстрее всего отвечает на вопрос о допуске. Как только аккаунт проходит условие, RPC становится правильной поверхностью для точной проверки токенов и подтверждения результата, потому что напрямую открывает канонические NFT view-методы коллекции. + +### У меня обычный стейкинг или liquid staking? + +Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». + +**Сеть** + +- mainnet + +**Официальные ссылки** + +- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) +- [Liquid staking](https://docs.near.org/primitives/liquid-staking) + +Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. + +**Что вы делаете** + +- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. +- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. +- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. +- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +``` + +1. Получите представление по прямому стейкингу. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' +``` + +2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` + +3. Классифицируйте аккаунт на основе этих двух индексированных представлений. + +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' +``` + +**Зачем нужен следующий шаг?** + +Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. + ## Частые ошибки - Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index f3cccc5..2a1441d 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -82,6 +82,294 @@ - Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). - Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). +## Готовые сценарии + +### Определить аккаунт по публичному ключу, а затем получить сводку по нему + +Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» + +**Что вы делаете** + +- Ищете по публичному ключу один или несколько `account_id`. +- Извлекаете первый найденный `account_id` через `jq`. +- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. + +```bash +API_BASE_URL=https://api.fastnear.com +PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' +# Пример публичного ключа из модели страницы в документации: +# PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' + +ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" + +ACCOUNT_ID="$( + curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ + | tee /tmp/fastnear-public-key.json \ + | jq -r '.account_ids[0]' +)" + +jq '{account_ids}' /tmp/fastnear-public-key.json + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +``` + +**Зачем нужен следующий шаг?** + +Поиск по публичному ключу отвечает на вопрос об идентификации. Полный снимок аккаунта отвечает на следующий прикладной вопрос уже в продуктовой форме. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, расширяйтесь до [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. + +### Проверить владение коллекцией, а затем выпустить производный NFT + +Используйте этот сценарий, когда история звучит так: «если аккаунт уже владеет хотя бы одним NFT из коллекции X, выпустить ещё один NFT, в чьих метаданных будет зафиксирована эта связь». + +**Сеть** + +- testnet + +**Официальные ссылки** + +- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) +- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) + +Перед началом убедитесь, что аккаунт уже владеет хотя бы одним токеном из `nft.examples.testnet`. Если такого токена ещё нет, сначала выпустите его по гайду с предразвёрнутым контрактом, а затем вернитесь к этому сценарию. + +**Что вы делаете** + +- Используете FastNear API, чтобы быстро ответить на вопрос о допуске. +- Расширяетесь до RPC `nft_tokens_for_owner`, чтобы получить точные `token_id` и метаданные из исходной коллекции. +- Строите детерминированные производные метаданные на основе этого набора токенов. +- Выпускаете производный токен и затем подтверждаете его тем же view-методом NFT. + +```bash +API_BASE_URL=https://test.api.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SOURCE_COLLECTION_ID=nft.examples.testnet +DESTINATION_COLLECTION_ID=nft.examples.testnet +SIGNER_ACCOUNT_ID="$ACCOUNT_ID" +TOKEN_ID="derivative-$(date +%s)" +``` + +1. Через FastNear API проверьте, есть ли у аккаунта хоть один NFT из исходной коллекции. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | tee /tmp/testnet-account-nfts.json >/dev/null + +jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + holds_collection: any(.tokens[]?; .contract_id == $source_collection_id), + matching_contracts: [ + .tokens[]? + | select(.contract_id == $source_collection_id) + ] +}' /tmp/testnet-account-nfts.json +``` + +2. Расширьтесь до канонического RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. + +```bash +NFT_TOKENS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + from_index: "0", + limit: 50 + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOURCE_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_tokens_for_owner", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/source-collection-tokens.json >/dev/null + +jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + source_collection_id: $source_collection_id, + source_count: length, + source_token_ids: (map(.token_id) | sort | .[:5]) +}' /tmp/source-collection-tokens.json +``` + +3. Постройте детерминированные производные метаданные из этого набора токенов. + +```bash +DERIVATIVE_METADATA_JSON="$( + jq -c --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + title: ("Derivative witness for " + $source_collection_id), + description: + ("Minted because the holder currently owns " + + (length | tostring) + + " token(s) from " + + $source_collection_id), + media: ( + map(.metadata.media) + | map(select(. != null)) + | .[0] + ), + copies: 1, + extra: ({ + source_collection_id: $source_collection_id, + source_count: length, + source_token_ids: (map(.token_id) | sort | .[:5]) + } | @json) + }' /tmp/source-collection-tokens.json +)" + +printf '%s\n' "$DERIVATIVE_METADATA_JSON" | jq '.' +``` + +4. Выпустите производный токен в целевой коллекции. + +```bash +near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ + --arg token_id "$TOKEN_ID" \ + --arg receiver_id "$ACCOUNT_ID" \ + --argjson metadata "$DERIVATIVE_METADATA_JSON" '{ + token_id: $token_id, + receiver_id: $receiver_id, + metadata: $metadata + }')" \ + --accountId "$SIGNER_ACCOUNT_ID" \ + --deposit 0.1 \ + --networkId testnet +``` + +5. Подтвердите новый токен тем же каноническим NFT view-методом. + +Если сразу после возврата mint-транзакции токен ещё не виден, не считайте это ошибкой сразу же: опросите view-метод несколько раз. + +```bash +for attempt in 1 2 3 4 5; do + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$DESTINATION_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_tokens_for_owner", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | jq --arg token_id "$TOKEN_ID" ' + map(select(.token_id == $token_id)) + ' \ + | tee /tmp/derivative-token-verification.json >/dev/null + + if jq -e 'length > 0' /tmp/derivative-token-verification.json >/dev/null; then + break + fi + + sleep 1 +done + +jq '.' /tmp/derivative-token-verification.json +``` + +**Зачем нужен следующий шаг?** + +FastNear API быстрее всего отвечает на вопрос о допуске. Как только аккаунт проходит условие, RPC становится правильной поверхностью для точной проверки токенов и подтверждения результата, потому что напрямую открывает канонические NFT view-методы коллекции. + +### У меня обычный стейкинг или liquid staking? + +Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». + +**Сеть** + +- mainnet + +**Официальные ссылки** + +- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) +- [Liquid staking](https://docs.near.org/primitives/liquid-staking) + +Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. + +**Что вы делаете** + +- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. +- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. +- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. +- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +``` + +1. Получите представление по прямому стейкингу. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' +``` + +2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` + +3. Классифицируйте аккаунт на основе этих двух индексированных представлений. + +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' +``` + +**Зачем нужен следующий шаг?** + +Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. + ## Частые ошибки - Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index 95c15da..9a7427c 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -117,6 +117,64 @@ - как выглядит последнее индексированное значение и какие изменения видны в истории - совпал ли `view_state` с текущим индексированным значением +### Shell-сценарий + +Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «какая у этого ключа более широкая индексированная история?» + +**Что вы делаете** + +- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. +- Извлекаете точный `key` через `jq`. +- Переиспользуете этот ключ в `POST /v0/history`, чтобы расшириться до истории. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +EXACT_KEY="$( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | tee /tmp/kv-latest.json \ + | jq -r '.entries[0].key' +)" + +jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' /tmp/kv-latest.json + +curl -s "$KV_BASE_URL/v0/history" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ + | jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + value + } + ] + }' +``` + +**Зачем нужен следующий шаг?** + +Поиск последней записи даёт максимально узкий ответ. Повторное использование точного `key` в `POST /v0/history` показывает, не является ли этот ключ частью более широкой индексированной картины. Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). + ## Частые ошибки - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index 95c15da..9a7427c 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -117,6 +117,64 @@ - как выглядит последнее индексированное значение и какие изменения видны в истории - совпал ли `view_state` с текущим индексированным значением +### Shell-сценарий + +Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «какая у этого ключа более широкая индексированная история?» + +**Что вы делаете** + +- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. +- Извлекаете точный `key` через `jq`. +- Переиспользуете этот ключ в `POST /v0/history`, чтобы расшириться до истории. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +EXACT_KEY="$( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | tee /tmp/kv-latest.json \ + | jq -r '.entries[0].key' +)" + +jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' /tmp/kv-latest.json + +curl -s "$KV_BASE_URL/v0/history" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ + | jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + value + } + ] + }' +``` + +**Зачем нужен следующий шаг?** + +Поиск последней записи даёт максимально узкий ответ. Повторное использование точного `key` в `POST /v0/history` показывает, не является ли этот ключ частью более широкой индексированной картины. Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). + ## Частые ошибки - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 037053b..b4e3d61 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -1076,6 +1076,294 @@ https://test.api.fastnear.com - Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). - Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). +## Готовые сценарии + +### Определить аккаунт по публичному ключу, а затем получить сводку по нему + +Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» + +**Что вы делаете** + +- Ищете по публичному ключу один или несколько `account_id`. +- Извлекаете первый найденный `account_id` через `jq`. +- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. + +```bash +API_BASE_URL=https://api.fastnear.com +PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' +# Пример публичного ключа из модели страницы в документации: +# PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' + +ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" + +ACCOUNT_ID="$( + curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ + | tee /tmp/fastnear-public-key.json \ + | jq -r '.account_ids[0]' +)" + +jq '{account_ids}' /tmp/fastnear-public-key.json + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +``` + +**Зачем нужен следующий шаг?** + +Поиск по публичному ключу отвечает на вопрос об идентификации. Полный снимок аккаунта отвечает на следующий прикладной вопрос уже в продуктовой форме. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, расширяйтесь до [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. + +### Проверить владение коллекцией, а затем выпустить производный NFT + +Используйте этот сценарий, когда история звучит так: «если аккаунт уже владеет хотя бы одним NFT из коллекции X, выпустить ещё один NFT, в чьих метаданных будет зафиксирована эта связь». + +**Сеть** + +- testnet + +**Официальные ссылки** + +- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) +- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) + +Перед началом убедитесь, что аккаунт уже владеет хотя бы одним токеном из `nft.examples.testnet`. Если такого токена ещё нет, сначала выпустите его по гайду с предразвёрнутым контрактом, а затем вернитесь к этому сценарию. + +**Что вы делаете** + +- Используете FastNear API, чтобы быстро ответить на вопрос о допуске. +- Расширяетесь до RPC `nft_tokens_for_owner`, чтобы получить точные `token_id` и метаданные из исходной коллекции. +- Строите детерминированные производные метаданные на основе этого набора токенов. +- Выпускаете производный токен и затем подтверждаете его тем же view-методом NFT. + +```bash +API_BASE_URL=https://test.api.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SOURCE_COLLECTION_ID=nft.examples.testnet +DESTINATION_COLLECTION_ID=nft.examples.testnet +SIGNER_ACCOUNT_ID="$ACCOUNT_ID" +TOKEN_ID="derivative-$(date +%s)" +``` + +1. Через FastNear API проверьте, есть ли у аккаунта хоть один NFT из исходной коллекции. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | tee /tmp/testnet-account-nfts.json >/dev/null + +jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + holds_collection: any(.tokens[]?; .contract_id == $source_collection_id), + matching_contracts: [ + .tokens[]? + | select(.contract_id == $source_collection_id) + ] +}' /tmp/testnet-account-nfts.json +``` + +2. Расширьтесь до канонического RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. + +```bash +NFT_TOKENS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + from_index: "0", + limit: 50 + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOURCE_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_tokens_for_owner", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/source-collection-tokens.json >/dev/null + +jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + source_collection_id: $source_collection_id, + source_count: length, + source_token_ids: (map(.token_id) | sort | .[:5]) +}' /tmp/source-collection-tokens.json +``` + +3. Постройте детерминированные производные метаданные из этого набора токенов. + +```bash +DERIVATIVE_METADATA_JSON="$( + jq -c --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ + title: ("Derivative witness for " + $source_collection_id), + description: + ("Minted because the holder currently owns " + + (length | tostring) + + " token(s) from " + + $source_collection_id), + media: ( + map(.metadata.media) + | map(select(. != null)) + | .[0] + ), + copies: 1, + extra: ({ + source_collection_id: $source_collection_id, + source_count: length, + source_token_ids: (map(.token_id) | sort | .[:5]) + } | @json) + }' /tmp/source-collection-tokens.json +)" + +printf '%s\n' "$DERIVATIVE_METADATA_JSON" | jq '.' +``` + +4. Выпустите производный токен в целевой коллекции. + +```bash +near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ + --arg token_id "$TOKEN_ID" \ + --arg receiver_id "$ACCOUNT_ID" \ + --argjson metadata "$DERIVATIVE_METADATA_JSON" '{ + token_id: $token_id, + receiver_id: $receiver_id, + metadata: $metadata + }')" \ + --accountId "$SIGNER_ACCOUNT_ID" \ + --deposit 0.1 \ + --networkId testnet +``` + +5. Подтвердите новый токен тем же каноническим NFT view-методом. + +Если сразу после возврата mint-транзакции токен ещё не виден, не считайте это ошибкой сразу же: опросите view-метод несколько раз. + +```bash +for attempt in 1 2 3 4 5; do + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$DESTINATION_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_tokens_for_owner", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | jq --arg token_id "$TOKEN_ID" ' + map(select(.token_id == $token_id)) + ' \ + | tee /tmp/derivative-token-verification.json >/dev/null + + if jq -e 'length > 0' /tmp/derivative-token-verification.json >/dev/null; then + break + fi + + sleep 1 +done + +jq '.' /tmp/derivative-token-verification.json +``` + +**Зачем нужен следующий шаг?** + +FastNear API быстрее всего отвечает на вопрос о допуске. Как только аккаунт проходит условие, RPC становится правильной поверхностью для точной проверки токенов и подтверждения результата, потому что напрямую открывает канонические NFT view-методы коллекции. + +### У меня обычный стейкинг или liquid staking? + +Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». + +**Сеть** + +- mainnet + +**Официальные ссылки** + +- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) +- [Liquid staking](https://docs.near.org/primitives/liquid-staking) + +Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. + +**Что вы делаете** + +- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. +- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. +- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. +- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +``` + +1. Получите представление по прямому стейкингу. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' +``` + +2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` + +3. Классифицируйте аккаунт на основе этих двух индексированных представлений. + +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' +``` + +**Зачем нужен следующий шаг?** + +Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. + ## Частые ошибки - Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. @@ -1400,6 +1688,64 @@ https://kv.test.fastnear.com - как выглядит последнее индексированное значение и какие изменения видны в истории - совпал ли `view_state` с текущим индексированным значением +### Shell-сценарий + +Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «какая у этого ключа более широкая индексированная история?» + +**Что вы делаете** + +- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. +- Извлекаете точный `key` через `jq`. +- Переиспользуете этот ключ в `POST /v0/history`, чтобы расшириться до истории. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +EXACT_KEY="$( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | tee /tmp/kv-latest.json \ + | jq -r '.entries[0].key' +)" + +jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' /tmp/kv-latest.json + +curl -s "$KV_BASE_URL/v0/history" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ + | jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + value + } + ] + }' +``` + +**Зачем нужен следующий шаг?** + +Поиск последней записи даёт максимально узкий ответ. Повторное использование точного `key` в `POST /v0/history` показывает, не является ли этот ключ частью более широкой индексированной картины. Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). + ## Частые ошибки - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. @@ -1868,6 +2214,52 @@ https://testnet.neardata.xyz - когда то же наблюдение стало финализированным - изменил ли канонический разбор через RPC интерпретацию +### Shell-сценарий + +Используйте этот сценарий, когда вспомогательный маршрут должен сам выбирать для вас последний финализированный блок, но следующий шаг всё равно требует канонического подтверждения через RPC. + +**Что вы делаете** + +- Смотрите redirect, который возвращает `GET /v0/last_block/final`. +- Загружаете итоговый документ блока. +- Извлекаете `block.header.height` через `jq`. +- Переиспользуете эту высоту в RPC `block` по высоте. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +RPC_URL=https://rpc.mainnet.fastnear.com + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +printf 'Redirect target: %s\n' "$FINAL_LOCATION" + +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | tee /tmp/neardata-final-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + +BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "block", + params: { + block_id: ($block_height | tonumber) + } + }')" \ + | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +``` + +**Зачем нужен следующий шаг?** + +Вспомогательный маршрут — самый простой путь опроса для сценария «последний финализированный блок». Как только он сообщил точную высоту блока, правильной следующей поверхностью становится RPC, если нужны канонические семантики этого блока без догадок о том, какой блок проверять. + ## Частые ошибки - Воспринимать NEAR Data API как потоковый продукт, а не как поверхность для опроса. @@ -2147,6 +2539,550 @@ https://archival-rpc.testnet.fastnear.com - Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. - Нужен уже не единичный статус, а более широкий сценарий расследования. +## Готовые сценарии + +### Проверить и удалить старые function-call-ключи Near Social + +Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. + +**Что вы делаете** + +- Через канонический RPC получаете полный список access key аккаунта. +- Сужаете этот список до function-call-ключей, привязанных к `social.near`. +- Точно проверяете один выбранный ключ перед удалением. +- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. + +Сразу важны два ограничения: + +- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. +- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export SOCIAL_RECEIVER_ID=social.near +export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' +export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' +export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' +``` + +1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/fastnear-access-keys.json >/dev/null + +jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + nonce: .access_key.nonce, + receiver_id: .access_key.permission.FunctionCall.receiver_id, + method_names: .access_key.permission.FunctionCall.method_names, + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } +' /tmp/fastnear-access-keys.json +``` + +Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. + +2. Ещё раз проверьте конкретный ключ перед удалением. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$DELETE_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" + } + }')" \ + | jq '{nonce: .result.nonce, permission: .result.permission}' +``` + +3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + is_function_call: true, + limit: 10 + }')" \ + | jq '{ + account_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_success + } + ] + }' +``` + +Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. + +4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. + +Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. + +```bash +SIGNED_TX_BASE64="$( + node --input-type=module <<'EOF' + +const { + ACCOUNT_ID, + NETWORK_ID = 'mainnet', + RPC_URL = 'https://rpc.mainnet.fastnear.com', + DELETE_PUBLIC_KEY, + FULL_ACCESS_PUBLIC_KEY, + FULL_ACCESS_PRIVATE_KEY, +} = process.env; + +for (const name of [ + 'ACCOUNT_ID', + 'DELETE_PUBLIC_KEY', + 'FULL_ACCESS_PUBLIC_KEY', + 'FULL_ACCESS_PRIVATE_KEY', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); +const derivedPublicKey = keyPair.getPublicKey().toString(); + +if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { + throw new Error( + `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: ACCOUNT_ID, + public_key: FULL_ACCESS_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const transaction = transactions.createTransaction( + ACCOUNT_ID, + utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), + ACCOUNT_ID, + BigInt(accessKey.nonce) + 1n, + [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +)" +``` + +5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. + +```bash +if curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' + .result.keys[] + | select(.public_key == $public_key) + ' >/dev/null; then + echo "Key is still present: $DELETE_PUBLIC_KEY" +else + echo "Key deleted: $DELETE_PUBLIC_KEY" +fi +``` + +**Зачем нужен следующий шаг?** + +Повторный вызов `view_access_key_list` замыкает сценарий на той же канонической поверхности, с которой вы начинали поиск. Если ключ исчез именно там, дополнительная индексированная сводка уже не нужна, чтобы подтвердить удаление. + +### Проверить регистрацию FT storage и затем перевести токены + +Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». + +**Сеть** + +- testnet + +**Официальные ссылки** + +- [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) +- [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) + +В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. + +**Что вы делаете** + +- Через канонические RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. +- При необходимости получаете минимальный размер storage deposit. +- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. +- Подтверждаете баланс получателя тем же каноническим view-методом контракта. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet +export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' +export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' +export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' +``` + +1. Проверьте, зарегистрирован ли получатель на FT-контракте. + +```bash +STORAGE_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$STORAGE_BALANCE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "storage_balance_of", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/ft-storage-balance.json >/dev/null + +jq '{ + registered: ((.result.result | implode | fromjson) != null), + storage_balance: (.result.result | implode | fromjson) +}' /tmp/ft-storage-balance.json +``` + +2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. + +```bash +MIN_STORAGE_YOCTO="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_CONTRACT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "storage_balance_bounds", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/ft-storage-bounds.json \ + | jq -r '.result.result | implode | fromjson | .min' +)" + +printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" +``` + +3. Определите одну переиспользуемую функцию подписи для function-call к контракту. + +Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. + +```bash +sign_function_call() { + METHOD_NAME="$1" \ + ARGS_JSON="$2" \ + DEPOSIT_YOCTO="$3" \ + GAS_TGAS="$4" \ + node --input-type=module <<'EOF' + +const { + NETWORK_ID = 'testnet', + RPC_URL = 'https://rpc.testnet.fastnear.com', + TOKEN_CONTRACT_ID, + SENDER_ACCOUNT_ID, + SENDER_PUBLIC_KEY, + SENDER_PRIVATE_KEY, + METHOD_NAME, + ARGS_JSON, + DEPOSIT_YOCTO = '0', + GAS_TGAS = '100', +} = process.env; + +for (const name of [ + 'TOKEN_CONTRACT_ID', + 'SENDER_ACCOUNT_ID', + 'SENDER_PUBLIC_KEY', + 'SENDER_PRIVATE_KEY', + 'METHOD_NAME', + 'ARGS_JSON', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); +const signer = await InMemorySigner.fromKeyPair( + NETWORK_ID, + SENDER_ACCOUNT_ID, + keyPair +); + +const derivedPublicKey = keyPair.getPublicKey().toString(); +if (derivedPublicKey !== SENDER_PUBLIC_KEY) { + throw new Error( + `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: SENDER_ACCOUNT_ID, + public_key: SENDER_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const action = transactions.functionCall( + METHOD_NAME, + Buffer.from(ARGS_JSON), + BigInt(GAS_TGAS) * 10n ** 12n, + BigInt(DEPOSIT_YOCTO) +); + +const transaction = transactions.createTransaction( + SENDER_ACCOUNT_ID, + utils.PublicKey.fromString(SENDER_PUBLIC_KEY), + TOKEN_CONTRACT_ID, + BigInt(accessKey.nonce) + 1n, + [action], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + SENDER_ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +} +``` + +4. При необходимости сначала зарегистрируйте storage для получателя. + +```bash +if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then + SIGNED_TX_BASE64="$( + sign_function_call \ + storage_deposit \ + "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id, + registration_only: true + }')" \ + "$MIN_STORAGE_YOCTO" \ + 100 + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash + }' +fi +``` + +5. После готовности storage переведите FT. + +```bash +SIGNED_TX_BASE64="$( + sign_function_call \ + ft_transfer \ + "$(jq -nc \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ + receiver_id: $receiver_id, + amount: $amount, + memo: "FastNear RPC example" + }')" \ + 1 \ + 100 +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Подтвердите FT-баланс получателя каноническим view-методом контракта. + +```bash +RECEIVER_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_balance_of", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + receiver_balance: (.result.result | implode | fromjson) + }' +``` + +**Зачем нужен следующий шаг?** + +Это канонический RPC-сценарий, потому что каждый шаг остаётся на точном состоянии контракта и точной семантике отправки транзакций: сначала вы доказываете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. + ## Частые ошибки - Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. @@ -2355,6 +3291,34 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash - куда на диске должны попасть данные - должен ли оператор оставаться в FastNear snapshot docs или переходить к более широкой документации nearcore +### Shell-сценарий + +Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. + +**Что вы делаете** + +- Один раз получаете последнюю высоту архивного снапшота mainnet. +- Сохраняете её в `LATEST`. +- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. + +```bash +HOT_DATA_PATH=~/.near/data +COLD_DATA_PATH=/mnt/hdds/cold-data + +LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" +echo "Latest archival mainnet snapshot block: $LATEST" + +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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash +``` + +**Зачем нужен следующий шаг?** + +Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. + ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. @@ -2762,6 +3726,73 @@ https://transfers.main.fastnear.com - Пользователю прямо нужны квитанции или каноническое подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. +## Готовый сценарий + +### Запросить узкое окно переводов, а затем перейти по receipt + +Используйте этот сценарий, когда первый вопрос всё ещё касается только переводов, но вы уже понимаете, что потом может понадобиться одна точная точка перехода в контекст исполнения. + +**Что вы делаете** + +- Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. +- Извлекаете первый `receipt_id` через `jq`. +- Переиспользуете этот receipt ID в Transactions API, чтобы перейти от движения актива к контексту исполнения. + +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +FROM_TIMESTAMP_MS=1711929600000 +TO_TIMESTAMP_MS=1712016000000 + +RECEIPT_ID="$( + curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | tee /tmp/transfers-window.json \ + | jq -r '.transfers[0].receipt_id' +)" + +jq '{ + resume_token, + transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + asset_id, + amount, + other_account_id, + block_height + } + ] +}' /tmp/transfers-window.json + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' +``` + +**Зачем нужен следующий шаг?** + +Запрос переводов позволяет держать первый проход узким и удобным для пагинации. Переход по `receipt_id` даёт одну точную опорную точку в исполнении, не заставляя сразу расширяться до полной истории аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. + ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. @@ -3009,6 +4040,224 @@ https://tx.test.fastnear.com - какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить - был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым +### Понять двухстороннее сопоставление `token_diff`, а затем проследить живой расчёт NEAR Intents + +Используйте это расследование, когда история звучит так: «покажи, что именно NEAR Intents делает под капотом, но привяжи разбор к публичным данным, которые можно проверить самостоятельно». + +**Цель** + +- Сначала объяснить модель сопоставления, а затем превратить один реальный расчёт через `intents.near` в читаемую историю исполнения на базе Transactions API и канонического RPC. + +**Официальные ссылки** + +- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) +- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) +- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) + +#### Часть 1: анатомия протокола + +Базовая форма сопоставления здесь — это `token_diff` intent. Одна сторона объявляет, какие активы она готова отдать и получить, а вторая сторона объявляет противоположную разницу. В официальной документации verifier двухсторонний обмен USDC и USDT показан как один подписанный intent со смыслом «я отдам `-10` USDC и получу `+10` USDT» и второй intent, который описывает обратную сторону сделки. Такие подписанные intent можно собрать через Message Bus или через любой другой внешний канал координации и затем отправить вместе в `intents.near`. + +Эта концептуальная часть полезна, чтобы понять сам протокол, но подписанные примеры в официальной документации носят иллюстративный и привязанный ко времени характер. Для рабочего FastNear-сценария полезнее разбирать один реальный расчёт из mainnet, чем делать вид, будто пример из документации является готовой живой транзакцией. + +#### Часть 2: живая FastNear-трассировка + +Для живой трассировки ниже используйте этот фиксированный якорь расчёта, зафиксированный **18 апреля 2026 года**: + +- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` +- аккаунт `signer` и `receiver`: `intents.near` +- высота включающего блока: `194573310` + +Публичных FastNear-поверхностей уже достаточно, чтобы восстановить многое: + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Якорь расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и получаем саму транзакцию плюс список последующих receipt | Даёт читаемый каркас расчёта без необходимости сразу декодировать сырые receipt | +| Контекст включающего блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Помещает расчёт в контекст блока и показывает, какие receipt появились там | +| Канонический DAG по receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` | Даёт протокольно-канонический DAG, `executor_id` и сырые логи событий | +| Классификация событий | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Извлекаем имена событий вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` из строк `EVENT_JSON` | Позволяет объяснять расчёт по семействам событий, а не по непрозрачным `receipt_id` | + +**Что должен включать полезный ответ** + +- как концептуальная двухсторонняя модель `token_diff` отображается на реальный расчёт через `execute_intents` +- какие последующие контракты и методы появились после `intents.near` +- какие семейства событий выпустила трассировка +- какие высоты блоков сформировали основной каскад + +Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. + +### Shell-сценарий для живой трассировки NEAR Intents + +Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. + +**Что вы делаете** + +- Получаете историю транзакции через Transactions API. +- Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. +- Подтверждаете канонический DAG по receipt и семейства логов событий через `EXPERIMENTAL_tx_status`. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 +INTENTS_SIGNER_ID=intents.near +``` + +1. Начните с самой транзакции расчёта. + +```bash +INTENTS_BLOCK_HASH="$( + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$INTENTS_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/intents-transaction.json \ + | jq -r '.transactions[0].execution_outcome.block_hash' +)" + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + receipt_flow: [ + .transactions[0].receipts[:6][] + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + block_height: .execution_outcome.block_height, + methods: ( + [.receipt.receipt.Action.actions[]?.FunctionCall.method_name] + | map(select(. != null)) + ), + first_log: (.execution_outcome.outcome.logs[0] // null) + } + ] +}' /tmp/intents-transaction.json +``` + +2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. + +```bash +curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_id "$INTENTS_BLOCK_HASH" '{ + block_id: $block_id, + with_receipts: true, + with_transactions: false + }')" \ + | tee /tmp/intents-block.json >/dev/null + +jq --arg tx_hash "$INTENTS_TX_HASH" '{ + block_height: .block.block_height, + block_hash: .block.block_hash, + tx_receipts: [ + .block_receipts[] + | select(.transaction_hash == $tx_hash) + | { + receipt_id, + predecessor_id, + receiver_id, + block_height + } + ] +}' /tmp/intents-block.json +``` + +3. Подтвердите канонический DAG по receipt и извлеките семейства событий через RPC. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$INTENTS_TX_HASH" \ + --arg sender_account_id "$INTENTS_SIGNER_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $sender_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/intents-rpc.json >/dev/null + +jq '{ + final_execution_status: .result.final_execution_status, + receipts_outcome: [ + .result.receipts_outcome[:6][] + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + first_log: (.outcome.logs[0] // null) + } + ] +}' /tmp/intents-rpc.json + +jq -r ' + .result.receipts_outcome[] + | .outcome.logs[] + | select(startswith("EVENT_JSON:")) + | capture("event\":\"(?[^\"]+)\"").event +' /tmp/intents-rpc.json | sort -u +``` + +**Зачем нужен следующий шаг?** + +`POST /v0/transactions` даёт читаемый каркас расчёта. `POST /v0/block` показывает, как этот расчёт расположен внутри включающего блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и сырые логи событий, а не только индексированное резюме. + +### Shell-сценарий для pivot по receipt + +Используйте этот сценарий, когда у вас уже есть один `receipt_id` и нужен самый короткий путь обратно к читаемой истории транзакции. + +**Что вы делаете** + +- Сначала разрешаете receipt. +- Извлекаете `receipt.transaction_hash` через `jq`. +- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RECEIPT_ID=YOUR_RECEIPT_ID +# Пример receipt ID из недавнего mainnet-перевода: +# RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' + +TX_HASH="$( + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | tee /tmp/receipt-lookup.json \ + | jq -r '.receipt.transaction_hash' +)" + +jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash, + tx_block_height: .receipt.tx_block_height + } +}' /tmp/receipt-lookup.json + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + receipt_count: (.transactions[0].receipts | length) + }' +``` + +**Зачем нужен следующий шаг?** + +`POST /v0/receipt` даёт точку перехода. `POST /v0/transactions` превращает эту точку в читаемую историю с контекстом по отправителю, получателю, блоку и связанным receipt-ам. И только после этого обычно стоит расширяться до окон по блоку или аккаунту. + ## Частые ошибки - Пытаться отправлять транзакцию через history API вместо сырого RPC. diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index 9d971b5..4893091 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -118,6 +118,52 @@ - когда то же наблюдение стало финализированным - изменил ли канонический разбор через RPC интерпретацию +### Shell-сценарий + +Используйте этот сценарий, когда вспомогательный маршрут должен сам выбирать для вас последний финализированный блок, но следующий шаг всё равно требует канонического подтверждения через RPC. + +**Что вы делаете** + +- Смотрите redirect, который возвращает `GET /v0/last_block/final`. +- Загружаете итоговый документ блока. +- Извлекаете `block.header.height` через `jq`. +- Переиспользуете эту высоту в RPC `block` по высоте. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +RPC_URL=https://rpc.mainnet.fastnear.com + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +printf 'Redirect target: %s\n' "$FINAL_LOCATION" + +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | tee /tmp/neardata-final-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + +BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "block", + params: { + block_id: ($block_height | tonumber) + } + }')" \ + | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +``` + +**Зачем нужен следующий шаг?** + +Вспомогательный маршрут — самый простой путь опроса для сценария «последний финализированный блок». Как только он сообщил точную высоту блока, правильной следующей поверхностью становится RPC, если нужны канонические семантики этого блока без догадок о том, какой блок проверять. + ## Частые ошибки - Воспринимать NEAR Data API как потоковый продукт, а не как поверхность для опроса. diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index 9d971b5..4893091 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -118,6 +118,52 @@ - когда то же наблюдение стало финализированным - изменил ли канонический разбор через RPC интерпретацию +### Shell-сценарий + +Используйте этот сценарий, когда вспомогательный маршрут должен сам выбирать для вас последний финализированный блок, но следующий шаг всё равно требует канонического подтверждения через RPC. + +**Что вы делаете** + +- Смотрите redirect, который возвращает `GET /v0/last_block/final`. +- Загружаете итоговый документ блока. +- Извлекаете `block.header.height` через `jq`. +- Переиспользуете эту высоту в RPC `block` по высоте. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +RPC_URL=https://rpc.mainnet.fastnear.com + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +printf 'Redirect target: %s\n' "$FINAL_LOCATION" + +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | tee /tmp/neardata-final-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + +BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "block", + params: { + block_id: ($block_height | tonumber) + } + }')" \ + | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +``` + +**Зачем нужен следующий шаг?** + +Вспомогательный маршрут — самый простой путь опроса для сценария «последний финализированный блок». Как только он сообщил точную высоту блока, правильной следующей поверхностью становится RPC, если нужны канонические семантики этого блока без догадок о том, какой блок проверять. + ## Частые ошибки - Воспринимать NEAR Data API как потоковый продукт, а не как поверхность для опроса. diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index a6165a4..354f45c 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -107,6 +107,550 @@ - Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. - Нужен уже не единичный статус, а более широкий сценарий расследования. +## Готовые сценарии + +### Проверить и удалить старые function-call-ключи Near Social + +Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. + +**Что вы делаете** + +- Через канонический RPC получаете полный список access key аккаунта. +- Сужаете этот список до function-call-ключей, привязанных к `social.near`. +- Точно проверяете один выбранный ключ перед удалением. +- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. + +Сразу важны два ограничения: + +- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. +- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export SOCIAL_RECEIVER_ID=social.near +export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' +export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' +export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' +``` + +1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/fastnear-access-keys.json >/dev/null + +jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + nonce: .access_key.nonce, + receiver_id: .access_key.permission.FunctionCall.receiver_id, + method_names: .access_key.permission.FunctionCall.method_names, + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } +' /tmp/fastnear-access-keys.json +``` + +Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. + +2. Ещё раз проверьте конкретный ключ перед удалением. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$DELETE_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" + } + }')" \ + | jq '{nonce: .result.nonce, permission: .result.permission}' +``` + +3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + is_function_call: true, + limit: 10 + }')" \ + | jq '{ + account_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_success + } + ] + }' +``` + +Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. + +4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. + +Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. + +```bash +SIGNED_TX_BASE64="$( + node --input-type=module <<'EOF' + +const { + ACCOUNT_ID, + NETWORK_ID = 'mainnet', + RPC_URL = 'https://rpc.mainnet.fastnear.com', + DELETE_PUBLIC_KEY, + FULL_ACCESS_PUBLIC_KEY, + FULL_ACCESS_PRIVATE_KEY, +} = process.env; + +for (const name of [ + 'ACCOUNT_ID', + 'DELETE_PUBLIC_KEY', + 'FULL_ACCESS_PUBLIC_KEY', + 'FULL_ACCESS_PRIVATE_KEY', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); +const derivedPublicKey = keyPair.getPublicKey().toString(); + +if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { + throw new Error( + `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: ACCOUNT_ID, + public_key: FULL_ACCESS_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const transaction = transactions.createTransaction( + ACCOUNT_ID, + utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), + ACCOUNT_ID, + BigInt(accessKey.nonce) + 1n, + [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +)" +``` + +5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. + +```bash +if curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' + .result.keys[] + | select(.public_key == $public_key) + ' >/dev/null; then + echo "Key is still present: $DELETE_PUBLIC_KEY" +else + echo "Key deleted: $DELETE_PUBLIC_KEY" +fi +``` + +**Зачем нужен следующий шаг?** + +Повторный вызов `view_access_key_list` замыкает сценарий на той же канонической поверхности, с которой вы начинали поиск. Если ключ исчез именно там, дополнительная индексированная сводка уже не нужна, чтобы подтвердить удаление. + +### Проверить регистрацию FT storage и затем перевести токены + +Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». + +**Сеть** + +- testnet + +**Официальные ссылки** + +- [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) +- [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) + +В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. + +**Что вы делаете** + +- Через канонические RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. +- При необходимости получаете минимальный размер storage deposit. +- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. +- Подтверждаете баланс получателя тем же каноническим view-методом контракта. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet +export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' +export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' +export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' +``` + +1. Проверьте, зарегистрирован ли получатель на FT-контракте. + +```bash +STORAGE_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$STORAGE_BALANCE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "storage_balance_of", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/ft-storage-balance.json >/dev/null + +jq '{ + registered: ((.result.result | implode | fromjson) != null), + storage_balance: (.result.result | implode | fromjson) +}' /tmp/ft-storage-balance.json +``` + +2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. + +```bash +MIN_STORAGE_YOCTO="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_CONTRACT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "storage_balance_bounds", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/ft-storage-bounds.json \ + | jq -r '.result.result | implode | fromjson | .min' +)" + +printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" +``` + +3. Определите одну переиспользуемую функцию подписи для function-call к контракту. + +Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. + +```bash +sign_function_call() { + METHOD_NAME="$1" \ + ARGS_JSON="$2" \ + DEPOSIT_YOCTO="$3" \ + GAS_TGAS="$4" \ + node --input-type=module <<'EOF' + +const { + NETWORK_ID = 'testnet', + RPC_URL = 'https://rpc.testnet.fastnear.com', + TOKEN_CONTRACT_ID, + SENDER_ACCOUNT_ID, + SENDER_PUBLIC_KEY, + SENDER_PRIVATE_KEY, + METHOD_NAME, + ARGS_JSON, + DEPOSIT_YOCTO = '0', + GAS_TGAS = '100', +} = process.env; + +for (const name of [ + 'TOKEN_CONTRACT_ID', + 'SENDER_ACCOUNT_ID', + 'SENDER_PUBLIC_KEY', + 'SENDER_PRIVATE_KEY', + 'METHOD_NAME', + 'ARGS_JSON', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); +const signer = await InMemorySigner.fromKeyPair( + NETWORK_ID, + SENDER_ACCOUNT_ID, + keyPair +); + +const derivedPublicKey = keyPair.getPublicKey().toString(); +if (derivedPublicKey !== SENDER_PUBLIC_KEY) { + throw new Error( + `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: SENDER_ACCOUNT_ID, + public_key: SENDER_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const action = transactions.functionCall( + METHOD_NAME, + Buffer.from(ARGS_JSON), + BigInt(GAS_TGAS) * 10n ** 12n, + BigInt(DEPOSIT_YOCTO) +); + +const transaction = transactions.createTransaction( + SENDER_ACCOUNT_ID, + utils.PublicKey.fromString(SENDER_PUBLIC_KEY), + TOKEN_CONTRACT_ID, + BigInt(accessKey.nonce) + 1n, + [action], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + SENDER_ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +} +``` + +4. При необходимости сначала зарегистрируйте storage для получателя. + +```bash +if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then + SIGNED_TX_BASE64="$( + sign_function_call \ + storage_deposit \ + "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id, + registration_only: true + }')" \ + "$MIN_STORAGE_YOCTO" \ + 100 + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash + }' +fi +``` + +5. После готовности storage переведите FT. + +```bash +SIGNED_TX_BASE64="$( + sign_function_call \ + ft_transfer \ + "$(jq -nc \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ + receiver_id: $receiver_id, + amount: $amount, + memo: "FastNear RPC example" + }')" \ + 1 \ + 100 +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Подтвердите FT-баланс получателя каноническим view-методом контракта. + +```bash +RECEIVER_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_balance_of", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + receiver_balance: (.result.result | implode | fromjson) + }' +``` + +**Зачем нужен следующий шаг?** + +Это канонический RPC-сценарий, потому что каждый шаг остаётся на точном состоянии контракта и точной семантике отправки транзакций: сначала вы доказываете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. + ## Частые ошибки - Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index a6165a4..354f45c 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -107,6 +107,550 @@ - Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. - Нужен уже не единичный статус, а более широкий сценарий расследования. +## Готовые сценарии + +### Проверить и удалить старые function-call-ключи Near Social + +Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. + +**Что вы делаете** + +- Через канонический RPC получаете полный список access key аккаунта. +- Сужаете этот список до function-call-ключей, привязанных к `social.near`. +- Точно проверяете один выбранный ключ перед удалением. +- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. + +Сразу важны два ограничения: + +- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. +- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export SOCIAL_RECEIVER_ID=social.near +export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' +export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' +export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' +``` + +1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/fastnear-access-keys.json >/dev/null + +jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + nonce: .access_key.nonce, + receiver_id: .access_key.permission.FunctionCall.receiver_id, + method_names: .access_key.permission.FunctionCall.method_names, + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } +' /tmp/fastnear-access-keys.json +``` + +Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. + +2. Ещё раз проверьте конкретный ключ перед удалением. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$DELETE_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" + } + }')" \ + | jq '{nonce: .result.nonce, permission: .result.permission}' +``` + +3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + is_function_call: true, + limit: 10 + }')" \ + | jq '{ + account_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_success + } + ] + }' +``` + +Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. + +4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. + +Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. + +```bash +SIGNED_TX_BASE64="$( + node --input-type=module <<'EOF' + +const { + ACCOUNT_ID, + NETWORK_ID = 'mainnet', + RPC_URL = 'https://rpc.mainnet.fastnear.com', + DELETE_PUBLIC_KEY, + FULL_ACCESS_PUBLIC_KEY, + FULL_ACCESS_PRIVATE_KEY, +} = process.env; + +for (const name of [ + 'ACCOUNT_ID', + 'DELETE_PUBLIC_KEY', + 'FULL_ACCESS_PUBLIC_KEY', + 'FULL_ACCESS_PRIVATE_KEY', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); +const derivedPublicKey = keyPair.getPublicKey().toString(); + +if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { + throw new Error( + `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: ACCOUNT_ID, + public_key: FULL_ACCESS_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const transaction = transactions.createTransaction( + ACCOUNT_ID, + utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), + ACCOUNT_ID, + BigInt(accessKey.nonce) + 1n, + [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +)" +``` + +5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. + +```bash +if curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' + .result.keys[] + | select(.public_key == $public_key) + ' >/dev/null; then + echo "Key is still present: $DELETE_PUBLIC_KEY" +else + echo "Key deleted: $DELETE_PUBLIC_KEY" +fi +``` + +**Зачем нужен следующий шаг?** + +Повторный вызов `view_access_key_list` замыкает сценарий на той же канонической поверхности, с которой вы начинали поиск. Если ключ исчез именно там, дополнительная индексированная сводка уже не нужна, чтобы подтвердить удаление. + +### Проверить регистрацию FT storage и затем перевести токены + +Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». + +**Сеть** + +- testnet + +**Официальные ссылки** + +- [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) +- [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) + +В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. + +**Что вы делаете** + +- Через канонические RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. +- При необходимости получаете минимальный размер storage deposit. +- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. +- Подтверждаете баланс получателя тем же каноническим view-методом контракта. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet +export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' +export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' +export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' +``` + +1. Проверьте, зарегистрирован ли получатель на FT-контракте. + +```bash +STORAGE_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$STORAGE_BALANCE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "storage_balance_of", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/ft-storage-balance.json >/dev/null + +jq '{ + registered: ((.result.result | implode | fromjson) != null), + storage_balance: (.result.result | implode | fromjson) +}' /tmp/ft-storage-balance.json +``` + +2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. + +```bash +MIN_STORAGE_YOCTO="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_CONTRACT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "storage_balance_bounds", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/ft-storage-bounds.json \ + | jq -r '.result.result | implode | fromjson | .min' +)" + +printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" +``` + +3. Определите одну переиспользуемую функцию подписи для function-call к контракту. + +Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. + +```bash +sign_function_call() { + METHOD_NAME="$1" \ + ARGS_JSON="$2" \ + DEPOSIT_YOCTO="$3" \ + GAS_TGAS="$4" \ + node --input-type=module <<'EOF' + +const { + NETWORK_ID = 'testnet', + RPC_URL = 'https://rpc.testnet.fastnear.com', + TOKEN_CONTRACT_ID, + SENDER_ACCOUNT_ID, + SENDER_PUBLIC_KEY, + SENDER_PRIVATE_KEY, + METHOD_NAME, + ARGS_JSON, + DEPOSIT_YOCTO = '0', + GAS_TGAS = '100', +} = process.env; + +for (const name of [ + 'TOKEN_CONTRACT_ID', + 'SENDER_ACCOUNT_ID', + 'SENDER_PUBLIC_KEY', + 'SENDER_PRIVATE_KEY', + 'METHOD_NAME', + 'ARGS_JSON', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); +const signer = await InMemorySigner.fromKeyPair( + NETWORK_ID, + SENDER_ACCOUNT_ID, + keyPair +); + +const derivedPublicKey = keyPair.getPublicKey().toString(); +if (derivedPublicKey !== SENDER_PUBLIC_KEY) { + throw new Error( + `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: SENDER_ACCOUNT_ID, + public_key: SENDER_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const action = transactions.functionCall( + METHOD_NAME, + Buffer.from(ARGS_JSON), + BigInt(GAS_TGAS) * 10n ** 12n, + BigInt(DEPOSIT_YOCTO) +); + +const transaction = transactions.createTransaction( + SENDER_ACCOUNT_ID, + utils.PublicKey.fromString(SENDER_PUBLIC_KEY), + TOKEN_CONTRACT_ID, + BigInt(accessKey.nonce) + 1n, + [action], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + SENDER_ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +} +``` + +4. При необходимости сначала зарегистрируйте storage для получателя. + +```bash +if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then + SIGNED_TX_BASE64="$( + sign_function_call \ + storage_deposit \ + "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id, + registration_only: true + }')" \ + "$MIN_STORAGE_YOCTO" \ + 100 + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash + }' +fi +``` + +5. После готовности storage переведите FT. + +```bash +SIGNED_TX_BASE64="$( + sign_function_call \ + ft_transfer \ + "$(jq -nc \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ + receiver_id: $receiver_id, + amount: $amount, + memo: "FastNear RPC example" + }')" \ + 1 \ + 100 +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Подтвердите FT-баланс получателя каноническим view-методом контракта. + +```bash +RECEIVER_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_balance_of", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + receiver_balance: (.result.result | implode | fromjson) + }' +``` + +**Зачем нужен следующий шаг?** + +Это канонический RPC-сценарий, потому что каждый шаг остаётся на точном состоянии контракта и точной семантике отправки транзакций: сначала вы доказываете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. + ## Частые ошибки - Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. diff --git a/static/ru/snapshots/examples.md b/static/ru/snapshots/examples.md index 85b8409..7116617 100644 --- a/static/ru/snapshots/examples.md +++ b/static/ru/snapshots/examples.md @@ -118,6 +118,34 @@ - куда на диске должны попасть данные - должен ли оператор оставаться в FastNear snapshot docs или переходить к более широкой документации nearcore +### Shell-сценарий + +Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. + +**Что вы делаете** + +- Один раз получаете последнюю высоту архивного снапшота mainnet. +- Сохраняете её в `LATEST`. +- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. + +```bash +HOT_DATA_PATH=~/.near/data +COLD_DATA_PATH=/mnt/hdds/cold-data + +LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" +echo "Latest archival mainnet snapshot block: $LATEST" + +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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash +``` + +**Зачем нужен следующий шаг?** + +Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. + ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. diff --git a/static/ru/snapshots/examples/index.md b/static/ru/snapshots/examples/index.md index 85b8409..7116617 100644 --- a/static/ru/snapshots/examples/index.md +++ b/static/ru/snapshots/examples/index.md @@ -118,6 +118,34 @@ - куда на диске должны попасть данные - должен ли оператор оставаться в FastNear snapshot docs или переходить к более широкой документации nearcore +### Shell-сценарий + +Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. + +**Что вы делаете** + +- Один раз получаете последнюю высоту архивного снапшота mainnet. +- Сохраняете её в `LATEST`. +- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. + +```bash +HOT_DATA_PATH=~/.near/data +COLD_DATA_PATH=/mnt/hdds/cold-data + +LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" +echo "Latest archival mainnet snapshot block: $LATEST" + +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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash +``` + +**Зачем нужен следующий шаг?** + +Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. + ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. diff --git a/static/ru/transfers/examples.md b/static/ru/transfers/examples.md index 078903e..5d01a52 100644 --- a/static/ru/transfers/examples.md +++ b/static/ru/transfers/examples.md @@ -77,6 +77,73 @@ - Пользователю прямо нужны квитанции или каноническое подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. +## Готовый сценарий + +### Запросить узкое окно переводов, а затем перейти по receipt + +Используйте этот сценарий, когда первый вопрос всё ещё касается только переводов, но вы уже понимаете, что потом может понадобиться одна точная точка перехода в контекст исполнения. + +**Что вы делаете** + +- Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. +- Извлекаете первый `receipt_id` через `jq`. +- Переиспользуете этот receipt ID в Transactions API, чтобы перейти от движения актива к контексту исполнения. + +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +FROM_TIMESTAMP_MS=1711929600000 +TO_TIMESTAMP_MS=1712016000000 + +RECEIPT_ID="$( + curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | tee /tmp/transfers-window.json \ + | jq -r '.transfers[0].receipt_id' +)" + +jq '{ + resume_token, + transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + asset_id, + amount, + other_account_id, + block_height + } + ] +}' /tmp/transfers-window.json + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' +``` + +**Зачем нужен следующий шаг?** + +Запрос переводов позволяет держать первый проход узким и удобным для пагинации. Переход по `receipt_id` даёт одну точную опорную точку в исполнении, не заставляя сразу расширяться до полной истории аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. + ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. diff --git a/static/ru/transfers/examples/index.md b/static/ru/transfers/examples/index.md index 078903e..5d01a52 100644 --- a/static/ru/transfers/examples/index.md +++ b/static/ru/transfers/examples/index.md @@ -77,6 +77,73 @@ - Пользователю прямо нужны квитанции или каноническое подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. +## Готовый сценарий + +### Запросить узкое окно переводов, а затем перейти по receipt + +Используйте этот сценарий, когда первый вопрос всё ещё касается только переводов, но вы уже понимаете, что потом может понадобиться одна точная точка перехода в контекст исполнения. + +**Что вы делаете** + +- Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. +- Извлекаете первый `receipt_id` через `jq`. +- Переиспользуете этот receipt ID в Transactions API, чтобы перейти от движения актива к контексту исполнения. + +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +FROM_TIMESTAMP_MS=1711929600000 +TO_TIMESTAMP_MS=1712016000000 + +RECEIPT_ID="$( + curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | tee /tmp/transfers-window.json \ + | jq -r '.transfers[0].receipt_id' +)" + +jq '{ + resume_token, + transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + asset_id, + amount, + other_account_id, + block_height + } + ] +}' /tmp/transfers-window.json + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' +``` + +**Зачем нужен следующий шаг?** + +Запрос переводов позволяет держать первый проход узким и удобным для пагинации. Переход по `receipt_id` даёт одну точную опорную точку в исполнении, не заставляя сразу расширяться до полной истории аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. + ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index 726aedd..62fd195 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -156,6 +156,224 @@ - какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить - был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым +### Понять двухстороннее сопоставление `token_diff`, а затем проследить живой расчёт NEAR Intents + +Используйте это расследование, когда история звучит так: «покажи, что именно NEAR Intents делает под капотом, но привяжи разбор к публичным данным, которые можно проверить самостоятельно». + +**Цель** + +- Сначала объяснить модель сопоставления, а затем превратить один реальный расчёт через `intents.near` в читаемую историю исполнения на базе Transactions API и канонического RPC. + +**Официальные ссылки** + +- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) +- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) +- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) + +#### Часть 1: анатомия протокола + +Базовая форма сопоставления здесь — это `token_diff` intent. Одна сторона объявляет, какие активы она готова отдать и получить, а вторая сторона объявляет противоположную разницу. В официальной документации verifier двухсторонний обмен USDC и USDT показан как один подписанный intent со смыслом «я отдам `-10` USDC и получу `+10` USDT» и второй intent, который описывает обратную сторону сделки. Такие подписанные intent можно собрать через Message Bus или через любой другой внешний канал координации и затем отправить вместе в `intents.near`. + +Эта концептуальная часть полезна, чтобы понять сам протокол, но подписанные примеры в официальной документации носят иллюстративный и привязанный ко времени характер. Для рабочего FastNear-сценария полезнее разбирать один реальный расчёт из mainnet, чем делать вид, будто пример из документации является готовой живой транзакцией. + +#### Часть 2: живая FastNear-трассировка + +Для живой трассировки ниже используйте этот фиксированный якорь расчёта, зафиксированный **18 апреля 2026 года**: + +- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` +- аккаунт `signer` и `receiver`: `intents.near` +- высота включающего блока: `194573310` + +Публичных FastNear-поверхностей уже достаточно, чтобы восстановить многое: + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Якорь расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и получаем саму транзакцию плюс список последующих receipt | Даёт читаемый каркас расчёта без необходимости сразу декодировать сырые receipt | +| Контекст включающего блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Помещает расчёт в контекст блока и показывает, какие receipt появились там | +| Канонический DAG по receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` | Даёт протокольно-канонический DAG, `executor_id` и сырые логи событий | +| Классификация событий | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Извлекаем имена событий вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` из строк `EVENT_JSON` | Позволяет объяснять расчёт по семействам событий, а не по непрозрачным `receipt_id` | + +**Что должен включать полезный ответ** + +- как концептуальная двухсторонняя модель `token_diff` отображается на реальный расчёт через `execute_intents` +- какие последующие контракты и методы появились после `intents.near` +- какие семейства событий выпустила трассировка +- какие высоты блоков сформировали основной каскад + +Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. + +### Shell-сценарий для живой трассировки NEAR Intents + +Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. + +**Что вы делаете** + +- Получаете историю транзакции через Transactions API. +- Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. +- Подтверждаете канонический DAG по receipt и семейства логов событий через `EXPERIMENTAL_tx_status`. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 +INTENTS_SIGNER_ID=intents.near +``` + +1. Начните с самой транзакции расчёта. + +```bash +INTENTS_BLOCK_HASH="$( + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$INTENTS_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/intents-transaction.json \ + | jq -r '.transactions[0].execution_outcome.block_hash' +)" + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + receipt_flow: [ + .transactions[0].receipts[:6][] + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + block_height: .execution_outcome.block_height, + methods: ( + [.receipt.receipt.Action.actions[]?.FunctionCall.method_name] + | map(select(. != null)) + ), + first_log: (.execution_outcome.outcome.logs[0] // null) + } + ] +}' /tmp/intents-transaction.json +``` + +2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. + +```bash +curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_id "$INTENTS_BLOCK_HASH" '{ + block_id: $block_id, + with_receipts: true, + with_transactions: false + }')" \ + | tee /tmp/intents-block.json >/dev/null + +jq --arg tx_hash "$INTENTS_TX_HASH" '{ + block_height: .block.block_height, + block_hash: .block.block_hash, + tx_receipts: [ + .block_receipts[] + | select(.transaction_hash == $tx_hash) + | { + receipt_id, + predecessor_id, + receiver_id, + block_height + } + ] +}' /tmp/intents-block.json +``` + +3. Подтвердите канонический DAG по receipt и извлеките семейства событий через RPC. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$INTENTS_TX_HASH" \ + --arg sender_account_id "$INTENTS_SIGNER_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $sender_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/intents-rpc.json >/dev/null + +jq '{ + final_execution_status: .result.final_execution_status, + receipts_outcome: [ + .result.receipts_outcome[:6][] + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + first_log: (.outcome.logs[0] // null) + } + ] +}' /tmp/intents-rpc.json + +jq -r ' + .result.receipts_outcome[] + | .outcome.logs[] + | select(startswith("EVENT_JSON:")) + | capture("event\":\"(?[^\"]+)\"").event +' /tmp/intents-rpc.json | sort -u +``` + +**Зачем нужен следующий шаг?** + +`POST /v0/transactions` даёт читаемый каркас расчёта. `POST /v0/block` показывает, как этот расчёт расположен внутри включающего блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и сырые логи событий, а не только индексированное резюме. + +### Shell-сценарий для pivot по receipt + +Используйте этот сценарий, когда у вас уже есть один `receipt_id` и нужен самый короткий путь обратно к читаемой истории транзакции. + +**Что вы делаете** + +- Сначала разрешаете receipt. +- Извлекаете `receipt.transaction_hash` через `jq`. +- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RECEIPT_ID=YOUR_RECEIPT_ID +# Пример receipt ID из недавнего mainnet-перевода: +# RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' + +TX_HASH="$( + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | tee /tmp/receipt-lookup.json \ + | jq -r '.receipt.transaction_hash' +)" + +jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash, + tx_block_height: .receipt.tx_block_height + } +}' /tmp/receipt-lookup.json + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + receipt_count: (.transactions[0].receipts | length) + }' +``` + +**Зачем нужен следующий шаг?** + +`POST /v0/receipt` даёт точку перехода. `POST /v0/transactions` превращает эту точку в читаемую историю с контекстом по отправителю, получателю, блоку и связанным receipt-ам. И только после этого обычно стоит расширяться до окон по блоку или аккаунту. + ## Частые ошибки - Пытаться отправлять транзакцию через history API вместо сырого RPC. diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index 726aedd..62fd195 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -156,6 +156,224 @@ - какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить - был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым +### Понять двухстороннее сопоставление `token_diff`, а затем проследить живой расчёт NEAR Intents + +Используйте это расследование, когда история звучит так: «покажи, что именно NEAR Intents делает под капотом, но привяжи разбор к публичным данным, которые можно проверить самостоятельно». + +**Цель** + +- Сначала объяснить модель сопоставления, а затем превратить один реальный расчёт через `intents.near` в читаемую историю исполнения на базе Transactions API и канонического RPC. + +**Официальные ссылки** + +- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) +- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) +- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) + +#### Часть 1: анатомия протокола + +Базовая форма сопоставления здесь — это `token_diff` intent. Одна сторона объявляет, какие активы она готова отдать и получить, а вторая сторона объявляет противоположную разницу. В официальной документации verifier двухсторонний обмен USDC и USDT показан как один подписанный intent со смыслом «я отдам `-10` USDC и получу `+10` USDT» и второй intent, который описывает обратную сторону сделки. Такие подписанные intent можно собрать через Message Bus или через любой другой внешний канал координации и затем отправить вместе в `intents.near`. + +Эта концептуальная часть полезна, чтобы понять сам протокол, но подписанные примеры в официальной документации носят иллюстративный и привязанный ко времени характер. Для рабочего FastNear-сценария полезнее разбирать один реальный расчёт из mainnet, чем делать вид, будто пример из документации является готовой живой транзакцией. + +#### Часть 2: живая FastNear-трассировка + +Для живой трассировки ниже используйте этот фиксированный якорь расчёта, зафиксированный **18 апреля 2026 года**: + +- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` +- аккаунт `signer` и `receiver`: `intents.near` +- высота включающего блока: `194573310` + +Публичных FastNear-поверхностей уже достаточно, чтобы восстановить многое: + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Якорь расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и получаем саму транзакцию плюс список последующих receipt | Даёт читаемый каркас расчёта без необходимости сразу декодировать сырые receipt | +| Контекст включающего блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Помещает расчёт в контекст блока и показывает, какие receipt появились там | +| Канонический DAG по receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` | Даёт протокольно-канонический DAG, `executor_id` и сырые логи событий | +| Классификация событий | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Извлекаем имена событий вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` из строк `EVENT_JSON` | Позволяет объяснять расчёт по семействам событий, а не по непрозрачным `receipt_id` | + +**Что должен включать полезный ответ** + +- как концептуальная двухсторонняя модель `token_diff` отображается на реальный расчёт через `execute_intents` +- какие последующие контракты и методы появились после `intents.near` +- какие семейства событий выпустила трассировка +- какие высоты блоков сформировали основной каскад + +Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. + +### Shell-сценарий для живой трассировки NEAR Intents + +Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. + +**Что вы делаете** + +- Получаете историю транзакции через Transactions API. +- Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. +- Подтверждаете канонический DAG по receipt и семейства логов событий через `EXPERIMENTAL_tx_status`. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 +INTENTS_SIGNER_ID=intents.near +``` + +1. Начните с самой транзакции расчёта. + +```bash +INTENTS_BLOCK_HASH="$( + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$INTENTS_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/intents-transaction.json \ + | jq -r '.transactions[0].execution_outcome.block_hash' +)" + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + receipt_flow: [ + .transactions[0].receipts[:6][] + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + block_height: .execution_outcome.block_height, + methods: ( + [.receipt.receipt.Action.actions[]?.FunctionCall.method_name] + | map(select(. != null)) + ), + first_log: (.execution_outcome.outcome.logs[0] // null) + } + ] +}' /tmp/intents-transaction.json +``` + +2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. + +```bash +curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_id "$INTENTS_BLOCK_HASH" '{ + block_id: $block_id, + with_receipts: true, + with_transactions: false + }')" \ + | tee /tmp/intents-block.json >/dev/null + +jq --arg tx_hash "$INTENTS_TX_HASH" '{ + block_height: .block.block_height, + block_hash: .block.block_hash, + tx_receipts: [ + .block_receipts[] + | select(.transaction_hash == $tx_hash) + | { + receipt_id, + predecessor_id, + receiver_id, + block_height + } + ] +}' /tmp/intents-block.json +``` + +3. Подтвердите канонический DAG по receipt и извлеките семейства событий через RPC. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$INTENTS_TX_HASH" \ + --arg sender_account_id "$INTENTS_SIGNER_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $sender_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/intents-rpc.json >/dev/null + +jq '{ + final_execution_status: .result.final_execution_status, + receipts_outcome: [ + .result.receipts_outcome[:6][] + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + first_log: (.outcome.logs[0] // null) + } + ] +}' /tmp/intents-rpc.json + +jq -r ' + .result.receipts_outcome[] + | .outcome.logs[] + | select(startswith("EVENT_JSON:")) + | capture("event\":\"(?[^\"]+)\"").event +' /tmp/intents-rpc.json | sort -u +``` + +**Зачем нужен следующий шаг?** + +`POST /v0/transactions` даёт читаемый каркас расчёта. `POST /v0/block` показывает, как этот расчёт расположен внутри включающего блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и сырые логи событий, а не только индексированное резюме. + +### Shell-сценарий для pivot по receipt + +Используйте этот сценарий, когда у вас уже есть один `receipt_id` и нужен самый короткий путь обратно к читаемой истории транзакции. + +**Что вы делаете** + +- Сначала разрешаете receipt. +- Извлекаете `receipt.transaction_hash` через `jq`. +- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RECEIPT_ID=YOUR_RECEIPT_ID +# Пример receipt ID из недавнего mainnet-перевода: +# RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' + +TX_HASH="$( + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | tee /tmp/receipt-lookup.json \ + | jq -r '.receipt.transaction_hash' +)" + +jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash, + tx_block_height: .receipt.tx_block_height + } +}' /tmp/receipt-lookup.json + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + receipt_count: (.transactions[0].receipts | length) + }' +``` + +**Зачем нужен следующий шаг?** + +`POST /v0/receipt` даёт точку перехода. `POST /v0/transactions` превращает эту точку в читаемую историю с контекстом по отправителю, получателю, блоку и связанным receipt-ам. И только после этого обычно стоит расширяться до окон по блоку или аккаунту. + ## Частые ошибки - Пытаться отправлять транзакцию через history API вместо сырого RPC. From 433221222964cc6dcb994f922e55dbc1af071c98 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 12:58:40 -0700 Subject: [PATCH 04/35] docs: add NEAR Social profile proof walkthrough --- docs/tx/examples.md | 382 ++++++++++++++++++ .../current/tx/examples.md | 382 ++++++++++++++++++ static/ru/llms-full.txt | 382 ++++++++++++++++++ static/ru/tx/examples.md | 382 ++++++++++++++++++ static/ru/tx/examples/index.md | 382 ++++++++++++++++++ 5 files changed, 1910 insertions(+) diff --git a/docs/tx/examples.md b/docs/tx/examples.md index e0efc1f..f1d631a 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -164,6 +164,388 @@ Use this investigation when the only thing you have is a receipt ID from a trace - the minimum block and account context needed to explain it - whether the state effect was durable and at which block height it became visible +### Prove that `mike.near` set `profile.name` to `Mike Purvis`, then recover the SocialDB profile write transaction + +Use this investigation when the user story is “I can see `Mike Purvis` on `mike.near`'s NEAR Social profile, but I want to prove exactly when that field was written and which transaction wrote it.” + +**Goal** + +- Start from one readable SocialDB profile field, then recover the exact receipt and originating transaction that wrote it. + +**Official references** + +- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) +- [NEAR Social live read surface](https://api.near.social) + +This follows the same proof recipe as the follow-edge investigation, but it teaches one extra SocialDB nuance: for historical proof, the field-level `:block` is usually more precise than the parent object's `:block`. In this live case, `mike.near/profile/name` was written at block `78675795`, while the broader `mike.near/profile` object later advanced to a different block because unrelated sibling fields changed. FastNear's role is to turn that field-level block into a receipt, then a transaction, and then a readable write payload. + +For this live example, the current `profile.name` value is `Mike Purvis`, the field-level SocialDB write block is `78675795`, the receipt ID is `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN`, the originating transaction hash is `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ`, and the outer transaction block is `78675794`. + +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Semantic field lookup | NEAR Social `POST /get` | Read `mike.near/profile/name` with block metadata enabled | Gives the human-readable field value and the field-level SocialDB `:block` anchor where that value was written | +| Receipt bridge | Transactions API [`POST /v0/block`](/tx/block) | Use the SocialDB field block with `with_receipts: true`, then filter the block receipts back down to `mike.near -> social.near` | Turns the field-level write block into a concrete receipt and originating transaction hash | +| Transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the originating transaction by hash and decode the first `FunctionCall.args` payload | Proves that the underlying write was a `social.near set` call that carried `profile.name` and the surrounding profile fields in the same payload | +| Canonical current-state confirmation | RPC [`query(call_function)`](/rpc/contract/call-function) | Call `social.near get` directly at `final` | Confirms the field still has that value now, even though the earlier steps already proved the specific historical write | + +**What a useful answer should include** + +- whether `mike.near/profile/name` still resolves to `Mike Purvis` +- the field-level SocialDB write block height (`78675795`) and why that anchor is better than the parent profile block for this question +- the specific receipt ID and originating transaction hash behind that write +- proof that the write was a `set` call carrying `profile.name` and other profile fields in the same payload +- the distinction between the receipt execution block (`78675795`) and the outer transaction inclusion block (`78675794`) + +### NEAR Social profile-proof shell walkthrough + +Use this when you want a concrete, repeatable proof chain from one readable NEAR Social profile field to the exact SocialDB write transaction behind it. + +**What you're doing** + +- Read the current `profile.name` field from NEAR Social and capture its field-level SocialDB write block. +- Reuse that block height in FastNear block receipts to recover the receipt ID and transaction hash. +- Reuse the transaction hash in `POST /v0/transactions` to prove the payload was a `social.near set` write carrying `profile.name`. +- Finish with canonical RPC confirmation that the field still resolves to the same value at `final`. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +PROFILE_FIELD=profile/name +``` + +1. Read the profile field from NEAR Social and capture the field-level SocialDB write block. + +```bash +PROFILE_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-profile-name.json \ + | jq -r --arg account_id "$ACCOUNT_ID" \ + '.[ $account_id ].profile.name[":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + current_name: .[$account_id].profile.name[""], + field_block_height: .[$account_id].profile.name[":block"], + parent_profile_block_height: .[$account_id].profile[":block"] +}' /tmp/mike-profile-name.json + +# Expected current_name: "Mike Purvis" +# Expected field block height: 78675795 +``` + +2. Reuse that block height in FastNear block receipts and recover the receipt and transaction bridge. + +```bash +PROFILE_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-profile-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + profile_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-profile-block.json + +# Expected receipt ID: 2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN +# Expected transaction hash: 6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ +``` + +3. Reuse the derived transaction hash in `POST /v0/transactions` and decode the SocialDB write payload. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-profile-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), + description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), + tags: ( + .args + | @base64d + | fromjson + | .data["mike.near"].profile.tags + | keys + ) + } + ) +}' /tmp/mike-profile-transaction.json +``` + +4. Finish with canonical current-state confirmation via raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-profile-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + finality: "final", + current_name: ( + .result.result + | implode + | fromjson + | .[$account_id].profile.name + ) +}' /tmp/mike-profile-rpc.json +``` + +That last step confirms the field still resolves to `Mike Purvis` now. The earlier NEAR Social and FastNear steps are what proved which historical write set that field and which transaction carried the write. + +**Why this next step?** + +NEAR Social gives you the semantic field value. FastNear block receipts give you the bridge to a specific write. FastNear transaction lookup turns that write into a readable profile payload. RPC gives you canonical current-state confirmation. + +### Prove that `mike.near` followed `mob.near`, then recover the SocialDB write transaction + +Use this investigation when the user story is “I can see that `mike.near` follows `mob.near`, but I want to prove exactly when that follow edge was written and which transaction wrote it.” + +**Goal** + +- Start from the readable NEAR Social follow edge, then recover the exact receipt and originating transaction that wrote it into SocialDB. + +**Official references** + +- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) +- [NEAR Social live read surface](https://api.near.social) + +The readable follow edge comes from NEAR Social data, not from FastNear. The important bridge is the SocialDB `:block` metadata: it tells you the receipt execution block where that value was written. That block is not the same thing as the original outer transaction inclusion block. FastNear's job in this workflow is to turn that block height into a receipt, then into a transaction, and finally into a readable execution story. + +For this live example, the current edge is `mike.near -> mob.near`, the SocialDB write block is `79574924`, the receipt ID is `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th`, the originating transaction hash is `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb`, and the outer transaction block is `79574923`. + +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Semantic edge lookup | NEAR Social `POST /get` | Read `mike.near/graph/follow/mob.near` with block metadata enabled | Gives the human-readable follow edge and the SocialDB `:block` anchor where that value was written | +| Receipt bridge | Transactions API [`POST /v0/block`](/tx/block) | Use the SocialDB block height with `with_receipts: true`, then filter the block receipts back down to `mike.near -> social.near` | Turns the SocialDB write block into a concrete receipt and originating transaction hash | +| Transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the originating transaction by hash and decode the first `FunctionCall.args` payload | Proves that the underlying write was a `social.near set` call that wrote both `graph.follow` and `index.graph` entries | +| Canonical current-state confirmation | RPC [`query(call_function)`](/rpc/contract/call-function) | Call `social.near get` directly at `final` | Confirms the follow edge still exists now, even though the earlier steps already proved the specific historical write | + +**What a useful answer should include** + +- whether the `mike.near -> mob.near` follow edge exists now +- the SocialDB write block height (`79574924`) and why it is a receipt execution block +- the specific receipt ID and originating transaction hash behind that write +- proof that the write was a `set` call carrying both `graph.follow.mob.near` and the matching `index.graph` entry +- the distinction between the receipt execution block (`79574924`) and the outer transaction inclusion block (`79574923`) + +### NEAR Social follow-proof shell walkthrough + +Use this when you want a concrete, repeatable proof chain from one readable NEAR Social follow edge to the exact SocialDB write transaction behind it. + +**What you're doing** + +- Read the current follow edge from NEAR Social and capture the SocialDB write block. +- Reuse that block height in FastNear block receipts to recover the receipt ID and transaction hash. +- Reuse the transaction hash in `POST /v0/transactions` to prove the payload was a `social.near set` write. +- Finish with canonical RPC confirmation that the edge still exists at `final`. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +TARGET_ACCOUNT_ID=mob.near +``` + +1. Read the follow edge from NEAR Social and capture the SocialDB write block. + +```bash +FOLLOW_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + keys: [($account_id + "/graph/follow/" + $target_account_id)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-follow-edge.json \ + | jq -r --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" \ + '.[ $account_id ].graph.follow[ $target_account_id ][":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + follow_edge: .[$account_id].graph.follow[$target_account_id][""], + follow_block_height: .[$account_id].graph.follow[$target_account_id][":block"] +}' /tmp/mike-follow-edge.json + +# Expected follow block height: 79574924 +``` + +2. Reuse that block height in FastNear block receipts and recover the receipt and transaction bridge. + +```bash +FOLLOW_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$FOLLOW_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-follow-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + follow_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-follow-block.json + +# Expected receipt ID: UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th +# Expected transaction hash: FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +``` + +3. Reuse the derived transaction hash in `POST /v0/transactions` and decode the SocialDB write payload. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$FOLLOW_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-follow-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + follow_edge: (.args | @base64d | fromjson | .data["mike.near"].graph.follow["mob.near"]), + index_graph: ( + .args + | @base64d + | fromjson + | .data["mike.near"].index.graph + | fromjson + | map(select(.value.accountId == "mob.near")) + ) + } + ) +}' /tmp/mike-follow-transaction.json +``` + +4. Finish with canonical current-state confirmation via raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + keys: [($account_id + "/graph/follow/" + $target_account_id)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-follow-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + finality: "final", + current_follow_edge: ( + .result.result + | implode + | fromjson + | .[$account_id].graph.follow[$target_account_id] + ) +}' /tmp/mike-follow-rpc.json +``` + +That last step confirms the follow edge still exists now. The earlier NEAR Social and FastNear steps are what proved which historical write created the edge and which transaction carried that write. + +**Why this next step?** + +NEAR Social gives you the semantic edge. FastNear block receipts give you the bridge to a specific write. FastNear transaction lookup turns that write into a readable story. RPC gives you canonical current-state confirmation. + ### Understand a two-party `token_diff` match, then trace a live NEAR Intents settlement Use this investigation when the user story is “show me what NEAR Intents is doing under the hood, but keep the trace anchored in public data I can inspect myself.” diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index fb97ff8..ba2577c 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -164,6 +164,388 @@ page_actions: - какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить - был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым +### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB + +Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». + +**Цель** + +- Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. + +**Официальные ссылки** + +- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) +- [Живая поверхность чтения NEAR Social](https://api.near.social) + +Этот сценарий следует тому же рецепту доказательства, что и расследование по подписке, но добавляет ещё один важный нюанс SocialDB: для исторического доказательства `:block` на уровне конкретного поля обычно точнее, чем `:block` у родительского объекта. В этом живом примере `mike.near/profile/name` был записан на блоке `78675795`, тогда как более широкий объект `mike.near/profile` позже сдвинулся на другой блок из-за изменений в соседних полях. Роль FastNear в этом сценарии — превратить этот блок уровня поля в receipt, затем в транзакцию и потом в читаемый payload записи. + +Для этого живого примера текущее значение `profile.name` равно `Mike Purvis`, блок записи SocialDB на уровне поля равен `78675795`, ID receipt — `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN`, хеш исходной транзакции — `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ`, а внешний блок транзакции — `78675794`. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Семантическое чтение поля | NEAR Social `POST /get` | Читаем `mike.near/profile/name` с включёнными метаданными блока | Даёт читаемое значение поля и опорный `:block` SocialDB на уровне поля, где это значение было записано | +| Мост к receipt | Transactions API [`POST /v0/block`](/tx/block) | Используем блок уровня поля из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи уровня поля в конкретный receipt и хеш исходной транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который нёс `profile.name` и окружающие поля профиля в одном payload | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что поле и сейчас имеет это значение, хотя предыдущие шаги уже доказали конкретную историческую запись | + +**Что должен включать полезный ответ** + +- разрешается ли `mike.near/profile/name` сейчас в `Mike Purvis` +- высоту блока записи SocialDB на уровне поля (`78675795`) и объяснение, почему для этого вопроса этот якорь лучше, чем блок родительского профиля +- конкретный ID receipt и хеш исходной транзакции за этой записью +- доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload +- различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) + +### Shell-сценарий доказательства поля профиля в NEAR Social + +Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. + +**Что вы делаете** + +- Читаете текущее поле `profile.name` из NEAR Social и сохраняете блок записи SocialDB на уровне поля. +- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`, несущей `profile.name`. +- Завершаете каноническим RPC-подтверждением того, что поле всё ещё разрешается в то же значение на `final`. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +PROFILE_FIELD=profile/name +``` + +1. Прочитайте поле профиля из NEAR Social и сохраните блок записи SocialDB на уровне поля. + +```bash +PROFILE_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-profile-name.json \ + | jq -r --arg account_id "$ACCOUNT_ID" \ + '.[ $account_id ].profile.name[":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + current_name: .[$account_id].profile.name[""], + field_block_height: .[$account_id].profile.name[":block"], + parent_profile_block_height: .[$account_id].profile[":block"] +}' /tmp/mike-profile-name.json + +# Ожидаемое current_name: "Mike Purvis" +# Ожидаемая высота блока уровня поля: 78675795 +``` + +2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. + +```bash +PROFILE_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-profile-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + profile_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-profile-block.json + +# Ожидаемый receipt ID: 2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN +# Ожидаемый хеш транзакции: 6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ +``` + +3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-profile-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), + description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), + tags: ( + .args + | @base64d + | fromjson + | .data["mike.near"].profile.tags + | keys + ) + } + ) +}' /tmp/mike-profile-transaction.json +``` + +4. Завершите каноническим подтверждением текущего состояния через raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-profile-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + finality: "final", + current_name: ( + .result.result + | implode + | fromjson + | .[$account_id].profile.name + ) +}' /tmp/mike-profile-rpc.json +``` + +Этот последний шаг подтверждает, что поле и сейчас разрешается в `Mike Purvis`. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись установила это поле и какая транзакция несла эту запись. + +**Зачем нужен следующий шаг?** + +NEAR Social даёт семантическое значение поля. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемый payload профиля. RPC даёт каноническое подтверждение текущего состояния. + +### Доказать, что `mike.near` подписался на `mob.near`, а затем восстановить транзакцию записи в SocialDB + +Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». + +**Цель** + +- Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. + +**Официальные ссылки** + +- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) +- [Живая поверхность чтения NEAR Social](https://api.near.social) + +Читаемая связь подписки приходит из данных NEAR Social, а не из FastNear. Ключевой мост здесь — метаданные SocialDB `:block`: они указывают на блок, в котором исполнился receipt, записавший это значение. Этот блок не совпадает с блоком, в который была включена внешняя транзакция. Роль FastNear в этом сценарии — превратить эту высоту блока в receipt, затем в транзакцию и, наконец, в читаемую историю исполнения. + +Для этого живого примера текущая связь выглядит как `mike.near -> mob.near`, блок записи SocialDB равен `79574924`, ID receipt — `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th`, хеш исходной транзакции — `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb`, а внешний блок транзакции — `79574923`. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Семантическое чтение связи | NEAR Social `POST /get` | Читаем `mike.near/graph/follow/mob.near` с включёнными метаданными блока | Даёт читаемую связь подписки и опорный `:block` из SocialDB, где это значение было записано | +| Мост к receipt | Transactions API [`POST /v0/block`](/tx/block) | Используем высоту блока из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи SocialDB в конкретный receipt и хеш исходной транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который записал и `graph.follow`, и записи `index.graph` | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что связь подписки существует и сейчас, хотя предыдущие шаги уже доказали конкретную историческую запись | + +**Что должен включать полезный ответ** + +- существует ли сейчас связь подписки `mike.near -> mob.near` +- высоту блока записи SocialDB (`79574924`) и объяснение, почему это блок исполнения receipt +- конкретный ID receipt и хеш исходной транзакции за этой записью +- доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` +- различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) + +### Shell-сценарий доказательства подписки в NEAR Social + +Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. + +**Что вы делаете** + +- Читаете текущую связь подписки из NEAR Social и сохраняете блок записи SocialDB. +- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`. +- Завершаете каноническим RPC-подтверждением того, что связь всё ещё существует на `final`. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +TARGET_ACCOUNT_ID=mob.near +``` + +1. Прочитайте связь подписки из NEAR Social и сохраните блок записи SocialDB. + +```bash +FOLLOW_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + keys: [($account_id + "/graph/follow/" + $target_account_id)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-follow-edge.json \ + | jq -r --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" \ + '.[ $account_id ].graph.follow[ $target_account_id ][":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + follow_edge: .[$account_id].graph.follow[$target_account_id][""], + follow_block_height: .[$account_id].graph.follow[$target_account_id][":block"] +}' /tmp/mike-follow-edge.json + +# Ожидаемая высота блока записи: 79574924 +``` + +2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. + +```bash +FOLLOW_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$FOLLOW_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-follow-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + follow_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-follow-block.json + +# Ожидаемый receipt ID: UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th +# Ожидаемый хеш транзакции: FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +``` + +3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$FOLLOW_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-follow-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + follow_edge: (.args | @base64d | fromjson | .data["mike.near"].graph.follow["mob.near"]), + index_graph: ( + .args + | @base64d + | fromjson + | .data["mike.near"].index.graph + | fromjson + | map(select(.value.accountId == "mob.near")) + ) + } + ) +}' /tmp/mike-follow-transaction.json +``` + +4. Завершите каноническим подтверждением текущего состояния через raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + keys: [($account_id + "/graph/follow/" + $target_account_id)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-follow-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + finality: "final", + current_follow_edge: ( + .result.result + | implode + | fromjson + | .[$account_id].graph.follow[$target_account_id] + ) +}' /tmp/mike-follow-rpc.json +``` + +Этот последний шаг подтверждает, что связь подписки существует и сейчас. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись создала эту связь и какая транзакция несла эту запись. + +**Зачем нужен следующий шаг?** + +NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. + ### Понять двухстороннее сопоставление `token_diff`, а затем проследить живой расчёт NEAR Intents Используйте это расследование, когда история звучит так: «покажи, что именно NEAR Intents делает под капотом, но привяжи разбор к публичным данным, которые можно проверить самостоятельно». diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index b4e3d61..70daca2 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -4040,6 +4040,388 @@ https://tx.test.fastnear.com - какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить - был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым +### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB + +Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». + +**Цель** + +- Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. + +**Официальные ссылки** + +- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) +- [Живая поверхность чтения NEAR Social](https://api.near.social) + +Этот сценарий следует тому же рецепту доказательства, что и расследование по подписке, но добавляет ещё один важный нюанс SocialDB: для исторического доказательства `:block` на уровне конкретного поля обычно точнее, чем `:block` у родительского объекта. В этом живом примере `mike.near/profile/name` был записан на блоке `78675795`, тогда как более широкий объект `mike.near/profile` позже сдвинулся на другой блок из-за изменений в соседних полях. Роль FastNear в этом сценарии — превратить этот блок уровня поля в receipt, затем в транзакцию и потом в читаемый payload записи. + +Для этого живого примера текущее значение `profile.name` равно `Mike Purvis`, блок записи SocialDB на уровне поля равен `78675795`, ID receipt — `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN`, хеш исходной транзакции — `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ`, а внешний блок транзакции — `78675794`. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Семантическое чтение поля | NEAR Social `POST /get` | Читаем `mike.near/profile/name` с включёнными метаданными блока | Даёт читаемое значение поля и опорный `:block` SocialDB на уровне поля, где это значение было записано | +| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем блок уровня поля из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи уровня поля в конкретный receipt и хеш исходной транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который нёс `profile.name` и окружающие поля профиля в одном payload | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что поле и сейчас имеет это значение, хотя предыдущие шаги уже доказали конкретную историческую запись | + +**Что должен включать полезный ответ** + +- разрешается ли `mike.near/profile/name` сейчас в `Mike Purvis` +- высоту блока записи SocialDB на уровне поля (`78675795`) и объяснение, почему для этого вопроса этот якорь лучше, чем блок родительского профиля +- конкретный ID receipt и хеш исходной транзакции за этой записью +- доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload +- различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) + +### Shell-сценарий доказательства поля профиля в NEAR Social + +Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. + +**Что вы делаете** + +- Читаете текущее поле `profile.name` из NEAR Social и сохраняете блок записи SocialDB на уровне поля. +- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`, несущей `profile.name`. +- Завершаете каноническим RPC-подтверждением того, что поле всё ещё разрешается в то же значение на `final`. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +PROFILE_FIELD=profile/name +``` + +1. Прочитайте поле профиля из NEAR Social и сохраните блок записи SocialDB на уровне поля. + +```bash +PROFILE_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-profile-name.json \ + | jq -r --arg account_id "$ACCOUNT_ID" \ + '.[ $account_id ].profile.name[":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + current_name: .[$account_id].profile.name[""], + field_block_height: .[$account_id].profile.name[":block"], + parent_profile_block_height: .[$account_id].profile[":block"] +}' /tmp/mike-profile-name.json + +# Ожидаемое current_name: "Mike Purvis" +# Ожидаемая высота блока уровня поля: 78675795 +``` + +2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. + +```bash +PROFILE_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-profile-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + profile_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-profile-block.json + +# Ожидаемый receipt ID: 2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN +# Ожидаемый хеш транзакции: 6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ +``` + +3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-profile-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), + description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), + tags: ( + .args + | @base64d + | fromjson + | .data["mike.near"].profile.tags + | keys + ) + } + ) +}' /tmp/mike-profile-transaction.json +``` + +4. Завершите каноническим подтверждением текущего состояния через raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-profile-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + finality: "final", + current_name: ( + .result.result + | implode + | fromjson + | .[$account_id].profile.name + ) +}' /tmp/mike-profile-rpc.json +``` + +Этот последний шаг подтверждает, что поле и сейчас разрешается в `Mike Purvis`. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись установила это поле и какая транзакция несла эту запись. + +**Зачем нужен следующий шаг?** + +NEAR Social даёт семантическое значение поля. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемый payload профиля. RPC даёт каноническое подтверждение текущего состояния. + +### Доказать, что `mike.near` подписался на `mob.near`, а затем восстановить транзакцию записи в SocialDB + +Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». + +**Цель** + +- Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. + +**Официальные ссылки** + +- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) +- [Живая поверхность чтения NEAR Social](https://api.near.social) + +Читаемая связь подписки приходит из данных NEAR Social, а не из FastNear. Ключевой мост здесь — метаданные SocialDB `:block`: они указывают на блок, в котором исполнился receipt, записавший это значение. Этот блок не совпадает с блоком, в который была включена внешняя транзакция. Роль FastNear в этом сценарии — превратить эту высоту блока в receipt, затем в транзакцию и, наконец, в читаемую историю исполнения. + +Для этого живого примера текущая связь выглядит как `mike.near -> mob.near`, блок записи SocialDB равен `79574924`, ID receipt — `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th`, хеш исходной транзакции — `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb`, а внешний блок транзакции — `79574923`. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Семантическое чтение связи | NEAR Social `POST /get` | Читаем `mike.near/graph/follow/mob.near` с включёнными метаданными блока | Даёт читаемую связь подписки и опорный `:block` из SocialDB, где это значение было записано | +| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем высоту блока из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи SocialDB в конкретный receipt и хеш исходной транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который записал и `graph.follow`, и записи `index.graph` | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что связь подписки существует и сейчас, хотя предыдущие шаги уже доказали конкретную историческую запись | + +**Что должен включать полезный ответ** + +- существует ли сейчас связь подписки `mike.near -> mob.near` +- высоту блока записи SocialDB (`79574924`) и объяснение, почему это блок исполнения receipt +- конкретный ID receipt и хеш исходной транзакции за этой записью +- доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` +- различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) + +### Shell-сценарий доказательства подписки в NEAR Social + +Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. + +**Что вы делаете** + +- Читаете текущую связь подписки из NEAR Social и сохраняете блок записи SocialDB. +- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`. +- Завершаете каноническим RPC-подтверждением того, что связь всё ещё существует на `final`. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +TARGET_ACCOUNT_ID=mob.near +``` + +1. Прочитайте связь подписки из NEAR Social и сохраните блок записи SocialDB. + +```bash +FOLLOW_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + keys: [($account_id + "/graph/follow/" + $target_account_id)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-follow-edge.json \ + | jq -r --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" \ + '.[ $account_id ].graph.follow[ $target_account_id ][":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + follow_edge: .[$account_id].graph.follow[$target_account_id][""], + follow_block_height: .[$account_id].graph.follow[$target_account_id][":block"] +}' /tmp/mike-follow-edge.json + +# Ожидаемая высота блока записи: 79574924 +``` + +2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. + +```bash +FOLLOW_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$FOLLOW_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-follow-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + follow_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-follow-block.json + +# Ожидаемый receipt ID: UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th +# Ожидаемый хеш транзакции: FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +``` + +3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$FOLLOW_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-follow-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + follow_edge: (.args | @base64d | fromjson | .data["mike.near"].graph.follow["mob.near"]), + index_graph: ( + .args + | @base64d + | fromjson + | .data["mike.near"].index.graph + | fromjson + | map(select(.value.accountId == "mob.near")) + ) + } + ) +}' /tmp/mike-follow-transaction.json +``` + +4. Завершите каноническим подтверждением текущего состояния через raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + keys: [($account_id + "/graph/follow/" + $target_account_id)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-follow-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + finality: "final", + current_follow_edge: ( + .result.result + | implode + | fromjson + | .[$account_id].graph.follow[$target_account_id] + ) +}' /tmp/mike-follow-rpc.json +``` + +Этот последний шаг подтверждает, что связь подписки существует и сейчас. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись создала эту связь и какая транзакция несла эту запись. + +**Зачем нужен следующий шаг?** + +NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. + ### Понять двухстороннее сопоставление `token_diff`, а затем проследить живой расчёт NEAR Intents Используйте это расследование, когда история звучит так: «покажи, что именно NEAR Intents делает под капотом, но привяжи разбор к публичным данным, которые можно проверить самостоятельно». diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index 62fd195..aa752a1 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -156,6 +156,388 @@ - какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить - был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым +### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB + +Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». + +**Цель** + +- Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. + +**Официальные ссылки** + +- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) +- [Живая поверхность чтения NEAR Social](https://api.near.social) + +Этот сценарий следует тому же рецепту доказательства, что и расследование по подписке, но добавляет ещё один важный нюанс SocialDB: для исторического доказательства `:block` на уровне конкретного поля обычно точнее, чем `:block` у родительского объекта. В этом живом примере `mike.near/profile/name` был записан на блоке `78675795`, тогда как более широкий объект `mike.near/profile` позже сдвинулся на другой блок из-за изменений в соседних полях. Роль FastNear в этом сценарии — превратить этот блок уровня поля в receipt, затем в транзакцию и потом в читаемый payload записи. + +Для этого живого примера текущее значение `profile.name` равно `Mike Purvis`, блок записи SocialDB на уровне поля равен `78675795`, ID receipt — `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN`, хеш исходной транзакции — `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ`, а внешний блок транзакции — `78675794`. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Семантическое чтение поля | NEAR Social `POST /get` | Читаем `mike.near/profile/name` с включёнными метаданными блока | Даёт читаемое значение поля и опорный `:block` SocialDB на уровне поля, где это значение было записано | +| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем блок уровня поля из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи уровня поля в конкретный receipt и хеш исходной транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который нёс `profile.name` и окружающие поля профиля в одном payload | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что поле и сейчас имеет это значение, хотя предыдущие шаги уже доказали конкретную историческую запись | + +**Что должен включать полезный ответ** + +- разрешается ли `mike.near/profile/name` сейчас в `Mike Purvis` +- высоту блока записи SocialDB на уровне поля (`78675795`) и объяснение, почему для этого вопроса этот якорь лучше, чем блок родительского профиля +- конкретный ID receipt и хеш исходной транзакции за этой записью +- доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload +- различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) + +### Shell-сценарий доказательства поля профиля в NEAR Social + +Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. + +**Что вы делаете** + +- Читаете текущее поле `profile.name` из NEAR Social и сохраняете блок записи SocialDB на уровне поля. +- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`, несущей `profile.name`. +- Завершаете каноническим RPC-подтверждением того, что поле всё ещё разрешается в то же значение на `final`. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +PROFILE_FIELD=profile/name +``` + +1. Прочитайте поле профиля из NEAR Social и сохраните блок записи SocialDB на уровне поля. + +```bash +PROFILE_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-profile-name.json \ + | jq -r --arg account_id "$ACCOUNT_ID" \ + '.[ $account_id ].profile.name[":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + current_name: .[$account_id].profile.name[""], + field_block_height: .[$account_id].profile.name[":block"], + parent_profile_block_height: .[$account_id].profile[":block"] +}' /tmp/mike-profile-name.json + +# Ожидаемое current_name: "Mike Purvis" +# Ожидаемая высота блока уровня поля: 78675795 +``` + +2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. + +```bash +PROFILE_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-profile-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + profile_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-profile-block.json + +# Ожидаемый receipt ID: 2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN +# Ожидаемый хеш транзакции: 6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ +``` + +3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-profile-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), + description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), + tags: ( + .args + | @base64d + | fromjson + | .data["mike.near"].profile.tags + | keys + ) + } + ) +}' /tmp/mike-profile-transaction.json +``` + +4. Завершите каноническим подтверждением текущего состояния через raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-profile-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + finality: "final", + current_name: ( + .result.result + | implode + | fromjson + | .[$account_id].profile.name + ) +}' /tmp/mike-profile-rpc.json +``` + +Этот последний шаг подтверждает, что поле и сейчас разрешается в `Mike Purvis`. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись установила это поле и какая транзакция несла эту запись. + +**Зачем нужен следующий шаг?** + +NEAR Social даёт семантическое значение поля. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемый payload профиля. RPC даёт каноническое подтверждение текущего состояния. + +### Доказать, что `mike.near` подписался на `mob.near`, а затем восстановить транзакцию записи в SocialDB + +Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». + +**Цель** + +- Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. + +**Официальные ссылки** + +- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) +- [Живая поверхность чтения NEAR Social](https://api.near.social) + +Читаемая связь подписки приходит из данных NEAR Social, а не из FastNear. Ключевой мост здесь — метаданные SocialDB `:block`: они указывают на блок, в котором исполнился receipt, записавший это значение. Этот блок не совпадает с блоком, в который была включена внешняя транзакция. Роль FastNear в этом сценарии — превратить эту высоту блока в receipt, затем в транзакцию и, наконец, в читаемую историю исполнения. + +Для этого живого примера текущая связь выглядит как `mike.near -> mob.near`, блок записи SocialDB равен `79574924`, ID receipt — `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th`, хеш исходной транзакции — `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb`, а внешний блок транзакции — `79574923`. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Семантическое чтение связи | NEAR Social `POST /get` | Читаем `mike.near/graph/follow/mob.near` с включёнными метаданными блока | Даёт читаемую связь подписки и опорный `:block` из SocialDB, где это значение было записано | +| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем высоту блока из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи SocialDB в конкретный receipt и хеш исходной транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который записал и `graph.follow`, и записи `index.graph` | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что связь подписки существует и сейчас, хотя предыдущие шаги уже доказали конкретную историческую запись | + +**Что должен включать полезный ответ** + +- существует ли сейчас связь подписки `mike.near -> mob.near` +- высоту блока записи SocialDB (`79574924`) и объяснение, почему это блок исполнения receipt +- конкретный ID receipt и хеш исходной транзакции за этой записью +- доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` +- различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) + +### Shell-сценарий доказательства подписки в NEAR Social + +Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. + +**Что вы делаете** + +- Читаете текущую связь подписки из NEAR Social и сохраняете блок записи SocialDB. +- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`. +- Завершаете каноническим RPC-подтверждением того, что связь всё ещё существует на `final`. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +TARGET_ACCOUNT_ID=mob.near +``` + +1. Прочитайте связь подписки из NEAR Social и сохраните блок записи SocialDB. + +```bash +FOLLOW_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + keys: [($account_id + "/graph/follow/" + $target_account_id)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-follow-edge.json \ + | jq -r --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" \ + '.[ $account_id ].graph.follow[ $target_account_id ][":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + follow_edge: .[$account_id].graph.follow[$target_account_id][""], + follow_block_height: .[$account_id].graph.follow[$target_account_id][":block"] +}' /tmp/mike-follow-edge.json + +# Ожидаемая высота блока записи: 79574924 +``` + +2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. + +```bash +FOLLOW_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$FOLLOW_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-follow-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + follow_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-follow-block.json + +# Ожидаемый receipt ID: UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th +# Ожидаемый хеш транзакции: FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +``` + +3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$FOLLOW_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-follow-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + follow_edge: (.args | @base64d | fromjson | .data["mike.near"].graph.follow["mob.near"]), + index_graph: ( + .args + | @base64d + | fromjson + | .data["mike.near"].index.graph + | fromjson + | map(select(.value.accountId == "mob.near")) + ) + } + ) +}' /tmp/mike-follow-transaction.json +``` + +4. Завершите каноническим подтверждением текущего состояния через raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + keys: [($account_id + "/graph/follow/" + $target_account_id)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-follow-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + finality: "final", + current_follow_edge: ( + .result.result + | implode + | fromjson + | .[$account_id].graph.follow[$target_account_id] + ) +}' /tmp/mike-follow-rpc.json +``` + +Этот последний шаг подтверждает, что связь подписки существует и сейчас. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись создала эту связь и какая транзакция несла эту запись. + +**Зачем нужен следующий шаг?** + +NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. + ### Понять двухстороннее сопоставление `token_diff`, а затем проследить живой расчёт NEAR Intents Используйте это расследование, когда история звучит так: «покажи, что именно NEAR Intents делает под капотом, но привяжи разбор к публичным данным, которые можно проверить самостоятельно». diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index 62fd195..aa752a1 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -156,6 +156,388 @@ - какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить - был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым +### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB + +Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». + +**Цель** + +- Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. + +**Официальные ссылки** + +- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) +- [Живая поверхность чтения NEAR Social](https://api.near.social) + +Этот сценарий следует тому же рецепту доказательства, что и расследование по подписке, но добавляет ещё один важный нюанс SocialDB: для исторического доказательства `:block` на уровне конкретного поля обычно точнее, чем `:block` у родительского объекта. В этом живом примере `mike.near/profile/name` был записан на блоке `78675795`, тогда как более широкий объект `mike.near/profile` позже сдвинулся на другой блок из-за изменений в соседних полях. Роль FastNear в этом сценарии — превратить этот блок уровня поля в receipt, затем в транзакцию и потом в читаемый payload записи. + +Для этого живого примера текущее значение `profile.name` равно `Mike Purvis`, блок записи SocialDB на уровне поля равен `78675795`, ID receipt — `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN`, хеш исходной транзакции — `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ`, а внешний блок транзакции — `78675794`. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Семантическое чтение поля | NEAR Social `POST /get` | Читаем `mike.near/profile/name` с включёнными метаданными блока | Даёт читаемое значение поля и опорный `:block` SocialDB на уровне поля, где это значение было записано | +| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем блок уровня поля из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи уровня поля в конкретный receipt и хеш исходной транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который нёс `profile.name` и окружающие поля профиля в одном payload | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что поле и сейчас имеет это значение, хотя предыдущие шаги уже доказали конкретную историческую запись | + +**Что должен включать полезный ответ** + +- разрешается ли `mike.near/profile/name` сейчас в `Mike Purvis` +- высоту блока записи SocialDB на уровне поля (`78675795`) и объяснение, почему для этого вопроса этот якорь лучше, чем блок родительского профиля +- конкретный ID receipt и хеш исходной транзакции за этой записью +- доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload +- различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) + +### Shell-сценарий доказательства поля профиля в NEAR Social + +Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. + +**Что вы делаете** + +- Читаете текущее поле `profile.name` из NEAR Social и сохраняете блок записи SocialDB на уровне поля. +- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`, несущей `profile.name`. +- Завершаете каноническим RPC-подтверждением того, что поле всё ещё разрешается в то же значение на `final`. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +PROFILE_FIELD=profile/name +``` + +1. Прочитайте поле профиля из NEAR Social и сохраните блок записи SocialDB на уровне поля. + +```bash +PROFILE_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-profile-name.json \ + | jq -r --arg account_id "$ACCOUNT_ID" \ + '.[ $account_id ].profile.name[":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + current_name: .[$account_id].profile.name[""], + field_block_height: .[$account_id].profile.name[":block"], + parent_profile_block_height: .[$account_id].profile[":block"] +}' /tmp/mike-profile-name.json + +# Ожидаемое current_name: "Mike Purvis" +# Ожидаемая высота блока уровня поля: 78675795 +``` + +2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. + +```bash +PROFILE_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-profile-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + profile_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-profile-block.json + +# Ожидаемый receipt ID: 2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN +# Ожидаемый хеш транзакции: 6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ +``` + +3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-profile-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), + description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), + tags: ( + .args + | @base64d + | fromjson + | .data["mike.near"].profile.tags + | keys + ) + } + ) +}' /tmp/mike-profile-transaction.json +``` + +4. Завершите каноническим подтверждением текущего состояния через raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-profile-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + finality: "final", + current_name: ( + .result.result + | implode + | fromjson + | .[$account_id].profile.name + ) +}' /tmp/mike-profile-rpc.json +``` + +Этот последний шаг подтверждает, что поле и сейчас разрешается в `Mike Purvis`. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись установила это поле и какая транзакция несла эту запись. + +**Зачем нужен следующий шаг?** + +NEAR Social даёт семантическое значение поля. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемый payload профиля. RPC даёт каноническое подтверждение текущего состояния. + +### Доказать, что `mike.near` подписался на `mob.near`, а затем восстановить транзакцию записи в SocialDB + +Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». + +**Цель** + +- Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. + +**Официальные ссылки** + +- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) +- [Живая поверхность чтения NEAR Social](https://api.near.social) + +Читаемая связь подписки приходит из данных NEAR Social, а не из FastNear. Ключевой мост здесь — метаданные SocialDB `:block`: они указывают на блок, в котором исполнился receipt, записавший это значение. Этот блок не совпадает с блоком, в который была включена внешняя транзакция. Роль FastNear в этом сценарии — превратить эту высоту блока в receipt, затем в транзакцию и, наконец, в читаемую историю исполнения. + +Для этого живого примера текущая связь выглядит как `mike.near -> mob.near`, блок записи SocialDB равен `79574924`, ID receipt — `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th`, хеш исходной транзакции — `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb`, а внешний блок транзакции — `79574923`. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Семантическое чтение связи | NEAR Social `POST /get` | Читаем `mike.near/graph/follow/mob.near` с включёнными метаданными блока | Даёт читаемую связь подписки и опорный `:block` из SocialDB, где это значение было записано | +| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем высоту блока из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи SocialDB в конкретный receipt и хеш исходной транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который записал и `graph.follow`, и записи `index.graph` | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что связь подписки существует и сейчас, хотя предыдущие шаги уже доказали конкретную историческую запись | + +**Что должен включать полезный ответ** + +- существует ли сейчас связь подписки `mike.near -> mob.near` +- высоту блока записи SocialDB (`79574924`) и объяснение, почему это блок исполнения receipt +- конкретный ID receipt и хеш исходной транзакции за этой записью +- доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` +- различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) + +### Shell-сценарий доказательства подписки в NEAR Social + +Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. + +**Что вы делаете** + +- Читаете текущую связь подписки из NEAR Social и сохраняете блок записи SocialDB. +- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`. +- Завершаете каноническим RPC-подтверждением того, что связь всё ещё существует на `final`. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +TARGET_ACCOUNT_ID=mob.near +``` + +1. Прочитайте связь подписки из NEAR Social и сохраните блок записи SocialDB. + +```bash +FOLLOW_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + keys: [($account_id + "/graph/follow/" + $target_account_id)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-follow-edge.json \ + | jq -r --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" \ + '.[ $account_id ].graph.follow[ $target_account_id ][":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + follow_edge: .[$account_id].graph.follow[$target_account_id][""], + follow_block_height: .[$account_id].graph.follow[$target_account_id][":block"] +}' /tmp/mike-follow-edge.json + +# Ожидаемая высота блока записи: 79574924 +``` + +2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. + +```bash +FOLLOW_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$FOLLOW_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-follow-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + follow_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-follow-block.json + +# Ожидаемый receipt ID: UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th +# Ожидаемый хеш транзакции: FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +``` + +3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$FOLLOW_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-follow-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + follow_edge: (.args | @base64d | fromjson | .data["mike.near"].graph.follow["mob.near"]), + index_graph: ( + .args + | @base64d + | fromjson + | .data["mike.near"].index.graph + | fromjson + | map(select(.value.accountId == "mob.near")) + ) + } + ) +}' /tmp/mike-follow-transaction.json +``` + +4. Завершите каноническим подтверждением текущего состояния через raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + keys: [($account_id + "/graph/follow/" + $target_account_id)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-follow-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + finality: "final", + current_follow_edge: ( + .result.result + | implode + | fromjson + | .[$account_id].graph.follow[$target_account_id] + ) +}' /tmp/mike-follow-rpc.json +``` + +Этот последний шаг подтверждает, что связь подписки существует и сейчас. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись создала эту связь и какая транзакция несла эту запись. + +**Зачем нужен следующий шаг?** + +NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. + ### Понять двухстороннее сопоставление `token_diff`, а затем проследить живой расчёт NEAR Intents Используйте это расследование, когда история звучит так: «покажи, что именно NEAR Intents делает под капотом, но привяжи разбор к публичным данным, которые можно проверить самостоятельно». From 6e65012018aa841722a88ccaa77f29fb656c0f30 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 16:01:20 -0700 Subject: [PATCH 05/35] docs: sharpen example workflows --- docs/api/examples.md | 164 +- docs/fastdata/kv/examples.md | 186 +- docs/neardata/examples.md | 173 +- docs/rpc/examples.md | 532 +++- docs/snapshots/examples.mdx | 126 +- docs/transfers/examples.md | 152 +- docs/tx/examples.md | 872 ++++++- .../current/api/examples.md | 160 +- .../current/fastdata/kv/examples.md | 186 +- .../current/neardata/examples.md | 171 +- .../current/rpc/examples.md | 532 +++- .../current/snapshots/examples.mdx | 128 +- .../current/transfers/examples.md | 150 +- .../current/tx/examples.md | 870 ++++++- static/ru/api/examples.md | 156 +- static/ru/api/examples/index.md | 156 +- static/ru/fastdata/kv/examples.md | 182 +- static/ru/fastdata/kv/examples/index.md | 182 +- static/ru/guides/llms.txt | 12 +- static/ru/llms-full.txt | 2264 +++++++++++------ static/ru/llms.txt | 12 +- static/ru/neardata/examples.md | 167 +- static/ru/neardata/examples/index.md | 167 +- static/ru/rpc/examples.md | 532 +++- static/ru/rpc/examples/index.md | 532 +++- static/ru/snapshots/examples.md | 123 +- static/ru/snapshots/examples/index.md | 123 +- static/ru/transfers/examples.md | 148 +- static/ru/transfers/examples/index.md | 148 +- static/ru/tx/examples.md | 868 ++++++- static/ru/tx/examples/index.md | 868 ++++++- 31 files changed, 7590 insertions(+), 3452 deletions(-) diff --git a/docs/api/examples.md b/docs/api/examples.md index 6e9f822..34e10e1 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -1,95 +1,13 @@ --- sidebar_label: Examples slug: /api/examples -title: FastNear API Examples -description: Plain-language workflows for using FastNear API docs for account summaries, key lookups, and asset-specific follow-up. +title: API Examples +description: Plain-language workflows for using FastNear API docs for account lookups, holdings checks, NFT gating, and staking classification. displayed_sidebar: fastnearApiSidebar page_actions: - markdown --- -# FastNear API Examples - -Use this page when the user wants a readable account- or asset-shaped answer and you want the shortest path through the FastNear API docs. Start with the smallest endpoint that can answer the question, then widen only if you need canonical RPC detail or indexed history. - -## When to start here - -- The user wants balances, holdings, staking, or a broad wallet-style account summary. -- You need to resolve a public key to one or more accounts. -- The answer should look like application data, not raw JSON-RPC envelopes. -- You want a fast first answer before deciding whether canonical RPC detail is necessary. - -## Minimum inputs - -- network: mainnet or testnet -- primary identifier: `account_id` or public key -- whether the user wants a broad summary or one specific asset family -- whether you may need exact canonical follow-up or recent activity history afterward - -## Common jobs - -### Get a wallet-style account summary - -**Start here** - -- [V1 Full Account View](/api/v1/account-full) for the broadest account snapshot. - -**Next page if needed** - -- [V1 Account FT](/api/v1/account-ft), [V1 Account NFT](/api/v1/account-nft), or [V1 Account Staking](/api/v1/account-staking) for narrower follow-up. -- [Transactions API account history](/tx/account) if the next question becomes "how did this account get here?" - -**Stop when** - -- The summary already answers the holdings or portfolio question in the shape the user wanted. - -**Widen when** - -- The user asks for exact canonical account or access-key semantics. Move to [RPC Reference](/rpc). -- The user asks for activity or execution history rather than current holdings. Move to [Transactions API](/tx). - -### Resolve a public key to one or more accounts - -**Start here** - -- [V1 Public Key Lookup](/api/v1/public-key) when you want the primary account match. -- [V1 Public Key Lookup All](/api/v1/public-key-all) when you need the broader set of associated accounts. - -**Next page if needed** - -- [V1 Full Account View](/api/v1/account-full) after resolution if the user immediately wants balances or holdings for the returned accounts. - -**Stop when** - -- You have identified the account or accounts that belong to the key. - -**Widen when** - -- The user starts asking about exact access-key permissions, nonces, or canonical key state. Move to [View Access Key](/rpc/account/view-access-key) or [View Access Key List](/rpc/account/view-access-key-list). -- The user wants recent activity for the resolved accounts rather than just identity resolution. Move to [Transactions API](/tx). - -### Follow one asset family instead of the whole account - -**Start here** - -- [V1 Account FT](/api/v1/account-ft) for fungible-token balances. -- [V1 Account NFT](/api/v1/account-nft) for NFT holdings. -- [V1 Account Staking](/api/v1/account-staking) for staking positions. - -**Next page if needed** - -- [V1 Full Account View](/api/v1/account-full) if the user later wants the broader account picture. -- [Transactions API account history](/tx/account) if the user asks how those holdings changed over time. - -**Stop when** - -- The asset-specific endpoint already answers the product question without extra reconstruction. - -**Widen when** - -- The indexed view is not enough and the user needs exact on-chain semantics. Move to [RPC Reference](/rpc). -- The question becomes historical or execution-oriented instead of "what does this account hold now?" Move to [Transactions API](/tx). - ## Worked walkthroughs ### Resolve a public key, then fetch the account snapshot @@ -100,7 +18,7 @@ Use this when you have a public key first and the next user-facing question is - Resolve the public key to one or more account IDs. - Extract the first matching account ID with `jq`. -- Reuse that value in the broad account snapshot endpoint. +- Reuse that value in the full account snapshot endpoint. ```bash API_BASE_URL=https://api.fastnear.com @@ -130,7 +48,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Why this next step?** -The public-key lookup answers identity. The full account snapshot answers the next practical question with product-shaped data. If the key maps to multiple accounts instead of one, widen to [V1 Public Key Lookup All](/api/v1/public-key-all) or loop through each returned `account_id`. +The public-key lookup tells you which account you are dealing with. The full account snapshot is the natural next read when you want balances, NFTs, staking, and pools in one response. If the key maps to multiple accounts instead of one, move to [V1 Public Key Lookup All](/api/v1/public-key-all) or loop through each returned `account_id`. ### Check collection membership, then mint a derivative NFT @@ -150,7 +68,7 @@ Before you start, make sure the account already holds at least one token from `n **What you're doing** - Use FastNear API to answer the gate question quickly. -- Widen to RPC `nft_tokens_for_owner` to recover exact token IDs and metadata from the source collection. +- Switch to RPC `nft_tokens_for_owner` to recover exact token IDs and metadata from the source collection. - Build deterministic derived metadata from that source set. - Mint the derivative token, then verify it with the same NFT view method. @@ -179,7 +97,7 @@ jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ }' /tmp/testnet-account-nfts.json ``` -2. Widen to canonical RPC so you can recover exact token IDs and source metadata from that collection. +2. Switch to RPC so you can recover exact token IDs and source metadata from that collection. ```bash NFT_TOKENS_ARGS_BASE64="$( @@ -260,7 +178,7 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Verify the new token with the same canonical NFT view method. +5. Verify the new token with the same NFT view method. Poll a few times instead of assuming failure if the token does not appear immediately after the mint transaction returns. @@ -300,7 +218,7 @@ jq '.' /tmp/derivative-token-verification.json **Why this next step?** -FastNear API is the fastest way to answer the gate question. Once the user qualifies, RPC becomes the right surface for exact token-level inspection and verification because it exposes the source collection’s canonical NFT view methods directly. +FastNear API is the quick gate check. Once the account qualifies, RPC is the right next step because that is where you can read exact token IDs and call the collection's own NFT views. ### Am I locked or liquid? @@ -378,10 +296,74 @@ jq -n \ If the classification is `direct_only`, the next operational question is usually about unstake and withdraw timing. If it is `liquid_only`, the next question is usually about redeeming or swapping the liquid token. If it is `mixed`, you should treat those as two separate exit paths rather than assuming one flow covers both. +## Common jobs + +### What does this account actually hold right now? + +**Start here** + +- [V1 Full Account View](/api/v1/account-full) when you want the fastest readable answer to “what is in this account right now?” + +**Next page if needed** + +- [V1 Account FT](/api/v1/account-ft), [V1 Account NFT](/api/v1/account-nft), or [V1 Account Staking](/api/v1/account-staking) if the broad summary is useful but you now want to stay on just one asset family. +- [Transactions API account history](/tx/account) if the next question becomes “how did this account get here?” instead of “what does it hold?” + +**Stop when** + +- The summary already answers the holdings question in one response. + +**Switch when** + +- The user asks for exact account state, access-key semantics, or protocol-native fields. Move to [RPC Reference](/rpc). +- The user asks for activity or execution history rather than current holdings. Move to [Transactions API](/tx). + +### Resolve a public key to one or more accounts + +**Start here** + +- [V1 Public Key Lookup](/api/v1/public-key) when you want the primary account match. +- [V1 Public Key Lookup All](/api/v1/public-key-all) when you need the full set of associated accounts. + +**Next page if needed** + +- [V1 Full Account View](/api/v1/account-full) after resolution if the user immediately wants balances or holdings for the returned accounts. + +**Stop when** + +- You have identified the account or accounts that belong to the key. + +**Switch when** + +- The user starts asking about exact access-key permissions, nonces, or current key state. Move to [View Access Key](/rpc/account/view-access-key) or [View Access Key List](/rpc/account/view-access-key-list). +- The user wants recent activity for the resolved accounts rather than just identity resolution. Move to [Transactions API](/tx). + +### Does this account hold FTs, NFTs, or staking positions? + +**Start here** + +- [V1 Account FT](/api/v1/account-ft) when the question is just about fungible-token balances. +- [V1 Account NFT](/api/v1/account-nft) when the question is specifically about NFT holdings. +- [V1 Account Staking](/api/v1/account-staking) when the question is really about staking positions, not the whole wallet picture. + +**Next page if needed** + +- [V1 Full Account View](/api/v1/account-full) if the user later wants the whole account picture after starting from one asset family. +- [Transactions API account history](/tx/account) if the user stops asking “what does this account hold?” and starts asking how it got there. + +**Stop when** + +- The asset-specific endpoint already answers the holdings question without making you rebuild the whole account picture. + +**Switch when** + +- The indexed view is not enough and the user needs the exact on-chain answer. Move to [RPC Reference](/rpc). +- The question becomes historical or execution-oriented instead of “what does this account hold now?” Move to [Transactions API](/tx). + ## Common mistakes - Leading with the broad account snapshot when the user only asked about one asset family. -- Using FastNear API when the user explicitly needs canonical RPC fields or permissions. +- Using FastNear API when the user explicitly needs exact RPC fields or permissions. - Staying in account-summary pages after the question turns into transaction history. - Forgetting that `?network=testnet` works only on compatible pages. diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index a7f8aaa..256d3eb 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -2,29 +2,92 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: KV FastData Examples -description: Plain-language workflows for using KV FastData docs for exact keys, key history, predecessor-scoped inspection, and canonical RPC follow-up. +description: Plain-language workflows for checking exact storage keys, following indexed write history, and confirming current state with RPC. displayed_sidebar: kvFastDataSidebar page_actions: - markdown --- -# KV FastData Examples +## Worked investigation + +### Check one contract key, then follow its history + +Use this investigation when one contract storage key looks suspicious and you want the latest indexed value, the write history for that same key, and one final `view_state` check. + +**Goal** + +- Explain what this storage key looks like in the index, how it changed, and whether `view_state` agrees right now. + +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Latest indexed value | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Fetch the newest indexed row for the exact key first | Gives the fastest narrow answer before widening into history | +| Indexed key history | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) or [`history-by-key`](/fastdata/kv/history-by-key) | Pull the same key’s change history over time | Shows whether the current value is stable, recent, or part of a suspicious sequence | +| Broader write pattern | KV FastData [`latest-by-account`](/fastdata/kv/latest-by-account) or [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Check the account or predecessor if the one key is only part of a larger pattern | Helps explain whether the key changed by itself or as part of a bigger write set | +| Exact state check | RPC [`view_state`](/rpc/contract/view-state) | Confirm the current on-chain state once the indexed pattern is clear | Separates indexed storage history from the exact state the chain would return now | -Use this page when the question is about indexed contract storage and you already have a precise scope in mind. The key decision on this surface is choosing the narrowest useful scope first: exact key, account, predecessor, or batch of known keys. Stay within KV FastData while the answer is still about indexed key-value data, then widen to RPC only when canonical on-chain state is required. +**What a useful answer should include** -## When to start here +- the exact key and contract scope investigated +- the latest indexed value and what changed in history +- whether `view_state` matched the indexed current value -- You want indexed contract storage instead of broad account or asset views. -- You already know a contract, exact key, predecessor, or account scope. -- You need latest indexed rows or indexed history over time. -- You want faster storage-oriented answers before deciding whether canonical RPC state is necessary. +### Shell walkthrough -## Minimum inputs +Use this when one fully qualified key is already known and you want to move cleanly from “what is the latest indexed row?” to “how did this exact key get here?” -- network -- contract ID plus one of: exact key, account scope, predecessor scope, or known set of keys -- whether you need latest indexed state or historical changes -- whether canonical follow-up may be required +**What you're doing** + +- Read one latest indexed key with the exact contract, predecessor, and key path. +- Extract the exact `key` with `jq`. +- Reuse that key in `POST /v0/history` to pull the write history for the same key. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +EXACT_KEY="$( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | tee /tmp/kv-latest.json \ + | jq -r '.entries[0].key' +)" + +jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' /tmp/kv-latest.json + +curl -s "$KV_BASE_URL/v0/history" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ + | jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + value + } + ] + }' +``` + +**Why this next step?** + +The first lookup answers “what do we have right now?” Reusing the exact `key` in `POST /v0/history` answers “how did it get here?” If that result is too broad, narrow back down with [GET History by Exact Key](/fastdata/kv/get-history-key). ## Common jobs @@ -42,9 +105,9 @@ Use this page when the question is about indexed contract storage and you alread - The latest indexed row already answers the storage question. -**Widen when** +**Switch when** -- The user needs exact current chain state rather than indexed storage. Move to [View State](/rpc/contract/view-state). +- The user needs the exact current chain state rather than the index. Move to [View State](/rpc/contract/view-state). ### Turn one exact key into a change history @@ -61,9 +124,9 @@ Use this page when the question is about indexed contract storage and you alread - You can explain how the key changed over time. -**Widen when** +**Switch when** -- The user asks whether the latest indexed value matches canonical on-chain state right now. +- The user asks whether the latest indexed value matches the chain right now. ### Trace writes from one predecessor @@ -80,9 +143,9 @@ Use this page when the question is about indexed contract storage and you alread - You can answer what this predecessor changed and where. -**Widen when** +**Switch when** -- The user stops asking about indexed writes and starts asking about present canonical state. +- The user stops asking about indexed writes and starts asking about the current chain state. ### Batch-check several known keys @@ -98,90 +161,9 @@ Use this page when the question is about indexed contract storage and you alread - The batch response already answers which of the keys matter. -**Widen when** - -- The user wants broader contract inspection instead of a known set of keys. - -## Worked investigation - -### Start with one indexed key, then confirm history and canonical state - -Use this investigation when one contract key looks suspicious and you need to connect its latest indexed value, indexed history, and canonical `view_state` follow-up into one clear story. - -**Goal** - -- Explain what a contract key looks like now, how it got there in indexed history, and whether the canonical RPC state agrees. - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Latest indexed value | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Fetch the newest indexed row for the exact key first | Gives the fastest narrow answer before widening into history | -| Indexed key history | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) or [`history-by-key`](/fastdata/kv/history-by-key) | Pull the same key’s change history over time | Shows whether the current value is stable, recent, or part of a suspicious sequence | -| Scope expansion | KV FastData [`latest-by-account`](/fastdata/kv/latest-by-account) or [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Widen to account or predecessor scope if the one key is only part of a larger pattern | Helps explain whether the key changed in isolation or as part of a wider write set | -| Canonical confirmation | RPC [`view_state`](/rpc/contract/view-state) | Confirm the current on-chain state once the indexed pattern is clear | Separates indexed storage history from exact current chain state | - -**What a useful answer should include** - -- the exact key and contract scope investigated -- the latest indexed value and what changed in history -- whether canonical `view_state` matched the indexed current value - -### Shell walkthrough - -Use this when one fully qualified key is already known and you want to move cleanly from “what is the latest indexed row?” to “what is the broader indexed history for this key?” - -**What you're doing** - -- Read one latest indexed key with the exact contract, predecessor, and key path. -- Extract the exact `key` with `jq`. -- Reuse that key in `POST /v0/history` to widen into history. - -```bash -KV_BASE_URL=https://kv.main.fastnear.com -CURRENT_ACCOUNT_ID=social.near -PREDECESSOR_ID=james.near -KEY='graph/follow/sleet.near' - -ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" - -EXACT_KEY="$( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | tee /tmp/kv-latest.json \ - | jq -r '.entries[0].key' -)" - -jq '{ - latest: ( - .entries[0] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ) -}' /tmp/kv-latest.json - -curl -s "$KV_BASE_URL/v0/history" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ - | jq '{ - page_token, - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - value - } - ] - }' -``` - -**Why this next step?** +**Switch when** -The latest lookup gives the narrowest possible answer. Reusing the exact `key` in `POST /v0/history` shows whether that key also appears in a wider indexed pattern. If that result is too broad, narrow back down with [GET History by Exact Key](/fastdata/kv/get-history-key). +- You no longer have a fixed key list and need to inspect the contract or predecessor more broadly. ## Common mistakes diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index d12d47d..e72ea29 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -1,31 +1,82 @@ --- sidebar_label: Examples slug: /neardata/examples -title: NEAR Data API Examples -description: Plain-language workflows for using NEAR Data API docs for polling, redirect helpers, and escalation to canonical RPC inspection. +title: NEAR Data Examples +description: Plain-language workflows for polling optimistic and finalized blocks and handing off to RPC when needed. displayed_sidebar: nearDataApiSidebar page_actions: - markdown --- -# NEAR Data API Examples +## Worked investigation + +### Catch a new block early, then confirm it after finality + +Use this investigation when you want to notice a new block as early as possible, but the final answer still needs a finalized block and sometimes an exact RPC read. + +**Goal** + +- Notice a recent block quickly, then check the same thing again once finality catches up. + +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Fastest detection | NEAR Data [`block-optimistic`](/neardata/block-optimistic) | Poll optimistic block reads to notice a new block-family change as early as possible | Gives the earliest useful signal before finalized confirmation exists | +| Latest optimistic helper | NEAR Data [`last-block-optimistic`](/neardata/last-block-optimistic) | Use the redirect helper when the client should always follow the newest optimistic target | Keeps the polling client simple when “latest” matters more than explicit heights | +| Stable confirmation | NEAR Data [`block`](/neardata/block) or [`last-block-final`](/neardata/last-block-final) | Re-check the same block family once finality catches up | Confirms that the observed optimistic change survived into finalized history | +| Light block summary | NEAR Data [`block-headers`](/neardata/block-headers) | Read header-level data if only timing or progression is needed | Avoids wider block payloads when header-level confirmation is enough | +| Exact RPC follow-up | RPC [Block by ID](/rpc/block/block-by-id) or [Block by Height](/rpc/block/block-by-height) | Fetch the exact block once you know which one matters | This is the point where RPC becomes useful if you need the protocol's own block object | + +**What a useful answer should include** + +- which optimistic observation first triggered the investigation +- when the same observation became finalized +- whether the exact RPC block changed the interpretation + +### Shell walkthrough + +Use this when you want the helper route to pick the latest finalized block for you, but you still want to confirm the exact block in RPC. + +**What you're doing** + +- Inspect the redirect returned by `GET /v0/last_block/final`. +- Fetch the resolved block document. +- Extract `block.header.height` with `jq`. +- Reuse that height in RPC `block` by height. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +RPC_URL=https://rpc.mainnet.fastnear.com -Use this page when freshness matters more than protocol-native exactness. NEAR Data API is for polling and recent block-family reads: start with the freshest or most stable block mode that matches the job, stay on the polling-oriented surface as long as it answers the question, and widen to RPC only when canonical block or state semantics become necessary. +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" -## When to start here +printf 'Redirect target: %s\n' "$FINAL_LOCATION" -- You want recent optimistic or finalized block-family data. -- You are building a polling client, monitor, or freshness check. -- Redirect helpers are acceptable or useful in your client flow. -- The job is about “what changed recently?” rather than canonical historical confirmation. +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | tee /tmp/neardata-final-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + +BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "block", + params: { + block_id: ($block_height | tonumber) + } + }')" \ + | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +``` -## Minimum inputs +**Why this next step?** -- network -- freshness mode: optimistic or finalized -- whether you have a specific height/hash or want the latest block-family object -- whether the client can follow redirects cleanly -- whether a later RPC follow-up may be required +The redirect helper is the easiest way to poll for “latest finalized.” Once it gives you a concrete block height, RPC is the natural next read if you want the exact block object the protocol would return. ## Common jobs @@ -43,7 +94,7 @@ Use this page when freshness matters more than protocol-native exactness. NEAR D - You can report the latest optimistic head or detect freshness drift. -**Widen when** +**Switch when** - The user needs finalized stability instead of maximum freshness. Move to [Final block by height](/neardata/block) or [Last final block redirect](/neardata/last-block-final). @@ -62,9 +113,9 @@ Use this page when freshness matters more than protocol-native exactness. NEAR D - You can show finalized progress without pulling in deeper protocol detail. -**Widen when** +**Switch when** -- The user needs exact canonical block fields or transaction semantics. Move to [RPC Reference](/rpc). +- The user needs exact block fields or transaction semantics. Move to [RPC Reference](/rpc). ### Use redirect helpers in a polling client @@ -74,17 +125,17 @@ Use this page when freshness matters more than protocol-native exactness. NEAR D **Next page if needed** -- Follow the canonical target returned by the helper and continue reading the block-family payload there. +- Follow the block URL returned by the helper and keep reading from there. **Stop when** - The client can reliably follow the helper route and consume the final block resource. -**Widen when** +**Switch when** - Redirect behavior itself becomes a problem for the client. Move to the direct block routes instead. -### Escalate from fresh block polling to canonical RPC inspection +### Move from recent block polling to exact RPC inspection **Start here** @@ -96,86 +147,16 @@ Use this page when freshness matters more than protocol-native exactness. NEAR D **Stop when** -- You can clearly name the recent block that deserves canonical follow-up. - -**Widen when** - -- The user asks for exact protocol-native structure, not just freshness-oriented reads. - -## Worked investigation - -### Start with an optimistic block, then confirm the finalized and canonical story - -Use this investigation when you need early detection from the optimistic head, but the final answer still needs a stable finalized view and, sometimes, canonical RPC confirmation. - -**Goal** - -- Catch a recent change quickly, then narrow it into a finalized and canonical block story without overfetching. - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Fastest detection | NEAR Data [`block-optimistic`](/neardata/block-optimistic) | Poll optimistic block reads to notice a new block-family change as early as possible | Gives the earliest useful signal before finalized confirmation exists | -| Latest optimistic helper | NEAR Data [`last-block-optimistic`](/neardata/last-block-optimistic) | Use the redirect helper when the client should always follow the newest optimistic target | Keeps the polling client simple when “latest” matters more than explicit heights | -| Stable confirmation | NEAR Data [`block`](/neardata/block) or [`last-block-final`](/neardata/last-block-final) | Re-check the same block family once finality catches up | Confirms that the observed optimistic change survived into finalized history | -| Light block summary | NEAR Data [`block-headers`](/neardata/block-headers) | Read header-level data if only timing or progression is needed | Avoids wider block payloads when header-level confirmation is enough | -| Canonical follow-up | RPC [Block by ID](/rpc/block/block-by-id) or [Block by Height](/rpc/block/block-by-height) | Fetch the exact canonical block once you know which one matters | Moves from freshness-oriented reads to protocol-native confirmation only when necessary | - -**What a useful answer should include** - -- which optimistic observation first triggered the investigation -- when the same observation became finalized -- whether canonical RPC inspection changed the interpretation - -### Shell walkthrough - -Use this when you want the polling helper to choose the latest finalized block for you, but the follow-up still needs canonical RPC confirmation. - -**What you're doing** - -- Inspect the redirect returned by `GET /v0/last_block/final`. -- Fetch the resolved block document. -- Extract `block.header.height` with `jq`. -- Reuse that height in RPC `block` by height. - -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -RPC_URL=https://rpc.mainnet.fastnear.com - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -printf 'Redirect target: %s\n' "$FINAL_LOCATION" - -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' - -BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" +- You can clearly name the recent block that deserves RPC follow-up. -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "block", - params: { - block_id: ($block_height | tonumber) - } - }')" \ - | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' -``` - -**Why this next step?** +**Switch when** -The redirect helper is the easiest polling surface for “latest finalized.” Once it tells you the exact block height, RPC becomes the right place to ask for canonical block semantics without guessing which block to inspect. +- The user asks for the exact protocol structure, not just recent block polling. ## Common mistakes -- Treating NEAR Data API as a streaming product instead of a polling surface. -- Starting with canonical RPC when the real need is a recent block monitor. +- Treating NEAR Data like a push stream instead of a polling API. +- Starting with RPC when the real need is a recent block monitor. - Forgetting that redirect helpers may return `401` before redirecting if the key is invalid, or may be awkward for some HTTP clients. - Staying on NEAR Data when the user has already asked for exact protocol-native block details. diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index 29eda05..8626326 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -10,110 +10,7 @@ page_actions: # RPC Examples -Use this page when you already know the answer needs canonical RPC behavior and you want the shortest doc path to get there. The goal is not to memorize every method. It is to pick the right starting page, stop as soon as the RPC result answers the question, and widen only when a higher-level surface would help. - -## When to start here - -- The user asked for exact on-chain state or protocol-native fields. -- You need a direct contract view call or transaction submission flow. -- You are inspecting blocks, chunks, validators, or protocol metadata. -- Correctness depends on node semantics rather than indexed summary data. - -## Minimum inputs - -- network: mainnet or testnet -- primary identifier: `account_id`, public key, contract ID plus method, transaction hash, or block height/hash -- whether you need current state, historical state, or submission/finality behavior -- whether the result should stay canonical or become a human-friendly summary afterward - -## Common jobs - -### Check exact account or access-key state - -**Start here** - -- [View Account](/rpc/account/view-account) for canonical account fields. -- [View Access Key](/rpc/account/view-access-key) or [View Access Key List](/rpc/account/view-access-key-list) for key inspection. - -**Next page if needed** - -- [FastNear API full account view](/api/v1/account-full) if you need a wallet-style summary after confirming the canonical state. -- [Transactions API account history](/tx/account) if the next question is "what has this account been doing?" - -**Stop when** - -- The RPC fields already answer the state or permission question. - -**Widen when** - -- The user wants balances, NFTs, staking, or other product-shaped output. -- The user really wants recent activity history rather than current canonical state. - -### Inspect a block or protocol snapshot - -**Start here** - -- [Block by ID](/rpc/block/block-by-id) or [Block by Height](/rpc/block/block-by-height) for a specific block. -- [Latest Block](/rpc/protocol/latest-block) for the current canonical head. -- [Status](/rpc/protocol/status), [Health](/rpc/protocol/health), or [Network Info](/rpc/protocol/network-info) for node and network diagnostics. - -**Next page if needed** - -- [Block Effects](/rpc/block/block-effects) if the block lookup needs state-change context. -- [Transactions API block history](/tx/block) or [Transactions API block range](/tx/blocks) if you need a more readable execution window. - -**Stop when** - -- The canonical block or protocol payload answers the question directly. - -**Widen when** - -- The user wants recent polling-oriented block data instead of one canonical snapshot. Move to [NEAR Data API](/neardata). -- The user needs a history story across many transactions, not just one block payload. Move to [Transactions API](/tx). - -### Run a contract view call - -**Start here** - -- [Call Function](/rpc/contract/call-function) for a contract view method. -- [View State](/rpc/contract/view-state) when the question is about raw contract storage. -- [View Code](/rpc/contract/view-code) if code presence or hash is the real question. - -**Next page if needed** - -- [FastNear API](/api) if the user actually wants a product-shaped answer such as holdings or account summary after the raw call. -- [KV FastData API](/fastdata/kv) if the next task is indexed key-value history rather than an exact RPC state read. - -**Stop when** - -- The contract view result already answers the question in canonical form. - -**Widen when** - -- The user wants indexed history or a simpler summary instead of raw contract output. -- The user starts asking "what changed over time?" rather than "what does it return right now?" - -### Send and confirm a transaction - -**Start here** - -- [Send Transaction](/rpc/transaction/send-tx) when you want canonical submission behavior with explicit waiting semantics. -- [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) or [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit) when those exact submission modes are the point. -- [Transaction Status](/rpc/transaction/tx-status) to confirm the canonical result. - -**Next page if needed** - -- [Transactions by Hash](/tx/transactions) for a readable history record after submission. -- [Receipt Lookup](/tx/receipt) when you need to investigate downstream execution or callback flow. - -**Stop when** - -- You have the submission result and final canonical status you needed. - -**Widen when** - -- The next question is about receipts, affected accounts, or execution history in a human-friendly order. -- You need a broader investigation workflow instead of one canonical status check. +Use this page when you already know the answer lives in RPC and you want the shortest path to it. The goal is not to memorize every method. It is to start with the right RPC read or write, stop as soon as the response answers the question, and only switch to a higher-level API when that would save time. ## Worked walkthroughs @@ -123,7 +20,7 @@ Use this when you know an account has accumulated older `social.near` function-c **What you're doing** -- Use canonical RPC to list every access key on the account. +- Use RPC itself to list every access key on the account. - Narrow that list to function-call keys scoped to `social.near`. - Inspect one candidate key exactly before you delete it. - Build and sign a `DeleteKey` transaction with a full-access key, then submit it through RPC and verify the key is gone. @@ -358,7 +255,7 @@ fi **Why this next step?** -Re-running `view_access_key_list` closes the loop on the same canonical surface you used for discovery. If the delete succeeded there, you do not need a higher-level indexed summary to prove the cleanup. +Re-running `view_access_key_list` closes the loop on the same RPC method you used for discovery. If the delete succeeded there, you do not need an indexed API to prove the cleanup. ### Register FT storage if needed, then transfer tokens @@ -377,10 +274,10 @@ This walkthrough uses the safe public contract `ft.predeployed.examples.testnet` **What you're doing** -- Use canonical RPC view calls to check whether the receiver already has FT storage on the contract. +- Use exact RPC view calls to check whether the receiver already has FT storage on the contract. - If needed, fetch the minimum storage requirement. - Sign and submit `storage_deposit`, then `ft_transfer`. -- Verify the receiver balance with the same contract’s canonical view method. +- Verify the receiver balance with the same contract’s own view method. ```bash export NETWORK_ID=testnet @@ -627,7 +524,7 @@ curl -s "$RPC_URL" \ }' ``` -6. Verify the receiver’s FT balance with the contract’s canonical view method. +6. Verify the receiver’s FT balance with the contract’s own view method. ```bash RECEIVER_BALANCE_ARGS_BASE64="$( @@ -659,7 +556,422 @@ curl -s "$RPC_URL" \ **Why this next step?** -This is a canonical RPC example because every step stays on exact contract state and exact transaction submission semantics: first prove storage state, then submit the minimum required change calls, then verify the post-transfer state directly on the contract. +This is a good RPC example because every step stays close to the contract itself: first check storage state, then send the minimum required change calls, then verify the post-transfer balance directly on the contract. + +### Can this account still publish to NEAR Social right now? + +Use this when the user story is “I’m about to publish a profile change, widget update, or graph write under `mike.near`, and I want a plain go/no-go answer before I open wallet signing.” + +This is the same question real NEAR Social clients have to answer before they try a write: + +- does the target account already have storage on `social.near`? +- if it does, is there still room left in that storage? +- if a different signer is trying to write under that account, has write permission already been granted? + +**Official references** + +- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) + +**What you're doing** + +- Check that the signer account itself exists and can pay gas. +- Ask `social.near` how much storage the target account has left. +- If the signer differs from the target account, ask `social.near` whether that delegated write is already allowed. +- Turn those exact RPC answers into one simple “ready now” or “fix this first” summary. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mike.near +export SIGNER_ACCOUNT_ID=mike.near +``` + +1. Check the signer account itself first. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/social-publish-signer.json >/dev/null + +jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + signer_account_id: $signer_account_id, + amount: .result.amount, + locked: .result.locked, + storage_usage: .result.storage_usage +}' /tmp/social-publish-signer.json +``` + +If this query fails, you do not have a signer account to work with. If it succeeds, you know the signer exists and can at least pay gas. + +2. Ask `social.near` how much storage is already available for the account you want to write under. + +```bash +SOCIAL_STORAGE_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_account_storage", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-account-storage.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + storage: (.result.result | implode | fromjson), + storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) +}' /tmp/social-account-storage.json +``` + +If `available_bytes` is greater than zero, storage is not the blocker. If this method returns `null` or `available_bytes` is zero, the account needs a `storage_deposit` top-up before a new write can land. + +3. If the signer is different from the target account, check delegated write permission too. + +```bash +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + jq -n --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + signer_matches_target: true, + permission_granted: true, + reason: "owner write" + }' +else + WRITE_PERMISSION_ARGS_BASE64="$( + jq -nc \ + --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ + --arg key "$ACCOUNT_ID" '{ + predecessor_id: $predecessor_id, + key: $key + }' | base64 | tr -d '\n' + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + signer_matches_target: false, + permission_granted: (.result.result | implode | fromjson) + }' +fi +``` + +4. Turn the storage and permission checks into one readable answer. + +```bash +AVAILABLE_BYTES="$( + jq -r ' + .result.result + | if length == 0 then "0" + else (implode | fromjson | .available_bytes // 0 | tostring) + end + ' /tmp/social-account-storage.json +)" + +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + PERMISSION_GRANTED=true +else + PERMISSION_GRANTED="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq -r '.result.result | implode | fromjson' + )" +fi + +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ + --argjson available_bytes "$AVAILABLE_BYTES" \ + --argjson permission_granted "$PERMISSION_GRANTED" '{ + account_id: $account_id, + signer_account_id: $signer_account_id, + storage_ready: ($available_bytes > 0), + permission_ready: $permission_granted, + ready_to_publish_now: (($available_bytes > 0) and $permission_granted) + }' +``` + +If that final object says `ready_to_publish_now: true`, RPC has already answered the question. If it says `false`, you know whether the blocker is storage, delegated permission, or both. + +**Why this next step?** + +This keeps the whole question on exact on-chain reads. `social.near` itself answers whether the target account has room left and whether a delegated signer is already allowed to write. That is a better NEAR Social readiness check than guessing from wallet state alone. + +### Did `efiz.near` really publish `DonateNEARtoEfiz`, and what does it do? + +Use this when the user story is lighter and more playful: “my friend says `efiz.near` once published a widget literally called `DonateNEARtoEfiz`. Check whether that is true, then show me what the widget actually does without leaving RPC.” + +This one is intentionally fun. It does not teach anything deep about async execution. It just shows how to use exact SocialDB reads to browse a BOS author's catalog and answer one very specific question from live on-chain data. + +**Official references** + +- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) + +**What you're doing** + +- Ask `social.near` for the widget catalog under `efiz.near`. +- Keep the block heights, because they tell you when each widget key was last written. +- Confirm that `DonateNEARtoEfiz` is really there, then read its exact source code through the same contract. +- End with one clean handoff: if the next question becomes “which transaction wrote this widget?”, switch to the NEAR Social proof recipes in [Transactions Examples](/tx/examples). + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=efiz.near +export WIDGET_NAME=DonateNEARtoEfiz +``` + +1. List the widget catalog and keep the last-write block heights. + +```bash +WIDGET_KEYS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "keys", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-keys.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].widget + | to_entries + | sort_by(.value * -1) + | map({ + widget_name: .key, + last_write_block: .value + }) + | .[0:20] +' /tmp/social-widget-keys.json +``` + +That gives you a compact BOS inventory. At the time of writing, `efiz.near` had a wonderfully eclectic widget catalog including names like `ReversedFeed`, `HelloWorld`, `PotlockDonateAll`, and `DonateNEARtoEfiz`, but the live query is the real source of truth. + +2. Confirm that `DonateNEARtoEfiz` is really in the catalog, then print the exact source stored in SocialDB. + +```bash +WIDGET_GET_ARGS_BASE64="$( + jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-source.json >/dev/null + +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:25] + | join("\n") + ' /tmp/social-widget-source.json +``` + +That prints the first 25 lines of the widget source so you can quickly tell what kind of component it is. In the live version at the time of writing, the source initializes `reciever: "efiz.near"` and builds a button that calls `donate` with the chosen amount. The widget name is not subtle. + +3. Pull the last-write block for the same widget so you keep one useful historical anchor. + +```bash +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + ' /tmp/social-widget-keys.json \ + | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +``` + +At the time of writing, the live last-write block for `efiz.near/widget/DonateNEARtoEfiz` was `92543301`. + +If your next question becomes “which transaction wrote that version of the widget?”, keep that block height and switch to the NEAR Social proof workflows in [Transactions Examples](/tx/examples). + +**Why this next step?** + +This is a nice reminder that RPC can be fun, not just forensic. `keys` lets you browse a BOS author's catalog like a developer, and `get` lets you inspect the exact widget body that lives on chain. Sometimes the answer really is “yes, your friend did publish a widget called `DonateNEARtoEfiz`, and here is the code.” + +## Common jobs + +### Check exact account or access-key state + +**Start here** + +- [View Account](/rpc/account/view-account) for exact account fields. +- [View Access Key](/rpc/account/view-access-key) or [View Access Key List](/rpc/account/view-access-key-list) for key inspection. + +**Next page if needed** + +- [FastNear API full account view](/api/v1/account-full) if you want a readable holdings summary after checking the exact RPC state. +- [Transactions API account history](/tx/account) if the next question is "what has this account been doing?" + +**Stop when** + +- The RPC fields already answer the state or permission question. + +**Switch when** + +- The user wants balances, NFTs, staking, or another readable account summary. +- The user really wants recent activity history rather than current state. + +### Check one exact block or protocol snapshot + +**Start here** + +- [Block by ID](/rpc/block/block-by-id) or [Block by Height](/rpc/block/block-by-height) when you already know which block you care about. +- [Latest Block](/rpc/protocol/latest-block) when the question is “what is the current head right now?” +- [Status](/rpc/protocol/status), [Health](/rpc/protocol/health), or [Network Info](/rpc/protocol/network-info) when the real question is about node or network condition, not transaction history. + +**Next page if needed** + +- [Block Effects](/rpc/block/block-effects) if the block payload tells you what block you are looking at but not what changed in it. +- [Transactions API block history](/tx/block) or [Transactions API block range](/tx/blocks) if the question becomes “what actually happened around this block?” rather than “what does this block payload say?” + +**Stop when** + +- One exact block or protocol response already answers the question directly. + +**Switch when** + +- The user wants to watch fresh blocks arrive rather than inspect one exact snapshot. Move to [NEAR Data API](/neardata). +- The user needs a readable story across many transactions, not just one block payload. Move to [Transactions API](/tx). + +### What does this contract return right now? + +**Start here** + +- [Call Function](/rpc/contract/call-function) when you already know the view method you want and just need the exact return value. +- [View State](/rpc/contract/view-state) when the real question is about raw contract storage or key prefixes, not a method result. +- [View Code](/rpc/contract/view-code) when the real question is “is there code here at all?” or “which code hash is deployed?” + +**Next page if needed** + +- [FastNear API](/api) if the raw contract answer is technically correct but the user actually wanted a readable holdings or account summary. +- [KV FastData API](/fastdata/kv) if the next question becomes “what did this storage key look like over time?” instead of “what is it right now?” + +**Stop when** + +- The view call, storage read, or code hash already answers the contract question exactly. + +**Switch when** + +- The user wants indexed history or a simpler summary instead of raw contract output. +- The user stops asking “what does it return right now?” and starts asking “what changed over time?” + +### Send and confirm a transaction + +**Start here** + +- [Send Transaction](/rpc/transaction/send-tx) when you want RPC submission with explicit waiting semantics. +- [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) or [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit) when those exact submission modes are the point. +- [Transaction Status](/rpc/transaction/tx-status) to confirm the final result. + +**Next page if needed** + +- [Transactions by Hash](/tx/transactions) for a readable history record after submission. +- [Receipt Lookup](/tx/receipt) when you need to investigate downstream execution or callback flow. +- [Transactions Examples](/tx/examples) when the next question is “one batched action failed, did the earlier actions roll back too?” + +**Stop when** + +- You have the submission result and final status you needed. + +**Switch when** + +- The next question is about receipts, affected accounts, or execution history in a human-friendly order. +- You need a fuller investigation workflow instead of one status check. ## Common mistakes diff --git a/docs/snapshots/examples.mdx b/docs/snapshots/examples.mdx index 83c4cae..059f5a1 100644 --- a/docs/snapshots/examples.mdx +++ b/docs/snapshots/examples.mdx @@ -1,32 +1,65 @@ --- sidebar_label: Examples -sidebar_position: 3 slug: /snapshots/examples title: Snapshot Examples -description: Plain-language operator workflows for choosing the right FastNear snapshot path and executing common bootstrap and recovery flows. +description: Plain-language operator workflows for choosing the right FastNear snapshot recovery path. displayed_sidebar: snapshotsSidebars page_actions: - markdown --- -# Snapshot Examples +## Worked investigation + +### Choose and execute the right mainnet recovery path + +Use this investigation when an operator says “I need this node back online” and you need to decide whether the right path is optimized `fast-rpc`, standard RPC, or archival hot/cold recovery. + +**Goal** + +- Turn a vague recovery request into the right mainnet snapshot path and the minimum command sequence to get moving safely. + +| Path or command | How we use it | Why we use it | +| --- | --- | --- | +| Mainnet optimized `fast-rpc` path | Choose it first when the goal is the fastest high-performance RPC recovery and the node can support the optimized profile | It is the preferred fast recovery path when archival retention is not required | +| Mainnet standard RPC path | Use it when the operator needs a simpler RPC recovery without the optimized profile | It gives a straightforward default recovery route into the normal nearcore data path | +| Latest archival block lookup | Fetch the latest archival snapshot height before archival recovery | Anchors the hot/cold downloads to a specific archival snapshot block | +| Archival hot-data command | Run it first and place the result on NVMe | Hot archival data must land on the fast storage tier to support the node correctly | +| Archival cold-data command | Run it after hot data and place it on the cold storage tier | Completes archival recovery without forcing all archival data onto the expensive hot tier | -Use this page when the question is “which snapshot path should I actually run?” rather than “what command exists?” Snapshots are operator workflows, not data APIs: start by choosing the correct node goal, network, and storage model, then run the narrowest download path that gets the node online safely. +**What a useful answer should include** -## When to start here +- which recovery path was chosen and why +- which critical env vars matter for the chosen path +- where data should land on disk +- whether the operator should stay in FastNear snapshot docs or move to general nearcore bootstrap docs -- You are bootstrapping or recovering a node. -- You need to choose among standard RPC, optimized `fast-rpc`, or archival hot/cold snapshot paths. -- You want the shortest route from operator intent to the right command sequence. -- You already know this is infrastructure work, not application data access. +### Shell walkthrough -## Minimum inputs +Use this when you have already decided that archival mainnet is the right path and now need the exact command sequence with one shared block anchor. -- network: mainnet or testnet -- node goal: standard RPC, optimized `fast-rpc`, or archival -- storage layout, especially whether hot and cold archival data can be separated -- whether you are using the default nearcore data path or custom hot/cold roots -- target data path and rough download constraints +**What you're doing** + +- Fetch the latest archival mainnet snapshot height once. +- Store it in `LATEST`. +- Reuse that exact block height for both the hot-data and cold-data downloads. + +```bash +HOT_DATA_PATH=~/.near/data +COLD_DATA_PATH=/mnt/hdds/cold-data + +LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" +echo "Latest archival mainnet snapshot block: $LATEST" + +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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash +``` + +**Why this next step?** + +Hot and cold archival data need to come from the same snapshot cut. Reusing one captured `LATEST` value across both commands keeps the archival dataset internally consistent and makes later nearcore configuration much less surprising. ## Common jobs @@ -44,7 +77,7 @@ Use this page when the question is “which snapshot path should I actually run? - You have the right `fast-rpc` command and environment variables for the target machine. -**Widen when** +**Switch when** - The real requirement is archival retention rather than fast sync. @@ -62,7 +95,7 @@ Use this page when the question is “which snapshot path should I actually run? - You can run the correct RPC recovery command with the expected data path. -**Widen when** +**Switch when** - The operator actually needs archival history or hot/cold data placement. @@ -80,9 +113,9 @@ Use this page when the question is “which snapshot path should I actually run? - The hot-data and cold-data plan is clear and the order of operations is correct. -**Widen when** +**Switch when** -- The operator is really looking for broader nearcore bootstrap guidance beyond FastNear snapshots. +- The operator is really looking for general nearcore bootstrap guidance beyond FastNear snapshots. ### Bootstrap testnet archival hot data @@ -98,63 +131,10 @@ Use this page when the question is “which snapshot path should I actually run? - You have the right testnet archival hot-data command and block anchor. -**Widen when** +**Switch when** - The user is not doing infrastructure bootstrap and should be routed back to API or RPC docs. -## Worked investigation - -### Choose and execute the right mainnet recovery path - -Use this investigation when an operator says “I need this node back online” and you need to quickly decide whether the correct path is optimized `fast-rpc`, standard RPC, or archival hot/cold recovery. - -**Goal** - -- Turn a vague recovery request into the right mainnet snapshot path and the minimum command sequence to start safely. - -| Path or command | How we use it | Why we use it | -| --- | --- | --- | -| Mainnet optimized `fast-rpc` path | Choose it first when the goal is the fastest high-performance RPC recovery and the node can support the optimized profile | It is the preferred fast recovery path when archival retention is not required | -| Mainnet standard RPC path | Use it when the operator needs a simpler RPC recovery without the optimized profile | It gives a straightforward default recovery route into the normal nearcore data path | -| Latest archival block lookup | Fetch the latest archival snapshot height before archival recovery | Anchors the hot/cold downloads to a specific archival snapshot block | -| Archival hot-data command | Run it first and place the result on NVMe | Hot archival data must land on the fast storage tier to support the node correctly | -| Archival cold-data command | Run it after hot data and place it on the cold storage tier | Completes archival recovery without forcing all archival data onto the expensive hot tier | - -**What a useful answer should include** - -- which recovery path was chosen and why -- which critical env vars matter for the chosen path -- where data should land on disk -- whether the operator should stay in FastNear snapshot docs or move to broader nearcore guidance - -### Shell walkthrough - -Use this when you have already decided that archival mainnet is the right path and now need the exact command sequence with one shared block anchor. - -**What you're doing** - -- Fetch the latest archival mainnet snapshot height once. -- Store it in `LATEST`. -- Reuse that exact block height for both the hot-data and cold-data downloads. - -```bash -HOT_DATA_PATH=~/.near/data -COLD_DATA_PATH=/mnt/hdds/cold-data - -LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" -echo "Latest archival mainnet snapshot block: $LATEST" - -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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash -``` - -**Why this next step?** - -Hot and cold archival data need to come from the same snapshot cut. Reusing one captured `LATEST` value across both commands keeps the archival dataset internally consistent and makes later nearcore configuration much less surprising. - ## Common mistakes - Using snapshot docs when the task is really about reading chain data. diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index 0a29029..7c0c6a7 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -1,101 +1,24 @@ --- sidebar_label: Examples slug: /transfers/examples -title: Transfers API Examples -description: Plain-language workflows for using Transfers API docs for narrow transfer history, pagination, and escalation into broader investigation. +title: Transfers Examples +description: Plain-language workflows for finding transfers, paginating with resume_token, and pivoting into transaction history. displayed_sidebar: transfersApiSidebar page_actions: - markdown --- -# Transfers API Examples - -Use this page when the question is specifically about asset movement and you want the shortest path through the transfer-history docs. This surface is intentionally narrow: start with the tightest transfer filter that answers the question, stay focused on sends and receives, and widen only when the question stops being transfer-only. - -## When to start here - -- The user cares about incoming or outgoing NEAR or FT transfers. -- You want a wallet feed, audit view, or support answer focused on asset movement. -- You already know the account and do not need the full execution story yet. -- Mainnet transfer history is enough for the task. - -## Minimum inputs - -- `account_id` -- no network choice: this surface is mainnet-only today -- optional direction, asset, amount, or time filters -- whether the answer needs only a few events or a longer history scan -- whether broader transaction context may be needed later - -## Common jobs - -### Find outgoing transfers for one account in a narrow time window - -**Start here** - -- [Query Transfers](/transfers/query) with the account, outgoing direction, and the tightest useful time filter. - -**Next page if needed** - -- Narrow again by asset or amount if the response still contains unrelated transfers. - -**Stop when** - -- You can answer who sent what, when, and in which asset. - -**Widen when** - -- The user asks why the transfer happened or what other actions surrounded it. Move to [Transactions API](/tx). - -### Build a transfer feed with `resume_token` pagination - -**Start here** - -- [Query Transfers](/transfers/query) for the first page of recent events. - -**Next page if needed** - -- Reuse the exact returned `resume_token` to fetch the next page with the same filters. - -**Stop when** - -- You have enough pages to answer the requested feed, support review, or compliance check. - -**Widen when** - -- The user asks for transaction metadata beyond transfer events. -- The feed needs balances or holdings, not just movement. Move to [FastNear API](/api). - -### Escalate from transfer-only history to full transaction investigation - -**Start here** - -- [Query Transfers](/transfers/query) to identify the specific transfer events that matter. - -**Next page if needed** - -- [Transactions API account history](/tx/account) if the user wants the surrounding execution story for the same account. -- [Transactions by Hash](/tx/transactions) when you already know which transaction to inspect next. - -**Stop when** - -- You have identified the right transfer event and the right next investigation surface. - -**Widen when** - -- The user explicitly needs receipt-level or canonical RPC confirmation. Move to [Transactions API](/tx) first, then [RPC Reference](/rpc) if needed. - ## Worked walkthrough -### Query a narrow transfer window, then pivot by receipt +### Find one suspicious transfer, then chase its receipt -Use this when the first question is still transfer-only, but you know you may need one precise execution pivot after you isolate the relevant movement. +Use this when the user story is “I know funds moved, but I want the exact execution anchor behind that movement without dragging in the whole account history yet.” **What you're doing** - Query a bounded outgoing transfer window for one account on mainnet. -- Extract the first `receipt_id` with `jq`. -- Reuse that receipt ID in Transactions API to move from asset movement into execution context. +- Pull out one transfer that looks like the movement you care about. +- Reuse its `receipt_id` in Transactions API to move from balance movement into execution history. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com @@ -150,14 +73,73 @@ curl -s "$TX_BASE_URL/v0/receipt" \ **Why this next step?** -The transfer query keeps the first pass narrow and pagination-friendly. Pivoting by `receipt_id` gives you one exact execution anchor without immediately widening into full account history. If you still need more rows afterward, continue paginating with the same `resume_token` and unchanged filters. +The transfer query answers the first question quickly: did this account send funds in this window, and to whom? Looking up the `receipt_id` gives you the exact execution anchor for that movement without dragging in the whole account history yet. If you still need more rows afterward, keep paginating with the same `resume_token` and unchanged filters. + +## Common jobs + +### Find outgoing transfers for one account in a narrow time window + +**Start here** + +- [Query Transfers](/transfers/query) with the account, outgoing direction, and the tightest useful time filter. + +**Next page if needed** + +- Narrow again by asset or amount if the response still contains unrelated transfers. + +**Stop when** + +- You can answer who sent what, when, and in which asset. + +**Switch when** + +- The user asks why the transfer happened or what other actions surrounded it. Move to [Transactions API](/tx). + +### Keep paging through a transfer feed without losing your place + +**Start here** + +- [Query Transfers](/transfers/query) for the first page of recent events, using the tightest stable filters you can. + +**Next page if needed** + +- Reuse the exact returned `resume_token` to fetch the next page with the same filters. +- Keep the filters unchanged while you paginate, or you are no longer looking at the same feed. + +**Stop when** + +- You have enough pages to answer the requested feed, support review, or compliance check. + +**Switch when** + +- The user asks for transaction metadata beyond transfer events. +- The feed needs balances or holdings, not just movement. Move to [FastNear API](/api). + +### Escalate from transfer-only history to full transaction investigation + +**Start here** + +- [Query Transfers](/transfers/query) to identify the specific transfer events that matter. + +**Next page if needed** + +- [Transactions API account history](/tx/account) if the user wants the surrounding execution story for the same account. +- [Transactions by Hash](/tx/transactions) when you already know which transaction to inspect next. + +**Stop when** + +- You have identified the right transfer event and the right next API to open. + +**Switch when** + +- The user explicitly needs receipt-level detail or exact RPC confirmation. Move to [Transactions API](/tx) first, then [RPC Reference](/rpc) if needed. ## Common mistakes - Using Transfers API when the user really wants balances, holdings, or account summaries. - Treating transfer history as full execution history. - Reusing a `resume_token` with different filters. -- Starting here for testnet questions; this surface is mainnet-only today. +- Starting here for testnet questions; this API is mainnet-only today. ## Related guides diff --git a/docs/tx/examples.md b/docs/tx/examples.md index f1d631a..09185a0 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -1,168 +1,465 @@ --- sidebar_label: Examples slug: /tx/examples -title: Transactions API Examples -description: Plain-language workflows for using Transactions API docs for transaction lookups, receipt investigation, account history, and block windows. +title: Transactions Examples +description: Plain-language investigations for following receipts, transactions, NEAR Social writes, promise chains, and NEAR Intents settlements. displayed_sidebar: transactionsApiSidebar page_actions: - markdown --- -# Transactions API Examples +## Worked investigations -Use this page when the question is "what happened?" and you want indexed history before dropping to canonical RPC confirmation. Start with the identifier you already have, explain the execution story in readable order, and widen only if exact RPC semantics become necessary. +### Trace an async promise chain and prove callback order -## When to start here +Use this investigation when one transaction creates promise work for later, a second transaction resumes it, and the real question is not “did both transactions succeed?” but “did the cross-contract callbacks actually run in the order I intended?” -- You already have a transaction hash, receipt ID, account ID, or bounded block range. -- The user wants execution history, support/debug context, or a readable timeline. -- You need indexed history without rebuilding it from raw RPC calls. -- The first answer should explain what happened before it dives into protocol detail. +**Goal** -## Minimum inputs +- Turn two transaction hashes into one readable proof story: what promise work was created, what order the resume call requested, and what order later showed up in downstream contract state. + +If your codebase or helper scripts call this a “stage/release” or “yield/resume” flow, that is fine. For docs, the more useful mental model is simpler: + +- **create promise work**: one transaction sets up deferred async work for later +- **resume promise work**: a later transaction asks the contract to continue that work in a requested order +- **trace the async path**: receipt trees show where the cross-contract callbacks actually ran +- **observe state**: downstream contract state shows what order became visible to users or integrators + +```mermaid +flowchart LR + Y["Tx 1
creates promise work"] --> H["Yielded promises become live
staged_calls_for(...)"] + H --> R["Tx 2
resumes promises in order beta -> alpha -> gamma"] + R --> C["Async cross-contract callbacks"] + C --> B["Recorder state
beta"] + B --> A["Recorder state
alpha"] + A --> G["Recorder state
gamma"] + Y -. "main receipt-tree evidence lives here" .-> D["Original promise DAG"] + R -. "requested order lives here" .-> P["Resume payload"] + G -. "observed order ends here" .-> O["Observed downstream order"] +``` -- network: mainnet or testnet -- primary identifier: transaction hash, receipt ID, `account_id`, or block/range -- whether you are investigating one item or a history window -- whether canonical RPC confirmation is required before you stop +That distinction matters because a successful resume transaction still does not prove the observed order by itself. You also need evidence that the promised work was really live before resume, and evidence that downstream state changed in the same order the resume call requested. -## Common jobs +For NEAR engineers, the important mental model is: the resume transaction tells you the **requested order**, but the original promise transaction usually remains the primary forensic anchor because the resumed callbacks still live on that original async receipt tree. Downstream contract state is what lets you compare requested order with observed order. -### Look up one transaction +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Promise-chain trace capture | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Query the original promise transaction hash and the later resume transaction hash with `wait_until: "FINAL"`, usually hot RPC first and archival RPC on `UNKNOWN_TRANSACTION` | The receipt DAG is the primary proof surface for callback order and tells you which receipts belong to which async transaction tree | +| Promise-readiness check | RPC [`query(call_function)`](/rpc/contract/call-function) | Poll the contract view that exposes deferred promise work, such as `staged_calls_for({ caller_id })`, with `finality: "final"` until the yielded promises appear | Confirms the promise work was really live before the resume transaction tried to continue it | +| Requested-order anchor | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch both transaction hashes to recover `block_height`, `block_hash`, `receiver_id`, indexed execution status, and the resume payload | Gives each transaction a durable block anchor and preserves the exact order the resume step requested | +| Downstream state snapshots | RPC [`query(call_function)`](/rpc/contract/call-function) | Read the downstream recorder state before resume, then poll it after resume until the expected entries appear | Proves actual callback order in contract state, not just metadata in the receipt tree | +| Receipt pivot | Transactions API [`POST /v0/receipt`](/tx/receipt) | Use any interesting yielded or downstream receipt ID to reconnect it to the originating transaction | Lets you move quickly from one receipt in the DAG back to the broader transaction story | +| Per-block reconstruction | Transactions API [`POST /v0/block`](/tx/block) | Fetch the included block and the cascade blocks with receipts enabled | Reconstructs the block-by-block execution timeline once you know which blocks matter | +| Account activity context | Transactions API [`POST /v0/account`](/tx/account) | Fetch function-call history for the contracts that participated in the cascade over the same window | Gives humans a simpler account-history view to compare against the trace | +| Block-pinned state replay | RPC [`query(call_function)`](/rpc/contract/call-function) | Re-run the recorder view with `block_id` pinned to the interesting heights | Turns final state into a time series so you can say when state changed, not just what it became | -**Start here** +**What a useful answer should include** -- [Transactions by Hash](/tx/transactions) when you already know the transaction ID. +- a one-sentence conclusion in plain language, such as “the first transaction created three deferred promises, the second transaction resumed them in order `beta -> alpha -> gamma`, and the recorder state later confirmed that same callback order” +- why the original promise transaction, not only the resume transaction, is usually the primary forensic anchor +- the requested callback order and the observed downstream effect order +- the blocks where the observable state changed +- any receipt or account pivots the next investigator should keep -**Next page if needed** +### Turn one ugly receipt ID from logs into a human story -- [Receipt Lookup](/tx/receipt) if the interesting part is now a downstream receipt. -- [Block](/tx/block) if the block context matters. -- [Transaction Status](/rpc/transaction/tx-status) if you need canonical RPC confirmation. +Use this investigation when all you have is one ugly `receipt_id` from logs, traces, or an error report, and you want to turn it into a plain-English answer a teammate can understand. -**Stop when** +**Goal** -- You can explain the outcome, affected accounts, and the main execution takeaway. +- Start from one receipt ID and recover the shortest useful story: who created it, where it executed, which transaction spawned it, and what that transaction was actually trying to do. -**Widen when** +For this pinned example, the “ugly receipt ID from logs” is: -- The user asks for exact RPC-level status semantics or submission behavior. -- The transaction lookup alone is not enough to explain downstream execution. +- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` +- originating transaction hash: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- transaction block height: `194263342` +- receipt execution block height: `194263343` -### Investigate a receipt +The human story behind that one receipt is simple: `mike.near` signed a plain `Transfer` transaction to `global-counter.mike.near`, the network turned it into one action receipt, and that receipt executed successfully in the next block. -**Start here** +```mermaid +flowchart LR + L["One ugly receipt ID
5GhZcpfK..."] --> R["Lookup receipt"] + R --> T["Recover tx hash
AdgNifPY..."] + T --> S["Read transaction actions"] + S --> H["Human story:
mike.near sent 5 NEAR to global-counter.mike.near"] +``` -- [Receipt Lookup](/tx/receipt) when the receipt ID is your best anchor. +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Receipt anchor | Transactions API [`POST /v0/receipt`](/tx/receipt) | Look up the receipt ID first and print the accounts, execution block, success flag, and linked transaction hash | Gives you the shortest path from a raw receipt ID to “what object am I even looking at?” | +| Transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Reuse the recovered transaction hash and print signer, receiver, ordered actions, and included block | Turns the raw receipt into a readable story of what the signer actually submitted | +| Canonical follow-up | RPC [`tx`](/rpc/transaction/tx-status) or [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Confirm protocol-native semantics only if the indexed answer is still not enough | Useful when the question shifts from “tell me the story” to “show me the exact RPC status semantics” | -**Next page if needed** +**What a useful answer should include** -- [Transactions by Hash](/tx/transactions) to reconnect the receipt to the originating transaction story. -- [Account History](/tx/account) if you need to see the surrounding activity for one of the touched accounts. +- which accounts created and executed the receipt +- which transaction hash the receipt belongs to +- what the transaction actually did +- whether the receipt was the main event or just one step in a larger cascade +- one plain-English sentence that a teammate could read without decoding receipt jargon -**Stop when** +### Prove that one failed action reverted the whole batch -- You can say where the receipt fits in the execution flow and why it matters. +Use this investigation when one transaction tried to create and fund a new account, add a key, and then call a method on that same new account. The final action failed because the fresh account had no contract code. The real question is simple: did the earlier actions stick, or did the whole batch revert? -**Widen when** +On NEAR, the actions inside one transaction batch execute in order inside the same first action receipt. If one action in that receipt fails, the earlier actions in that same batch do not stick. That is different from later async receipts or promise chains, where the first receipt can succeed and some later receipt can still fail independently. -- The user needs exact canonical confirmation beyond the indexed receipt view. Move to [RPC Reference](/rpc). -- The question shifts from one receipt to a broader history investigation. +**Goal** -### Review recent account activity +- Prove, from one pinned testnet transaction, that the final `FunctionCall` failed and the earlier `CreateAccount`, `Transfer`, and `AddKey` actions did not stick. -**Start here** +**Official references** -- [Account History](/tx/account) for an account-centric activity feed. +- [Transaction foundations](/transaction-flow/foundations) +- [Runtime execution](/transaction-flow/runtime-execution) + +This pinned failure was captured on **April 18, 2026** on testnet: + +- transaction hash: `CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M` +- signer account: `temp.mike.testnet` +- intended new account: `rollback-mo4vmkig.temp.mike.testnet` +- included block height: `246365118` +- included block hash: `6f5zTKDqQRwrxMywzvxeRvYcCERJmAnatJaqUEtQYUNM` +- ordered actions: `CreateAccount -> Transfer -> AddKey -> FunctionCall` +- failing method: `definitely_missing_method` +- RPC failure: `CodeDoesNotExist` on `rollback-mo4vmkig.temp.mike.testnet` + +```mermaid +flowchart LR + T["One signed transaction"] --> A["CreateAccount"] + A --> B["Transfer 0.01 NEAR"] + B --> C["AddKey"] + C --> D["FunctionCall definitely_missing_method()"] + D --> E["Failure: CodeDoesNotExist"] + E --> R["Whole batch reverts"] + R --> N["No new account"] + R --> K["No new key"] + R --> F["No funded receiver state"] +``` -**Next page if needed** +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Intended batch | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the pinned transaction hash and print the ordered action list, receiver, and included block metadata | Shows exactly what the signer tried to do before you reason about what stuck | +| Exact failure | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Query the same transaction with `wait_until: "FINAL"` and inspect `status.Failure` | Tells you which action failed and why the whole batch reverted at the protocol level | +| Post-state proof | RPC [`query(view_account)`](/rpc/account/view-account) | Query the intended new account after finality | If the created account still does not exist, then the earlier `CreateAccount`, `Transfer`, and `AddKey` from that same batch did not stick either | -- [Transactions by Hash](/tx/transactions) for a specific transaction from the feed. -- [Receipt Lookup](/tx/receipt) if one receipt becomes the real focus. +One detail is worth calling out before the shell walkthrough: the indexed transaction record still shows `transaction_outcome.outcome.status = SuccessReceiptId`, because the signed transaction successfully became its first action receipt. The proof that the batch reverted comes from the RPC top-level `status.Failure` on that first receipt, plus the post-state check that the intended new account never existed. -**Stop when** +**What a useful answer should include** -- The account history already answers what the account has been doing. +- the exact action order the signer submitted +- which action index failed and why +- the included block height and hash for the batch +- proof that the intended new account still does not exist after finality +- a short conclusion that the earlier `CreateAccount`, `Transfer`, and `AddKey` actions did not stick once the final `FunctionCall` failed -**Widen when** +### Failed batched transaction shell walkthrough -- The user actually wants transfer-only movement instead of broader execution context. Move to [Transfers API](/transfers). -- The user actually wants exact current state or holdings, not history. Move to [RPC Reference](/rpc) or [FastNear API](/api). +Use this when you want one concrete failed batch that you can inspect step by step with public FastNear testnet endpoints. -### Reconstruct a bounded block window +**What you're doing** -**Start here** +- Read the indexed transaction record to recover the intended action batch. +- Use RPC transaction status to prove the final `FunctionCall` failed and reverted the batch. +- Use one post-state RPC read to prove the new account never existed after finality. -- [Blocks](/tx/blocks) for a bounded block-range scan. -- [Block](/tx/block) when you already know the exact block you want. +```bash +TX_BASE_URL=https://tx.test.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M +SIGNER_ACCOUNT_ID=temp.mike.testnet +NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +``` -**Next page if needed** +1. Fetch the transaction and print the intended action batch. -- [Transactions by Hash](/tx/transactions) to inspect a specific item from the block window. -- [Receipt Lookup](/tx/receipt) when one receipt becomes the important follow-up. +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/failed-batch-transaction.json >/dev/null -**Stop when** +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height, + included_block_hash: .transactions[0].execution_outcome.block_hash + }, + batch: { + action_count: (.transactions[0].transaction.actions | length), + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + final_function_call_method_name: ( + .transactions[0].transaction.actions[3].FunctionCall.method_name + ) + }, + first_receipt_handoff: .transactions[0].transaction_outcome.outcome.status +}' /tmp/failed-batch-transaction.json + +# Expected action order: +# 1. CreateAccount +# 2. Transfer +# 3. AddKey +# 4. FunctionCall +``` -- The bounded history window answers the question without dropping into lower-level protocol details. +2. Query RPC transaction status and inspect the exact top-level failure. -**Widen when** +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/failed-batch-rpc-status.json >/dev/null -- The user needs exact canonical block fields or transaction finality. Move to [RPC Reference](/rpc). -- The user really wants freshest polling-oriented block reads rather than indexed history. Move to [NEAR Data API](/neardata). +jq '{ + final_execution_status: .result.final_execution_status, + failed_action_index: .result.status.Failure.ActionError.index, + failure: .result.status.Failure.ActionError.kind.FunctionCallError.CompilationError.CodeDoesNotExist +}' /tmp/failed-batch-rpc-status.json -## Worked investigations +# Expected failed_action_index: 3 +# Expected failure account_id: rollback-mo4vmkig.temp.mike.testnet +``` -### Prove callback order in a staged release flow +3. Query the intended new account after finality and prove it still does not exist. -Use this investigation when you staged async work first, released it later, and need to prove not just that the transactions succeeded, but that the downstream callbacks executed in a particular order. +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/failed-batch-view-account.json >/dev/null -**Goal** +jq '{ + error: .error.cause.name, + message: .error.data, + requested_account_id: .error.cause.info.requested_account_id, + proof_block_height: .error.cause.info.block_height +}' /tmp/failed-batch-view-account.json -- Turn two transaction hashes into a durable forensic record that covers receipt DAGs, block anchors, and contract state changes. +# Expected error: "UNKNOWN_ACCOUNT" +``` -In staged-release flows, the stage transaction usually remains the primary forensic anchor because the yielded callback receipts stay on its original transaction tree, not on the release transaction's tree. +That one post-state check is enough here. If `CreateAccount` had stuck, `view_account` would resolve. Because the account still does not exist, the earlier `Transfer` and `AddKey` from the same batched receipt did not stick either. -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Stage and release trace capture | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Query the stage transaction hash and the release transaction hash with `wait_until: "FINAL"`, usually hot RPC first and archival RPC on `UNKNOWN_TRANSACTION` | The receipt DAG is the primary proof surface for callback order and tells you which receipts belong to which transaction tree | -| Stage materialization check | RPC [`query(call_function)`](/rpc/contract/call-function) | Poll the staging contract's view method, such as `staged_calls_for({ caller_id })`, with `finality: "final"` until the yielded steps appear | Confirms the yielded callbacks are actually live before the release transaction tries to resume them | -| Transaction enrichment | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch both transaction hashes to recover `block_height`, `block_hash`, `receiver_id`, and indexed execution status | Gives each transaction a durable block anchor so later archival or human follow-up does not depend on memory | -| Recorder state snapshots | RPC [`query(call_function)`](/rpc/contract/call-function) | Read the downstream recorder state before the release, then poll it after release until the expected entries appear | Proves actual downstream effect order in contract state, not just metadata in the receipt tree | -| Receipt pivot | Transactions API [`POST /v0/receipt`](/tx/receipt) | Use any interesting yielded or downstream receipt ID to reconnect it to the originating transaction | Lets you move quickly from one receipt in the DAG back to the broader transaction story | -| Per-block reconstruction | Transactions API [`POST /v0/block`](/tx/block) | Fetch the included block and the cascade blocks with receipts enabled | Reconstructs the block-by-block execution timeline once you know which blocks matter | -| Account activity context | Transactions API [`POST /v0/account`](/tx/account) | Fetch function-call history for the contracts that participated in the cascade over the same window | Gives humans a simpler account-history view to compare against the trace | -| Block-pinned state replay | RPC [`query(call_function)`](/rpc/contract/call-function) | Re-run the recorder view with `block_id` pinned to the interesting heights | Turns final state into a time series so you can say when state changed, not just what it became | +**Why this next step?** -**What a useful answer should include** +For another failed batch, keep the same pattern: read what the transaction tried to do from [`POST /v0/transactions`](/tx/transactions), confirm the exact top-level failure with RPC transaction status, then inspect post-state on the account, key, contract, or other object that would have changed if the earlier actions had stuck. -- why the stage transaction, not the release transaction, is usually the primary forensic anchor -- the callback order you observed -- the blocks where the observable state changed -- any receipt or account pivots the next investigator should keep +### Why did this contract call look successful, but a later receipt failed? -### Start from a receipt ID and rebuild the execution story +Use this investigation when one contract call logged success, changed its own local state, and even the top-level RPC `status` looks successful, but the app still broke because a later detached cross-contract receipt failed. -Use this investigation when the only thing you have is a receipt ID from a trace, error report, or callback tree and you need to get back to a readable story of what happened. +This is the opposite of the failed batch example above. There, one action failed inside the first action receipt, so nothing in that batch stuck. Here, the first contract receipt really did succeed and its state change really did stick. The failure happened later, in a separate receipt. **Goal** -- Pivot from one receipt to the originating transaction, then widen just enough to explain the surrounding execution and state effects. +- Prove, from one pinned testnet transaction, that `seq-dr.mike.testnet.kickoff_append(...)` succeeded on its own receipt, then a detached `append(...)` call failed one block later with `CodeDoesNotExist`. + +**Official references** + +- [Transaction foundations](/transaction-flow/foundations) +- [Runtime execution](/transaction-flow/runtime-execution) + +This pinned async failure was captured on **April 18, 2026** on testnet: + +- transaction hash: `AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz` +- signer account: `temp.mike.testnet` +- first contract receiver: `seq-dr.mike.testnet` +- detached target account: `asyncfail-in2hwikn.temp.mike.testnet` +- transaction inclusion block: `246368568` +- successful first receipt: `6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS` at block `246368569` +- later failed receipt: `2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es` at block `246368570` +- first method: `kickoff_append` +- later failed method: `append` +- top-level RPC `status`: `SuccessValue` + +```mermaid +flowchart LR + T["Signed tx
kickoff_append(...)"] --> R["First receipt on seq-dr.mike.testnet
SuccessValue + kickoff log"] + R --> S["Router stores local state
kicked += late-failure"] + R --> D["Detached cross-contract receipt
append(...)"] + D --> F["Later failure
CodeDoesNotExist"] + S -. "state from the first receipt still sticks" .-> K["kicked() still contains late-failure"] +``` | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | -| Receipt anchor | Transactions API [`POST /v0/receipt`](/tx/receipt) | Look up the receipt ID first and identify the receipt payload, status, and linked transaction context | Receipt IDs show up in traces and logs before humans know the full transaction story | -| Transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Pull the originating transaction by hash once the receipt lookup gives you the pivot | Turns one receipt into a readable execution story with receiver, block, and status context | -| Canonical confirmation | RPC [`tx`](/rpc/transaction/tx-status) or [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Confirm the protocol-level result when the indexed view is not enough or the user needs canonical semantics | Useful when the investigation must distinguish between indexed interpretation and exact RPC behavior | -| Block context | Transactions API [`POST /v0/block`](/tx/block) | Fetch the containing block, and widen to nearby cascade blocks if the execution spilled over multiple heights | Places the receipt into a block timeline that is easier to explain | -| Account window | Transactions API [`POST /v0/account`](/tx/account) | Pull recent activity for the accounts touched by the receipt | Helps correlate the receipt with the surrounding account-level history | -| State replay | RPC [`query(call_function)`](/rpc/contract/call-function) | Re-run the relevant view method at a pinned `block_id` if the receipt changed contract-visible state | Lets you prove whether a receipt only existed in metadata or also changed durable contract state | +| Transaction skeleton | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the pinned transaction and print the included block plus the per-receipt timeline | Gives the shortest readable overview of which receipt ran first and which receipt failed later | +| Exact status semantics | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Inspect the top-level `status`, the first contract receipt outcome, and the later failed receipt outcome | Proves that top-level success and later descendant failure can coexist in one async story | +| Current contract state | RPC [`query(call_function)`](/rpc/contract/call-function) | Call `seq-dr.mike.testnet.kicked()` | Shows that the first receipt's local state change stuck even though the later detached receipt failed | + +One NEAR detail matters here: receipt success is not transitive. `seq-dr.mike.testnet` returned success on its own receipt because `kickoff_append(...)` only logged and detached the next hop. The detached `append(...)` receipt was a separate piece of async work, so its later failure did not rewind the router's earlier state change. **What a useful answer should include** -- the originating transaction you recovered from the receipt -- whether the receipt was the main event or only one step in a larger cascade -- the minimum block and account context needed to explain it -- whether the state effect was durable and at which block height it became visible +- that the signed transaction successfully handed off into the first router receipt +- that the router receipt itself succeeded and emitted the `dishonest_router:kickoff:late-failure` log +- that the later detached receipt to `asyncfail-in2hwikn.temp.mike.testnet` failed with `CodeDoesNotExist` +- that the router's own state still contains `late-failure`, so the first receipt's local side effect stuck +- one sentence explaining why this is different from a failed batched transaction + +### Later receipt failure shell walkthrough + +Use this when the user story is “the contract call looked fine, but something failed later, and I need to prove exactly where the story split.” + +**What you're doing** + +- Read the transaction and its receipt timeline from the indexed view. +- Use RPC transaction status to show that the top-level story still ended in `SuccessValue` even though a later receipt failed. +- Read the router's current state to show that the first receipt's local side effect stuck. + +```bash +TX_BASE_URL=https://tx.test.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz +SIGNER_ACCOUNT_ID=temp.mike.testnet +ROUTER_ACCOUNT_ID=seq-dr.mike.testnet +FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS +FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es +``` + +1. Fetch the transaction and print the receipt timeline in block order. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/later-receipt-failure-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + tx_handoff: .transactions[0].transaction_outcome.outcome.status + }, + receipts: [ + .transactions[0].receipts[] + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + block_height: .execution_outcome.block_height, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system_transfer"), + status: .execution_outcome.outcome.status + } + ] +}' /tmp/later-receipt-failure-transaction.json + +# What to notice: +# - the first contract receipt on seq-dr.mike.testnet succeeded in block 246368569 +# - the later append(...) receipt failed in block 246368570 +``` + +2. Query RPC transaction status and compare the top-level story with the later failed receipt. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/later-receipt-failure-rpc.json >/dev/null + +jq \ + --arg first_receipt_id "$FIRST_RECEIPT_ID" \ + --arg failed_receipt_id "$FAILED_RECEIPT_ID" '{ + top_level_status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status, + first_contract_receipt: ( + .result.receipts_outcome[] + | select(.id == $first_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + logs: .outcome.logs, + status: .outcome.status + } + ), + later_failed_receipt: ( + .result.receipts_outcome[] + | select(.id == $failed_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + status: .outcome.status + } + ) + }' /tmp/later-receipt-failure-rpc.json + +# What to notice: +# - top_level_status is still SuccessValue +# - the first contract receipt logged dishonest_router:kickoff:late-failure +# - the later append(...) receipt failed with CodeDoesNotExist +``` + +3. Read the router's current state and confirm that the first receipt's local side effect stuck. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ROUTER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "kicked", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/later-receipt-failure-kicked.json >/dev/null + +jq '{ + kicked: (.result.result | implode | fromjson), + contains_late_failure: ((.result.result | implode | fromjson) | index("late-failure") != null) +}' /tmp/later-receipt-failure-kicked.json +``` + +That last read is the practical proof that the first receipt's local state change stuck. The later failed receipt did not rewind the router's earlier `kicked.push(...)`. + +**Why this next step?** + +When a NEAR app “looked successful” and still broke later, the thing to ask is not just “what was the transaction status?” but “which receipt succeeded, and which later receipt failed?” This example gives you that exact split: indexed receipt timeline for the shape, RPC status for the exact semantics, and one contract-state read to prove the earlier side effect stuck. ### Prove that `mike.near` set `profile.name` to `Mike Purvis`, then recover the SocialDB profile write transaction @@ -546,61 +843,252 @@ That last step confirms the follow edge still exists now. The earlier NEAR Socia NEAR Social gives you the semantic edge. FastNear block receipts give you the bridge to a specific write. FastNear transaction lookup turns that write into a readable story. RPC gives you canonical current-state confirmation. -### Understand a two-party `token_diff` match, then trace a live NEAR Intents settlement +### Which transaction wrote `DonateNEARtoEfiz`? + +Use this investigation when the user story is lighter and more playful: “the RPC examples page just showed me that `efiz.near/widget/DonateNEARtoEfiz` exists and that its last-write block is `92543301`. Which transaction actually wrote that widget?” + +This one is fun, but the proof recipe is the same serious NEAR Social pattern: -Use this investigation when the user story is “show me what NEAR Intents is doing under the hood, but keep the trace anchored in public data I can inspect myself.” +- start from one SocialDB block anchor +- bridge that block to one `efiz.near -> social.near` receipt +- recover the originating transaction +- decode the `set` payload and prove it really stored the widget source **Goal** -- Explain the matching model first, then turn one real `intents.near` settlement into a readable execution story across Transactions API and canonical RPC. +- Turn the widget's last-write block into one readable answer: which transaction wrote `DonateNEARtoEfiz`, which receipt executed the write, and what exact widget source was stored in that payload. **Official references** -- [NEAR Intents overview](https://docs.near.org/chain-abstraction/intents/overview) -- [Intent types and execution](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) -- [Account abstraction](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) +- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) + +This investigation picks up right where the playful RPC widget example leaves off. For this live anchor: + +- account: `efiz.near` +- widget: `DonateNEARtoEfiz` +- SocialDB write block: `92543301` +- receipt ID: `FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E` +- originating transaction hash: `CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf` +- outer transaction block: `92543300` + +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Block-to-receipt bridge | Transactions API [`POST /v0/block`](/tx/block) | Start from block `92543301` with `with_receipts: true`, then filter back down to `efiz.near -> social.near` | Turns the widget's last-write block into one concrete receipt and one concrete transaction hash | +| Transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the originating transaction and decode the `FunctionCall.args` payload | Proves the write was a `social.near set` call carrying the `DonateNEARtoEfiz` source code | +| Canonical current-state confirmation | RPC [`query(call_function)`](/rpc/contract/call-function) | Call `social.near get` directly at `final` for the same widget path | Confirms that the widget still exists now, even though the earlier steps already proved which historical transaction wrote it | + +**What a useful answer should include** + +- the write block height and why it is the receipt execution block, not the outer transaction block +- the specific receipt ID and originating transaction hash behind the widget write +- proof that the write payload was a `set` call carrying `efiz.near/widget/DonateNEARtoEfiz` +- one plain-English sentence like “`efiz.near` wrote `DonateNEARtoEfiz` in tx `CUA61...`, and the payload really did store the donation widget source” + +### DonateNEARtoEfiz write-proof shell walkthrough -#### Part 1: protocol anatomy +Use this when you want to turn one playful widget block anchor into the exact transaction that wrote it. -The core matching shape is the `token_diff` intent. One side declares which assets it is willing to give and receive, and the matching side declares the opposite diff. In the official verifier docs, a two-party USDC/USDT swap is expressed as one signed payload that says “I will give `-10` USDC and receive `+10` USDT” and another that says the reverse. Those signed intents can be bundled through the Message Bus or through any other off-chain coordination channel, then submitted together to `intents.near`. +**What you're doing** + +- Start from the widget's last-write block. +- Reuse that block height in FastNear block receipts to recover the receipt and transaction bridge. +- Reuse the transaction hash in `POST /v0/transactions` to decode the stored widget source. +- Finish with raw RPC confirmation that the widget still exists now. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=efiz.near +WIDGET_NAME=DonateNEARtoEfiz +WIDGET_BLOCK_HEIGHT=92543301 +``` + +1. Start from the widget's last-write block and recover the SocialDB receipt plus transaction hash. + +```bash +WIDGET_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$WIDGET_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/efiz-widget-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + widget_write_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/efiz-widget-block.json + +# Expected receipt ID: FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E +# Expected transaction hash: CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf +``` + +2. Reuse the transaction hash and decode the SocialDB `set` payload. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/efiz-widget-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].transaction.actions[0].FunctionCall + | { + method_name, + widget_source_head: ( + .args + | @base64d + | fromjson + | .data["efiz.near"].widget["DonateNEARtoEfiz"][""] + | split("\n")[0:12] + ) + } + ) +}' /tmp/efiz-widget-transaction.json +``` -That conceptual part is useful for understanding the protocol, but the signed examples in the official docs are illustrative and time-bound. For an operational FastNear workflow, it is better to trace one real mainnet settlement than to pretend the documentation sample is a reusable live transaction. +That second step is the payoff. You are no longer just saying “something in that block updated SocialDB.” You are proving that tx `CUA61...` called `social.near set` and carried the actual `DonateNEARtoEfiz` widget body in its args. -#### Part 2: live FastNear trace +3. Finish with canonical current-state confirmation via raw RPC. -For the live trace below, use this fixed settlement anchor captured on **April 18, 2026**: +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/efiz-widget-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + finality: "final", + current_widget_head: ( + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:5] + ) +}' /tmp/efiz-widget-rpc.json +``` + +That last step confirms the widget still exists now. The earlier block and transaction steps are what proved which historical write created it. + +**Why this next step?** + +The widget's last-write block gives you the bridge. FastNear block receipts turn that bridge into one receipt and one transaction hash. FastNear transaction lookup turns the hash into readable write proof. RPC then confirms that the widget still exists now. + +### Trace one NEAR Intents settlement and show what actually happened + +Use this investigation when the user story is “I have one `intents.near` transaction. Show me what actually happened on-chain, which contracts participated, and which events prove it.” + +**Goal** + +- Start from one fixed `intents.near` transaction and turn it into a readable settlement story: which method kicked things off, which downstream contracts appeared, and which event families tell you what moved. + +**Official references** + +- [NEAR Intents overview](https://docs.near.org/chain-abstraction/intents/overview) +- [Intent types and execution](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) +- [Account abstraction](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) + +For the live trace below, use this fixed mainnet settlement anchor captured on **April 18, 2026**: - transaction hash: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` - signer and receiver: `intents.near` - included block height: `194573310` -The public FastNear surfaces are enough to reconstruct a lot: +The fast mental model is simple: + +- `intents.near` runs the settlement entrypoint +- downstream receipts fan out to the contracts that actually move or withdraw assets +- event logs tell you which settlement actions happened, with names like `token_diff`, `intents_executed`, `mt_transfer`, and `mt_withdraw` + +For this specific settlement, the short human answer is already pretty good: + +- `intents.near` called `execute_intents` +- downstream receipts hit `v2_1.omni.hot.tg` and `bridge-refuel.hot.tg` +- the trace emitted `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw`, and `mt_burn` + +If you want the protocol background, the core matching shape is the two-party `token_diff` intent: one side signs the asset diff it wants, the other side signs the opposite diff, and the matched pair gets submitted for settlement. For operational tracing, though, it is usually clearer to start from one real settlement and read the chain evidence directly. + +```mermaid +flowchart LR + T["One mainnet transaction
4cfei8p4..."] --> I["intents.near
execute_intents"] + I --> R["Downstream receipts"] + R --> C["Other contracts participate"] + R --> E["Event logs appear"] + E --> TD["token_diff"] + E --> IE["intents_executed"] + E --> MT["mt_transfer / mt_withdraw"] +``` + +The public FastNear surfaces are enough to answer the practical question: | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | -| Settlement anchor | Transactions API [`POST /v0/transactions`](/tx/transactions) | Start from the fixed transaction hash and recover the main transaction plus the downstream receipt list | Gives you one readable settlement skeleton without decoding raw receipts first | -| Included block context | Transactions API [`POST /v0/block`](/tx/block) | Fetch the included block with receipts enabled and filter it back down to the same transaction hash | Places the settlement into the surrounding block window and shows which receipts appeared there | -| Canonical receipt DAG | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Ask for the same transaction with `wait_until: "FINAL"` and inspect `receipts_outcome` | Gives you the protocol-native DAG, executor IDs, and raw event logs | -| Event classification | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Pull event names such as `token_diff`, `intents_executed`, `mt_transfer`, and `mt_withdraw` out of the logged `EVENT_JSON` lines | Lets you explain the settlement by event type instead of by opaque receipt IDs alone | +| Settlement skeleton | Transactions API [`POST /v0/transactions`](/tx/transactions) | Start from the fixed transaction hash and print the main transaction plus the first downstream receipts | Gives you the fastest readable answer to “what did this settlement call into next?” | +| Block context | Transactions API [`POST /v0/block`](/tx/block) | Fetch the included block with receipts enabled and filter it back down to the same transaction hash | Shows where the settlement landed and which receipts from that transaction appeared in the block | +| Canonical receipt and event proof | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Ask for the same transaction with `wait_until: "FINAL"` and inspect `receipts_outcome` plus `EVENT_JSON` logs | Gives you the protocol-native DAG, executor IDs, and event names that explain what the settlement actually did | **What a useful answer should include** -- how the conceptual two-party `token_diff` model maps onto the real `execute_intents` settlement -- which downstream contracts and methods appeared after `intents.near` +- the settlement entrypoint you observed on `intents.near` +- which downstream contracts and methods appeared right after it - which event families the trace emitted -- which block heights formed the main cascade +- a one-sentence summary of what happened, in plain language This example intentionally stays on public FastNear surfaces. NEAR Intents Explorer and the 1Click Explorer are useful too, but their Explorer API is JWT-gated and not the right default for a public docs walkthrough. -### Live NEAR Intents trace shell walkthrough +### NEAR Intents settlement shell walkthrough Use this when you want one concrete `intents.near` settlement that you can inspect immediately with public FastNear endpoints. **What you're doing** -- Pull the transaction story from Transactions API. +- Pull the readable settlement story from Transactions API. - Reuse the included block hash in `POST /v0/block` to inspect the containing block. -- Confirm the canonical receipt DAG and event log families with `EXPERIMENTAL_tx_status`. +- Confirm the canonical receipt DAG and event families with `EXPERIMENTAL_tx_status`. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -609,7 +1097,7 @@ INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 INTENTS_SIGNER_ID=intents.near ``` -1. Start with the settlement transaction itself. +1. Start with the settlement transaction itself and recover the first readable receipt flow. ```bash INTENTS_BLOCK_HASH="$( @@ -643,6 +1131,8 @@ jq '{ }' /tmp/intents-transaction.json ``` +That first step already gives you a strong operator answer: `intents.near` ran the settlement transaction, and the early downstream receipts show which contracts joined the cascade. + 2. Reuse the block hash to inspect the containing block with receipts enabled. ```bash @@ -712,24 +1202,27 @@ jq -r ' **Why this next step?** -`POST /v0/transactions` gives you the readable settlement skeleton. `POST /v0/block` shows how that settlement sits inside the containing block. `EXPERIMENTAL_tx_status` is the canonical follow-up when you need executor IDs, receipt-DAG structure, and raw event logs instead of just indexed summaries. +`POST /v0/transactions` tells you what the settlement called into. `POST /v0/block` shows where that settlement landed in block context. `EXPERIMENTAL_tx_status` is the canonical follow-up when you need executor IDs, receipt-DAG structure, and raw event names to explain what actually happened. -### Receipt pivot shell walkthrough +### Ugly receipt ID to human story shell walkthrough -Use this when you already have one `receipt_id` and want the shortest path back to a readable transaction story. +Use this when you already have one raw `receipt_id` from logs and want to turn it into a readable explanation fast. **What you're doing** - Resolve the receipt first. - Extract `receipt.transaction_hash` with `jq`. - Reuse that transaction hash in `POST /v0/transactions`. +- Finish with one human summary you could paste into chat or a ticket. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID=YOUR_RECEIPT_ID -# Example receipt ID from a recent mainnet transfer: -# RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +``` + +1. Resolve the receipt and figure out what object you are looking at. +```bash TX_HASH="$( curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ @@ -743,26 +1236,141 @@ jq '{ receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, + receipt_type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, transaction_hash: .receipt.transaction_hash, tx_block_height: .receipt.tx_block_height } }' /tmp/receipt-lookup.json +``` +2. Reuse the transaction hash and turn the receipt into a readable transaction story. + +```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - receipt_count: (.transactions[0].receipts | length) - }' + | tee /tmp/receipt-parent-transaction.json >/dev/null + +jq '{ + transaction: { + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transfer_deposit_yocto: ( + .transactions[0].transaction.actions[0].Transfer.deposit // null + ) + }, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/receipt-parent-transaction.json ``` +3. Turn that into one human sentence. + +```bash +jq -r ' + .transactions[0] as $tx + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) belongs to tx \($tx.transaction.hash): \($tx.transaction.signer_id) sent 5 NEAR to \($tx.transaction.receiver_id). The tx landed in block \($tx.execution_outcome.block_height), and the receipt executed successfully in block \($tx.receipts[0].execution_outcome.block_height)." +' /tmp/receipt-parent-transaction.json +``` + +For another receipt, keep the same pattern but change the final sentence to match the action types you just printed. + +That is the core trick: you do not need to explain every receipt field. You need to recover just enough context to say what the signer did, where the receipt executed, and whether this receipt was the main event or only one step in a bigger cascade. + **Why this next step?** -`POST /v0/receipt` gives you the pivot. `POST /v0/transactions` turns that pivot into a readable story with signer, receiver, block, and receipt context. Only after that should you widen to block or account windows. +`POST /v0/receipt` tells you what the raw receipt is attached to. `POST /v0/transactions` tells you what the signer was actually trying to do. Once you have those two pieces together, you can usually explain the receipt in one sentence before deciding whether you really need block context, account history, or canonical RPC status. + +## Common jobs + +### Look up one transaction + +**Start here** + +- [Transactions by Hash](/tx/transactions) when you already know the transaction ID. + +**Next page if needed** + +- [Receipt Lookup](/tx/receipt) if the interesting part is now a downstream receipt. +- [Block](/tx/block) if the block context matters. +- [Transaction Status](/rpc/transaction/tx-status) if you need canonical RPC confirmation. + +**Stop when** + +- You can explain the outcome, affected accounts, and the main execution takeaway. + +**Switch when** + +- The user asks for exact RPC-level status semantics or submission behavior. +- The transaction lookup alone is not enough to explain downstream execution. + +### Investigate a receipt + +**Start here** + +- [Receipt Lookup](/tx/receipt) when the receipt ID is your best anchor. + +**Next page if needed** + +- [Transactions by Hash](/tx/transactions) to reconnect the receipt to the originating transaction story. +- [Account History](/tx/account) if you need to see the surrounding activity for one of the touched accounts. + +**Stop when** + +- You can say where the receipt fits in the execution flow and why it matters. + +**Switch when** + +- The user needs exact canonical confirmation beyond the indexed receipt view. Move to [RPC Reference](/rpc). +- The question shifts from one receipt to a broader history investigation. + +### Review recent account activity + +**Start here** + +- [Account History](/tx/account) for an account-centric activity feed. + +**Next page if needed** + +- [Transactions by Hash](/tx/transactions) for a specific transaction from the feed. +- [Receipt Lookup](/tx/receipt) if one receipt becomes the real focus. + +**Stop when** + +- The account history already answers what the account has been doing. + +**Switch when** + +- The user actually wants transfer-only movement instead of broader execution context. Move to [Transfers API](/transfers). +- The user actually wants exact current state or holdings, not history. Move to [RPC Reference](/rpc) or [FastNear API](/api). + +### Reconstruct a bounded block window + +**Start here** + +- [Blocks](/tx/blocks) for a bounded block-range scan. +- [Block](/tx/block) when you already know the exact block you want. + +**Next page if needed** + +- [Transactions by Hash](/tx/transactions) to inspect a specific item from the block window. +- [Receipt Lookup](/tx/receipt) when one receipt becomes the important follow-up. + +**Stop when** + +- The bounded history window answers the question without dropping into lower-level protocol details. + +**Switch when** + +- The user needs exact canonical block fields or transaction finality. Move to [RPC Reference](/rpc). +- The user really wants freshest polling-oriented block reads rather than indexed history. Move to [NEAR Data API](/neardata). ## Common mistakes diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index 122eb82..332119d 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -1,95 +1,13 @@ --- sidebar_label: Examples slug: /api/examples -title: "Примеры FastNear API" -description: "Пошаговые сценарии использования FastNear API для сводок по аккаунтам, поиска по ключам и перехода к более узким представлениям активов." +title: "Примеры API" +description: "Пошаговые сценарии использования FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга." displayed_sidebar: fastnearApiSidebar page_actions: - markdown --- -# Примеры FastNear API - -Используйте эту страницу, когда нужен читаемый ответ в форме сводки по аккаунту или активу и хочется пройти по документации FastNear API самым коротким путём. Начинайте с самого узкого эндпоинта, который уже может решить задачу, и расширяйтесь только тогда, когда понадобятся канонические детали RPC или история исполнения. - -## Когда начинать здесь - -- Пользователю нужны балансы, активы, стейкинг или общая сводка по аккаунту в формате кошелька. -- Нужно определить один или несколько аккаунтов по публичному ключу. -- Ответ должен выглядеть как прикладные данные, а не как сырой JSON-RPC. -- Нужен быстрый первый ответ до того, как станет понятно, требуется ли каноническое подтверждение через RPC. - -## Минимальные входные данные - -- сеть: mainnet или testnet -- основной идентификатор: `account_id` или публичный ключ -- нужен ли широкий обзор или одна конкретная категория активов -- понадобится ли затем точное каноническое подтверждение или история активности - -## Частые задачи - -### Получить сводку по аккаунту в формате кошелька - -**Начните здесь** - -- [V1 Full Account View](/api/v1/account-full) для самого широкого снимка аккаунта. - -**Следующая страница при необходимости** - -- [V1 Account FT](/api/v1/account-ft), [V1 Account NFT](/api/v1/account-nft) или [V1 Account Staking](/api/v1/account-staking) для более узкого продолжения. -- [Transactions API account history](/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?» - -**Остановитесь, когда** - -- Сводка уже отвечает на вопрос о портфеле или активах в нужной пользователю форме. - -**Расширяйте, когда** - -- Пользователь спрашивает о точной канонической семантике аккаунта или ключей доступа. Переходите к [RPC Reference](/rpc). -- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](/tx). - -### Определить аккаунты по публичному ключу - -**Начните здесь** - -- [V1 Public Key Lookup](/api/v1/public-key), когда нужен основной аккаунт для ключа. -- [V1 Public Key Lookup All](/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. - -**Следующая страница при необходимости** - -- [V1 Full Account View](/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. - -**Остановитесь, когда** - -- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. - -**Расширяйте, когда** - -- Пользователь спрашивает о точных правах ключа, nonce или каноническом состоянии access key. Переходите к [View Access Key](/rpc/account/view-access-key) или [View Access Key List](/rpc/account/view-access-key-list). -- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](/tx). - -### Продолжить по одной категории активов, а не по всему аккаунту - -**Начните здесь** - -- [V1 Account FT](/api/v1/account-ft) для балансов FT-токенов. -- [V1 Account NFT](/api/v1/account-nft) для владения NFT. -- [V1 Account Staking](/api/v1/account-staking) для позиций стейкинга. - -**Следующая страница при необходимости** - -- [V1 Full Account View](/api/v1/account-full), если позже понадобится более широкий снимок аккаунта. -- [Transactions API account history](/tx/account), если вопрос смещается к тому, как активы менялись со временем. - -**Остановитесь, когда** - -- Эндпоинт по конкретной категории активов уже даёт готовый продуктовый ответ без дополнительной реконструкции. - -**Расширяйте, когда** - -- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](/rpc). -- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](/tx). - ## Готовые сценарии ### Определить аккаунт по публичному ключу, а затем получить сводку по нему @@ -130,7 +48,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Зачем нужен следующий шаг?** -Поиск по публичному ключу отвечает на вопрос об идентификации. Полный снимок аккаунта отвечает на следующий прикладной вопрос уже в продуктовой форме. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, расширяйтесь до [V1 Public Key Lookup All](/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. +Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. ### Проверить владение коллекцией, а затем выпустить производный NFT @@ -179,7 +97,7 @@ jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ }' /tmp/testnet-account-nfts.json ``` -2. Расширьтесь до канонического RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. +2. Перейдите к RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. ```bash NFT_TOKENS_ARGS_BASE64="$( @@ -260,7 +178,7 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Подтвердите новый токен тем же каноническим NFT view-методом. +5. Подтвердите новый токен тем же NFT view-методом. Если сразу после возврата mint-транзакции токен ещё не виден, не считайте это ошибкой сразу же: опросите view-метод несколько раз. @@ -300,7 +218,7 @@ jq '.' /tmp/derivative-token-verification.json **Зачем нужен следующий шаг?** -FastNear API быстрее всего отвечает на вопрос о допуске. Как только аккаунт проходит условие, RPC становится правильной поверхностью для точной проверки токенов и подтверждения результата, потому что напрямую открывает канонические NFT view-методы коллекции. +FastNear API — это быстрый ответ на вопрос о допуске. Как только аккаунт проходит условие, правильным следующим шагом становится RPC, потому что именно там видны точные `token_id` и собственные NFT view-методы коллекции. ### У меня обычный стейкинг или liquid staking? @@ -378,10 +296,74 @@ jq -n \ Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. +## Частые задачи + +### Что этот аккаунт вообще держит прямо сейчас? + +**Начните здесь** + +- [V1 Full Account View](/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» + +**Следующая страница при необходимости** + +- [V1 Account FT](/api/v1/account-ft), [V1 Account NFT](/api/v1/account-nft) или [V1 Account Staking](/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. +- [Transactions API account history](/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» + +**Остановитесь, когда** + +- Сводка уже отвечает на вопрос по активам в одной выдаче. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](/rpc). +- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](/tx). + +### Определить аккаунты по публичному ключу + +**Начните здесь** + +- [V1 Public Key Lookup](/api/v1/public-key), когда нужен основной аккаунт для ключа. +- [V1 Public Key Lookup All](/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. + +**Следующая страница при необходимости** + +- [V1 Full Account View](/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. + +**Остановитесь, когда** + +- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](/rpc/account/view-access-key) или [View Access Key List](/rpc/account/view-access-key-list). +- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](/tx). + +### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? + +**Начните здесь** + +- [V1 Account FT](/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. +- [V1 Account NFT](/api/v1/account-nft), когда вопрос конкретно про владение NFT. +- [V1 Account Staking](/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. + +**Следующая страница при необходимости** + +- [V1 Full Account View](/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. +- [Transactions API account history](/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» + +**Остановитесь, когда** + +- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. + +**Переходите дальше, когда** + +- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](/rpc). +- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](/tx). + ## Частые ошибки - Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. -- Использовать FastNear API, хотя пользователю прямо нужны канонические поля RPC или права доступа. +- Использовать FastNear API, хотя пользователю прямо нужны точные поля RPC или права доступа. - Оставаться на страницах сводок по аккаунту, когда вопрос уже стал вопросом об истории транзакций. - Забывать, что `?network=testnet` поддерживается только на совместимых страницах. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index 89670f2..70900fa 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -1,30 +1,93 @@ --- sidebar_label: Examples slug: /fastdata/kv/examples -title: "Примеры KV FastData API" -description: "Пошаговые сценарии использования KV FastData для точных ключей, истории ключей, анализа по предшественнику и перехода к каноническому RPC." +title: "Примеры KV FastData" +description: "Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC." displayed_sidebar: kvFastDataSidebar page_actions: - markdown --- -# Примеры KV FastData API +## Готовое расследование + +### Проверить один ключ контракта, а затем пройти по его истории + +Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и финальную проверку через `view_state`. + +**Цель** + +- Объяснить, как этот ключ выглядит в индексе, как он менялся и совпадает ли с этим `view_state` прямо сейчас. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Последнее индексированное значение | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | +| История индексированного ключа | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) или [`history-by-key`](/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | +| Более широкий паттерн записей | KV FastData [`latest-by-account`](/fastdata/kv/latest-by-account) или [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Смотрим аккаунт или предшественника, если один ключ — только часть более широкой картины | Помогает понять, менялся ли ключ сам по себе или как часть большего набора записей | +| Точная проверка состояния | RPC [`view_state`](/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | -Используйте эту страницу, когда вопрос касается индексированного хранилища контракта и уже есть точная область поиска. Главное решение на этой поверхности — выбрать самую узкую полезную область: точный ключ, аккаунт, `predecessor_id` или набор известных ключей. Оставайтесь внутри KV FastData, пока речь идёт об индексированных данных key-value, и переходите к RPC только тогда, когда нужно каноническое состояние в цепочке. +**Что должен включать полезный ответ** + +- какой именно ключ и какая область контракта были исследованы +- как выглядит последнее индексированное значение и какие изменения видны в истории +- совпал ли `view_state` с текущим индексированным значением + +### Shell-сценарий + +Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» + +**Что вы делаете** + +- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. +- Извлекаете точный `key` через `jq`. +- Переиспользуете этот ключ в `POST /v0/history`, чтобы получить историю записей по тому же ключу. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +EXACT_KEY="$( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | tee /tmp/kv-latest.json \ + | jq -r '.entries[0].key' +)" -## Когда начинать здесь +jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' /tmp/kv-latest.json -- Нужны индексированные данные хранилища контракта, а не широкие сводки по аккаунтам или активам. -- Уже известен контракт, точный ключ, `predecessor_id` или область по аккаунту. -- Нужны последние индексированные записи или история изменений во времени. -- Нужен быстрый ответ по хранилищу до решения о дальнейшей проверке через RPC. +curl -s "$KV_BASE_URL/v0/history" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ + | jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + value + } + ] + }' +``` -## Минимальные входы +**Зачем нужен следующий шаг?** -- сеть -- ID контракта плюс одно из: точный ключ, область аккаунта, область по `predecessor_id` или известный набор ключей -- нужно ли последнее индексированное состояние или история изменений -- может ли позже понадобиться каноническая проверка +Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](/fastdata/kv/get-history-key). ## Частые задачи @@ -42,7 +105,7 @@ page_actions: - Последняя индексированная запись уже отвечает на вопрос о хранилище. -**Расширяйте, когда** +**Переходите дальше, когда** - Пользователю нужно точное текущее состояние в цепочке, а не индексированное хранилище. Переходите к [View State](/rpc/contract/view-state). @@ -61,9 +124,9 @@ page_actions: - Уже можно объяснить, как ключ менялся со временем. -**Расширяйте, когда** +**Переходите дальше, когда** -- Пользователь спрашивает, совпадает ли последнее индексированное значение с каноническим состоянием цепочки прямо сейчас. +- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. ### Проследить записи от одного `predecessor_id` @@ -80,9 +143,9 @@ page_actions: - Уже можно ответить, что именно этот предшественник изменил и где. -**Расширяйте, когда** +**Переходите дальше, когда** -- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее каноническое состояние. +- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. ### Пакетно проверить несколько известных ключей @@ -98,90 +161,9 @@ page_actions: - Пакетный ответ уже показывает, какие ключи действительно важны. -**Расширяйте, когда** - -- Пользователь хочет более широкий анализ контракта вместо фиксированного набора ключей. - -## Готовое расследование - -### Начать с одного индексированного ключа, затем подтвердить историю и каноническое состояние - -Используйте это расследование, когда один ключ контракта выглядит подозрительно и нужно связать его последнее индексированное значение, историю изменений и каноническую проверку через `view_state` в одну ясную историю. - -**Цель** - -- Объяснить, как ключ контракта выглядит сейчас, как он пришёл к этому состоянию в индексированной истории и совпадает ли с этим каноническое состояние RPC. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Последнее индексированное значение | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | -| История индексированного ключа | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) или [`history-by-key`](/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | -| Расширение области | KV FastData [`latest-by-account`](/fastdata/kv/latest-by-account) или [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Расширяемся до области аккаунта или предшественника, если один ключ — только часть более широкой картины изменений | Помогает понять, менялся ли ключ изолированно или как часть более широкого набора записей | -| Каноническое подтверждение | RPC [`view_state`](/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированный паттерн уже понятен | Разделяет индексированную историю хранилища и точное текущее состояние в цепочке | - -**Что должен включать полезный ответ** - -- какой именно ключ и какая область контракта были исследованы -- как выглядит последнее индексированное значение и какие изменения видны в истории -- совпал ли `view_state` с текущим индексированным значением - -### Shell-сценарий - -Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «какая у этого ключа более широкая индексированная история?» - -**Что вы делаете** - -- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. -- Извлекаете точный `key` через `jq`. -- Переиспользуете этот ключ в `POST /v0/history`, чтобы расшириться до истории. - -```bash -KV_BASE_URL=https://kv.main.fastnear.com -CURRENT_ACCOUNT_ID=social.near -PREDECESSOR_ID=james.near -KEY='graph/follow/sleet.near' - -ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" - -EXACT_KEY="$( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | tee /tmp/kv-latest.json \ - | jq -r '.entries[0].key' -)" - -jq '{ - latest: ( - .entries[0] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ) -}' /tmp/kv-latest.json - -curl -s "$KV_BASE_URL/v0/history" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ - | jq '{ - page_token, - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - value - } - ] - }' -``` - -**Зачем нужен следующий шаг?** +**Переходите дальше, когда** -Поиск последней записи даёт максимально узкий ответ. Повторное использование точного `key` в `POST /v0/history` показывает, не является ли этот ключ частью более широкой индексированной картины. Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](/fastdata/kv/get-history-key). +- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. ## Частые ошибки diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index 3378a7e..99fb2be 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -1,31 +1,82 @@ --- sidebar_label: Examples slug: /neardata/examples -title: "Примеры NEAR Data API" -description: "Пошаговые сценарии использования NEAR Data API для опроса блоков, маршрутов перенаправления и перехода к каноническому RPC." +title: "Примеры NEAR Data" +description: "Пошаговые сценарии для polling по оптимистичным и финализированным блокам и перехода к RPC, когда это нужно." displayed_sidebar: nearDataApiSidebar page_actions: - markdown --- -# Примеры NEAR Data API +## Готовое расследование + +### Поймать новый блок как можно раньше, а затем подтвердить его после finality + +Используйте это расследование, когда нужно заметить новый блок как можно раньше, но финальный ответ всё равно должен опираться на финализированный блок и иногда на точное чтение через RPC. + +**Цель** + +- Быстро заметить недавний блок, а затем проверить то же самое, когда догонит finality. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | +| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | +| Стабильное подтверждение | NEAR Data [`block`](/neardata/block) или [`last-block-final`](/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | +| Лёгкая сводка по блоку | NEAR Data [`block-headers`](/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | +| Точный разбор через RPC | RPC [Блок по ID](/rpc/block/block-by-id) или [Блок по высоте](/rpc/block/block-by-height) | Получаем точный блок, как только понятно, какой именно блок важен | Здесь уже имеет смысл RPC, если нужен тот самый блок-объект, который вернул бы сам протокол | + +**Что должен включать полезный ответ** + +- какое наблюдение по оптимистичному блоку впервые запустило расследование +- когда то же наблюдение стало финализированным +- изменил ли точный разбор через RPC интерпретацию + +### Shell-сценарий + +Используйте этот сценарий, когда вспомогательный маршрут сам выбирает для вас последний финализированный блок, но следующий шаг всё равно требует точной проверки через RPC. + +**Что вы делаете** + +- Смотрите redirect, который возвращает `GET /v0/last_block/final`. +- Загружаете итоговый документ блока. +- Извлекаете `block.header.height` через `jq`. +- Переиспользуете эту высоту в RPC `block` по высоте. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +RPC_URL=https://rpc.mainnet.fastnear.com -Используйте эту страницу, когда свежесть важнее протокольной точности. NEAR Data API предназначен для опроса и чтения недавних данных по семейству блоков: начинайте с самого свежего или самого стабильного режима блоков, который подходит задаче, оставайтесь на этой поверхности для опроса, пока она отвечает на вопрос, и переходите к RPC только тогда, когда действительно нужна каноническая семантика блока или состояния. +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" -## Когда начинать здесь +printf 'Redirect target: %s\n' "$FINAL_LOCATION" -- Нужны недавние оптимистичные или финализированные данные по семейству блоков. -- Нужен клиент опроса, монитор или проверка свежести. -- Маршруты перенаправления приемлемы или полезны для клиентского сценария. -- Задача звучит как «что изменилось недавно?», а не как каноническое историческое подтверждение. +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | tee /tmp/neardata-final-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + +BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "block", + params: { + block_id: ($block_height | tonumber) + } + }')" \ + | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +``` -## Минимальные входы +**Зачем нужен следующий шаг?** -- сеть -- режим свежести: optimistic или finalized -- есть ли конкретная высота или хеш, либо нужен самый свежий объект семейства блоков -- может ли клиент корректно следовать перенаправлениям -- потребуется ли позже проверка через RPC +Helper route — самый простой способ опрашивать сценарий «последний финализированный блок». Как только он сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. ## Частые задачи @@ -43,7 +94,7 @@ page_actions: - Уже можно сообщить о последнем оптимистичном блоке или зафиксировать отставание по свежести. -**Расширяйте, когда** +**Переходите дальше, когда** - Нужна finalized-стабильность вместо максимальной свежести. Переходите к [Финализированному блоку по высоте](/neardata/block) или [Перенаправлению на последний финализированный блок](/neardata/last-block-final). @@ -62,9 +113,9 @@ page_actions: - Уже можно показывать движение финализированных блоков без перехода к более глубоким протокольным деталям. -**Расширяйте, когда** +**Переходите дальше, когда** -- Пользователю нужны точные канонические поля блока или семантика транзакций. Переходите к [RPC Reference](/rpc). +- Пользователю нужны точные поля блока или семантика транзакций. Переходите к [RPC Reference](/rpc). ### Использовать маршруты перенаправления в клиенте опроса @@ -74,17 +125,17 @@ page_actions: **Следующая страница при необходимости** -- Следуйте по канонической цели, которую вернул маршрут перенаправления, и уже там читайте нужные данные по семейству блоков. +- Следуйте по URL блока, который вернул маршрут перенаправления, и уже там читайте нужные данные. **Остановитесь, когда** - Клиент надёжно проходит по маршруту перенаправления и получает нужный ресурс блока. -**Расширяйте, когда** +**Переходите дальше, когда** - Само перенаправление мешает клиенту. Тогда переходите на прямые маршруты блоков. -### Перейти от свежего опроса к каноническому разбору через RPC +### Перейти от опроса свежих блоков к точному RPC-разбору **Начните здесь** @@ -96,86 +147,16 @@ page_actions: **Остановитесь, когда** -- Уже можно чётко назвать недавний блок, который заслуживает канонической проверки. +- Уже можно чётко назвать недавний блок, который заслуживает проверки через RPC. -**Расширяйте, когда** +**Переходите дальше, когда** - Пользователь просит точную структуру данных в терминах протокола, а не просто свежее чтение. -## Готовое расследование - -### Начать с оптимистичного блока, затем подтвердить финализированную и каноническую картину - -Используйте это расследование, когда нужно раннее обнаружение по оптимистичному блоку, но финальный ответ всё равно должен опираться на стабильный финализированный вид и, при необходимости, на каноническое подтверждение через RPC. - -**Цель** - -- Быстро заметить недавнее изменение, а затем сузить его до финализированной и канонической истории блока без лишних запросов. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | -| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | -| Стабильное подтверждение | NEAR Data [`block`](/neardata/block) или [`last-block-final`](/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | -| Лёгкая сводка по блоку | NEAR Data [`block-headers`](/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | -| Каноническая проверка | RPC [Блок по ID](/rpc/block/block-by-id) или [Блок по высоте](/rpc/block/block-by-height) | Получаем точный канонический блок, как только понятно, какой именно блок важен | Переводит расследование от свежих данных к протокольно точному подтверждению только когда это действительно нужно | - -**Что должен включать полезный ответ** - -- какое наблюдение по оптимистичному блоку впервые запустило расследование -- когда то же наблюдение стало финализированным -- изменил ли канонический разбор через RPC интерпретацию - -### Shell-сценарий - -Используйте этот сценарий, когда вспомогательный маршрут должен сам выбирать для вас последний финализированный блок, но следующий шаг всё равно требует канонического подтверждения через RPC. - -**Что вы делаете** - -- Смотрите redirect, который возвращает `GET /v0/last_block/final`. -- Загружаете итоговый документ блока. -- Извлекаете `block.header.height` через `jq`. -- Переиспользуете эту высоту в RPC `block` по высоте. - -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -RPC_URL=https://rpc.mainnet.fastnear.com - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -printf 'Redirect target: %s\n' "$FINAL_LOCATION" - -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' - -BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "block", - params: { - block_id: ($block_height | tonumber) - } - }')" \ - | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' -``` - -**Зачем нужен следующий шаг?** - -Вспомогательный маршрут — самый простой путь опроса для сценария «последний финализированный блок». Как только он сообщил точную высоту блока, правильной следующей поверхностью становится RPC, если нужны канонические семантики этого блока без догадок о том, какой блок проверять. - ## Частые ошибки -- Воспринимать NEAR Data API как потоковый продукт, а не как поверхность для опроса. -- Начинать с канонического RPC, когда настоящая задача — мониторинг свежих блоков. +- Воспринимать NEAR Data как push-стрим, а не как API для опроса. +- Начинать с RPC, когда настоящая задача — мониторинг свежих блоков. - Забывать, что невалидный ключ может вернуть `401` ещё до перенаправления, а сами перенаправления подходят не каждому HTTP-клиенту. - Оставаться на NEAR Data после того, как пользователь уже попросил точные протокольные детали блока. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index be965ec..efcb769 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -10,110 +10,7 @@ page_actions: # Примеры RPC -Используйте эту страницу, когда уже понятно, что ответ должен опираться на каноническое поведение RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы выбрать правильную стартовую страницу, остановиться, как только RPC-ответ уже решает задачу, и расширять набор поверхностей только тогда, когда это действительно поможет. - -## Когда начинать здесь - -- Пользователь просит точное состояние в цепочке или поля в протокольной форме. -- Нужен прямой вызов view-метода контракта или сценарий отправки транзакции. -- Нужно исследовать блоки, чанки, валидаторов или метаданные протокола. -- Важна семантика узла, а не индексированное агрегированное представление. - -## Минимальные входные данные - -- сеть: mainnet или testnet -- основной идентификатор: `account_id`, публичный ключ, ID контракта плюс метод, хеш транзакции или высота/хеш блока -- нужно ли текущее состояние, историческое состояние или поведение отправки/финальности -- должен ли результат остаться каноническим или затем превратиться в более удобное для человека резюме - -## Частые задачи - -### Проверить точное состояние аккаунта или ключа доступа - -**Начните здесь** - -- [View Account](/rpc/account/view-account) для канонических полей аккаунта. -- [View Access Key](/rpc/account/view-access-key) или [View Access Key List](/rpc/account/view-access-key-list) для проверки ключей. - -**Следующая страница при необходимости** - -- [FastNear API full account view](/api/v1/account-full), если после подтверждения канонического состояния нужна ещё и сводка в формате кошелька. -- [Transactions API account history](/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» - -**Остановитесь, когда** - -- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. - -**Расширяйте, когда** - -- Пользователю нужны балансы, NFT, стейкинг или другой продуктовый вид данных. -- Пользователя интересует не текущее каноническое состояние, а недавняя история активности. - -### Проверить блок или снимок состояния протокола - -**Начните здесь** - -- [Block by ID](/rpc/block/block-by-id) или [Block by Height](/rpc/block/block-by-height) для конкретного блока. -- [Latest Block](/rpc/protocol/latest-block) для текущей канонической головы цепочки. -- [Status](/rpc/protocol/status), [Health](/rpc/protocol/health) или [Network Info](/rpc/protocol/network-info) для диагностики узла и сети. - -**Следующая страница при необходимости** - -- [Block Effects](/rpc/block/block-effects), если после поиска блока нужен контекст по изменениям состояния. -- [Transactions API block history](/tx/block) или [Transactions API block range](/tx/blocks), если нужна более читаемая картина исполнения в рамках блока или диапазона. - -**Остановитесь, когда** - -- Канонический ответ блока или протокола уже напрямую отвечает на вопрос. - -**Расширяйте, когда** - -- Нужны данные по свежим блокам в режиме опроса, а не один канонический снимок. Переходите к [NEAR Data API](/neardata). -- Нужна история по нескольким транзакциям, а не только ответ одного блока. Переходите к [Transactions API](/tx). - -### Выполнить view-вызов контракта - -**Начните здесь** - -- [Call Function](/rpc/contract/call-function) для view-метода контракта. -- [View State](/rpc/contract/view-state), когда вопрос касается сырого хранилища контракта. -- [View Code](/rpc/contract/view-code), когда на самом деле нужно понять, есть ли код и каков его хеш. - -**Следующая страница при необходимости** - -- [FastNear API](/api), если после сырого вызова пользователю нужен продуктовый ответ, например по активам или сводке аккаунта. -- [KV FastData API](/fastdata/kv), если следующая задача связана с индексированной историей по ключам и значениям, а не с точным RPC-чтением. - -**Остановитесь, когда** - -- Результат view-вызова уже отвечает на вопрос в канонической форме. - -**Расширяйте, когда** - -- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. -- Вопрос смещается от «что метод возвращает сейчас?» к «что менялось со временем?» - -### Отправить транзакцию и подтвердить результат - -**Начните здесь** - -- [Send Transaction](/rpc/transaction/send-tx), когда нужно каноническое поведение отправки с явной семантикой ожидания. -- [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. -- [Transaction Status](/rpc/transaction/tx-status), чтобы подтвердить канонический результат. - -**Следующая страница при необходимости** - -- [Transactions by Hash](/tx/transactions), если после отправки нужна более читаемая история по транзакции. -- [Receipt Lookup](/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. - -**Остановитесь, когда** - -- У вас уже есть результат отправки и нужный канонический финальный статус. - -**Расширяйте, когда** - -- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. -- Нужен уже не единичный статус, а более широкий сценарий расследования. +Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. ## Готовые сценарии @@ -123,7 +20,7 @@ page_actions: **Что вы делаете** -- Через канонический RPC получаете полный список access key аккаунта. +- Через сам RPC получаете полный список access key аккаунта. - Сужаете этот список до function-call-ключей, привязанных к `social.near`. - Точно проверяете один выбранный ключ перед удалением. - Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. @@ -358,7 +255,7 @@ fi **Зачем нужен следующий шаг?** -Повторный вызов `view_access_key_list` замыкает сценарий на той же канонической поверхности, с которой вы начинали поиск. Если ключ исчез именно там, дополнительная индексированная сводка уже не нужна, чтобы подтвердить удаление. +Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. ### Проверить регистрацию FT storage и затем перевести токены @@ -377,10 +274,10 @@ fi **Что вы делаете** -- Через канонические RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. +- Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. - При необходимости получаете минимальный размер storage deposit. - Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. -- Подтверждаете баланс получателя тем же каноническим view-методом контракта. +- Подтверждаете баланс получателя тем же view-методом самого контракта. ```bash export NETWORK_ID=testnet @@ -627,7 +524,7 @@ curl -s "$RPC_URL" \ }' ``` -6. Подтвердите FT-баланс получателя каноническим view-методом контракта. +6. Подтвердите FT-баланс получателя тем же view-методом контракта. ```bash RECEIVER_BALANCE_ARGS_BASE64="$( @@ -659,7 +556,422 @@ curl -s "$RPC_URL" \ **Зачем нужен следующий шаг?** -Это канонический RPC-сценарий, потому что каждый шаг остаётся на точном состоянии контракта и точной семантике отправки транзакций: сначала вы доказываете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. + +### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? + +Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». + +Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: + +- есть ли у целевого аккаунта storage на `social.near`? +- если есть, осталось ли там ещё место? +- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? + +**Официальные ссылки** + +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +**Что вы делаете** + +- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. +- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. +- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. +- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mike.near +export SIGNER_ACCOUNT_ID=mike.near +``` + +1. Сначала проверьте сам аккаунт signer. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/social-publish-signer.json >/dev/null + +jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + signer_account_id: $signer_account_id, + amount: .result.amount, + locked: .result.locked, + storage_usage: .result.storage_usage +}' /tmp/social-publish-signer.json +``` + +Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. + +2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. + +```bash +SOCIAL_STORAGE_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_account_storage", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-account-storage.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + storage: (.result.result | implode | fromjson), + storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) +}' /tmp/social-account-storage.json +``` + +Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. + +3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. + +```bash +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + jq -n --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + signer_matches_target: true, + permission_granted: true, + reason: "owner write" + }' +else + WRITE_PERMISSION_ARGS_BASE64="$( + jq -nc \ + --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ + --arg key "$ACCOUNT_ID" '{ + predecessor_id: $predecessor_id, + key: $key + }' | base64 | tr -d '\n' + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + signer_matches_target: false, + permission_granted: (.result.result | implode | fromjson) + }' +fi +``` + +4. Сведите проверку storage и разрешения в один читаемый итог. + +```bash +AVAILABLE_BYTES="$( + jq -r ' + .result.result + | if length == 0 then "0" + else (implode | fromjson | .available_bytes // 0 | tostring) + end + ' /tmp/social-account-storage.json +)" + +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + PERMISSION_GRANTED=true +else + PERMISSION_GRANTED="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq -r '.result.result | implode | fromjson' + )" +fi + +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ + --argjson available_bytes "$AVAILABLE_BYTES" \ + --argjson permission_granted "$PERMISSION_GRANTED" '{ + account_id: $account_id, + signer_account_id: $signer_account_id, + storage_ready: ($available_bytes > 0), + permission_ready: $permission_granted, + ready_to_publish_now: (($available_bytes > 0) and $permission_granted) + }' +``` + +Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. + +**Зачем нужен следующий шаг?** + +Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. + +### Правда ли, что `efiz.near` опубликовал `DonateNEARtoEfiz`, и что этот виджет делает? + +Используйте этот сценарий, когда история более лёгкая и даже немного шуточная: «друг говорит, что `efiz.near` когда-то опубликовал виджет буквально с именем `DonateNEARtoEfiz`. Проверь это и покажи, что именно делает этот виджет, не выходя из RPC». + +Этот пример намеренно сделан для удовольствия. Он не учит ничему особенно глубокому про async-исполнение. Он просто показывает, как точными чтениями SocialDB просматривать каталог BOS-автора и отвечать на один очень конкретный вопрос по живым on-chain-данным. + +**Официальные ссылки** + +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +**Что вы делаете** + +- Спрашиваете у `social.near` каталог виджетов под `efiz.near`. +- Сохраняете высоты блоков, потому что они показывают, когда каждый ключ виджета последний раз переписывался. +- Подтверждаете, что `DonateNEARtoEfiz` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. +- Заканчиваете простым handoff: если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](/tx/examples). + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=efiz.near +export WIDGET_NAME=DonateNEARtoEfiz +``` + +1. Получите каталог виджетов и сохраните высоты блоков последней записи. + +```bash +WIDGET_KEYS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "keys", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-keys.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].widget + | to_entries + | sort_by(.value * -1) + | map({ + widget_name: .key, + last_write_block: .value + }) + | .[0:20] +' /tmp/social-widget-keys.json +``` + +Это даёт компактный BOS-каталог. На момент написания у `efiz.near` был удивительно эклектичный набор виджетов, включая `ReversedFeed`, `HelloWorld`, `PotlockDonateAll` и `DonateNEARtoEfiz`, но настоящий источник истины здесь — именно живой запрос. + +2. Подтвердите, что `DonateNEARtoEfiz` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. + +```bash +WIDGET_GET_ARGS_BASE64="$( + jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-source.json >/dev/null + +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:25] + | join("\n") + ' /tmp/social-widget-source.json +``` + +Так вы печатаете первые 25 строк исходника и быстро понимаете, что это вообще за компонент. В живой версии на момент написания код инициализирует `reciever: "efiz.near"` и строит кнопку, которая вызывает `donate` на выбранную сумму. Название виджета честно предупреждает, что будет дальше. + +3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. + +```bash +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + ' /tmp/social-widget-keys.json \ + | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +``` + +На момент написания живая высота последней записи для `efiz.near/widget/DonateNEARtoEfiz` была `92543301`. + +Если следующий вопрос уже меняется на «какая транзакция записала именно эту версию виджета?», сохраните эту высоту блока и переходите к сценариям-доказательствам NEAR Social в [Transactions Examples](/tx/examples). + +**Зачем нужен следующий шаг?** + +Это хорошее напоминание, что RPC может быть не только судебной экспертизой. `keys` позволяет просматривать каталог BOS-автора как разработчику, а `get` даёт возможность заглянуть в точное тело виджета, которое реально лежит on-chain. Иногда ответ и правда такой: «да, ваш друг действительно опубликовал виджет с именем `DonateNEARtoEfiz`, и вот его код». + +## Частые задачи + +### Проверить точное состояние аккаунта или ключа доступа + +**Начните здесь** + +- [View Account](/rpc/account/view-account) для точных полей аккаунта. +- [View Access Key](/rpc/account/view-access-key) или [View Access Key List](/rpc/account/view-access-key-list) для проверки ключей. + +**Следующая страница при необходимости** + +- [FastNear API full account view](/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. +- [Transactions API account history](/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» + +**Остановитесь, когда** + +- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. + +**Переходите дальше, когда** + +- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. +- Пользователя интересует не текущее состояние, а недавняя история активности. + +### Проверить один точный блок или снимок состояния протокола + +**Начните здесь** + +- [Block by ID](/rpc/block/block-by-id) или [Block by Height](/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. +- [Latest Block](/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» +- [Status](/rpc/protocol/status), [Health](/rpc/protocol/health) или [Network Info](/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. + +**Следующая страница при необходимости** + +- [Block Effects](/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. +- [Transactions API block history](/tx/block) или [Transactions API block range](/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» + +**Остановитесь, когда** + +- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. + +**Переходите дальше, когда** + +- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](/neardata). +- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](/tx). + +### Что этот контракт возвращает прямо сейчас? + +**Начните здесь** + +- [Call Function](/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. +- [View State](/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. +- [View Code](/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» + +**Следующая страница при необходимости** + +- [FastNear API](/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. +- [KV FastData API](/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» + +**Остановитесь, когда** + +- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. + +**Переходите дальше, когда** + +- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. +- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» + +### Отправить транзакцию и подтвердить результат + +**Начните здесь** + +- [Send Transaction](/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. +- [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. +- [Transaction Status](/rpc/transaction/tx-status), чтобы подтвердить финальный результат. + +**Следующая страница при необходимости** + +- [Transactions by Hash](/tx/transactions), если после отправки нужна более читаемая история по транзакции. +- [Receipt Lookup](/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. +- [Transactions Examples](/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» + +**Остановитесь, когда** + +- У вас уже есть результат отправки и нужный финальный статус. + +**Переходите дальше, когда** + +- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. +- Нужен уже не единичный статус, а более широкий сценарий расследования. ## Частые ошибки diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx index 47a5f34..0bcc619 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx @@ -1,32 +1,65 @@ --- sidebar_label: Examples -sidebar_position: 3 slug: /snapshots/examples -title: "Примеры снапшотов" -description: "Пошаговые операторские сценарии для выбора правильного пути снапшотов FastNear и выполнения типовых задач запуска и восстановления." +title: "Примеры Snapshot" +description: "Пошаговые операторские сценарии для выбора правильного пути восстановления через FastNear snapshots." displayed_sidebar: snapshotsSidebars page_actions: - markdown --- -# Примеры снапшотов +## Готовое расследование + +### Выбрать и выполнить правильный сценарий восстановления mainnet + +Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. + +**Цель** + +- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. + +| Путь или команда | Как используем | Зачем используем | +| --- | --- | --- | +| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | +| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | +| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | +| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | +| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | -Используйте эту страницу, когда вопрос звучит как «какой сценарий со снапшотом вообще запускать?», а не «какая команда существует?». Снапшоты — это операторские сценарии, а не API для данных: сначала выбирайте правильную цель узла, сеть и схему хранения, а затем запускайте самый узкий путь загрузки, который безопасно поднимет узел. +**Что должен включать полезный ответ** -## Когда начинать здесь +- какой сценарий восстановления выбран и почему +- какие ключевые env vars важны для выбранного пути +- куда на диске должны попасть данные +- должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap -- Нужно поднять или восстановить узел. -- Нужно выбрать между обычным RPC, optimized `fast-rpc` и архивным сценарием с hot/cold-данными. -- Нужен самый короткий путь от задачи оператора к правильной последовательности команд. -- Уже понятно, что это инфраструктурная работа, а не чтение прикладных данных. +### Shell-сценарий -## Минимальные входы +Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. -- сеть: mainnet или testnet -- цель узла: обычный RPC, optimized `fast-rpc` или архивный режим -- схема хранения, особенно возможность разделить hot- и cold-архивные данные -- используется ли стандартный каталог данных nearcore или отдельные каталоги для hot/cold -- целевой путь к данным и примерные ограничения по загрузке +**Что вы делаете** + +- Один раз получаете последнюю высоту архивного снапшота mainnet. +- Сохраняете её в `LATEST`. +- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. + +```bash +HOT_DATA_PATH=~/.near/data +COLD_DATA_PATH=/mnt/hdds/cold-data + +LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" +echo "Latest archival mainnet snapshot block: $LATEST" + +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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash +``` + +**Зачем нужен следующий шаг?** + +Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. ## Частые задачи @@ -44,7 +77,7 @@ page_actions: - Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. -**Расширяйте, когда** +**Переходите дальше, когда** - На самом деле требуется архивное хранение, а не просто быстрый запуск. @@ -62,7 +95,7 @@ page_actions: - Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. -**Расширяйте, когда** +**Переходите дальше, когда** - Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. @@ -80,9 +113,9 @@ page_actions: - План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. -**Расширяйте, когда** +**Переходите дальше, когда** -- Оператору нужны уже более широкие nearcore bootstrap-guides, а не только FastNear snapshots. +- Оператору нужны уже общие гайды nearcore по bootstrap, а не только FastNear snapshots. ### Поднять архивные hot-данные в testnet @@ -98,63 +131,10 @@ page_actions: - Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. -**Расширяйте, когда** +**Переходите дальше, когда** - Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. -## Готовое расследование - -### Выбрать и выполнить правильный сценарий восстановления mainnet - -Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно быстро понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. - -**Цель** - -- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную безопасную последовательность команд. - -| Путь или команда | Как используем | Зачем используем | -| --- | --- | --- | -| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | -| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | -| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | -| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | -| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | - -**Что должен включать полезный ответ** - -- какой сценарий восстановления выбран и почему -- какие ключевые env vars важны для выбранного пути -- куда на диске должны попасть данные -- должен ли оператор оставаться в FastNear snapshot docs или переходить к более широкой документации nearcore - -### Shell-сценарий - -Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. - -**Что вы делаете** - -- Один раз получаете последнюю высоту архивного снапшота mainnet. -- Сохраняете её в `LATEST`. -- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. - -```bash -HOT_DATA_PATH=~/.near/data -COLD_DATA_PATH=/mnt/hdds/cold-data - -LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" -echo "Latest archival mainnet snapshot block: $LATEST" - -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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash -``` - -**Зачем нужен следующий шаг?** - -Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. - ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md index f6c3747..f693e59 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -2,100 +2,23 @@ sidebar_label: Examples slug: /transfers/examples title: "Примеры Transfers API" -description: "Пошаговые сценарии использования Transfers API для узкой истории переводов, пагинации и перехода к более широкому расследованию." +description: "Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций." displayed_sidebar: transfersApiSidebar page_actions: - markdown --- -# Примеры Transfers API - -Используйте эту страницу, когда вопрос касается именно движения активов и нужен самый короткий путь по документации истории переводов. Эта поверхность специально узкая: начинайте с самого точного фильтра перевода, который отвечает на вопрос, держите фокус на отправках и получениях и расширяйтесь только тогда, когда вопрос перестаёт быть «только про переводы». - -## Когда начинать здесь - -- Пользователя интересуют входящие или исходящие переводы NEAR или FT. -- Нужна лента кошелька, представление для аудита или ответ для поддержки, сфокусированный на движении активов. -- Аккаунт уже известен, и пока не требуется полная история исполнения. -- Для задачи достаточно mainnet-истории переводов. - -## Минимальные входы - -- `account_id` -- выбора сети здесь нет: сегодня эта поверхность доступна только для mainnet -- опциональные фильтры по направлению, активу, сумме или времени -- нужен ли только короткий набор событий или длинный обзор истории -- может ли позже понадобиться более широкий контекст транзакций - -## Частые задачи - -### Найти исходящие переводы одного аккаунта в узком окне времени - -**Начните здесь** - -- [Запрос переводов](/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. - -**Следующая страница при необходимости** - -- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. - -**Остановитесь, когда** - -- Уже можно ответить, кто что отправил, когда и в каком активе. - -**Расширяйте, когда** - -- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](/tx). - -### Построить ленту переводов с пагинацией через `resume_token` - -**Начните здесь** - -- [Запрос переводов](/transfers/query) для первой страницы недавних событий. - -**Следующая страница при необходимости** - -- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. - -**Остановитесь, когда** - -- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. - -**Расширяйте, когда** - -- Пользователь просит метаданные транзакции сверх самих переводов. -- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](/api). - -### Перейти от истории переводов к полному расследованию транзакции - -**Начните здесь** - -- [Запрос переводов](/transfers/query), чтобы выделить конкретные интересующие переводы. - -**Следующая страница при необходимости** - -- [История аккаунта в Transactions API](/tx/account), если нужна окружающая история исполнения для того же аккаунта. -- [Transactions by Hash](/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. - -**Остановитесь, когда** - -- Уже определено правильное событие перевода и следующий подходящий раздел для расследования. - -**Расширяйте, когда** - -- Пользователю прямо нужны квитанции или каноническое подтверждение через RPC. Сначала переходите к [Transactions API](/tx), затем к [RPC Reference](/rpc), если потребуется. - ## Готовый сценарий -### Запросить узкое окно переводов, а затем перейти по receipt +### Найти один подозрительный перевод, а затем пройти по его receipt -Используйте этот сценарий, когда первый вопрос всё ещё касается только переводов, но вы уже понимаете, что потом может понадобиться одна точная точка перехода в контекст исполнения. +Используйте этот сценарий, когда история звучит так: «я вижу, что средства двигались, но хочу получить точную опорную точку исполнения для этого движения, не затягивая сразу всю историю аккаунта». **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Извлекаете первый `receipt_id` через `jq`. -- Переиспользуете этот receipt ID в Transactions API, чтобы перейти от движения актива к контексту исполнения. +- Выделяете один перевод, который действительно похож на нужное вам движение. +- Переиспользуете его `receipt_id` в Transactions API, чтобы перейти от движения актива к истории исполнения. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com @@ -150,14 +73,73 @@ curl -s "$TX_BASE_URL/v0/receipt" \ **Зачем нужен следующий шаг?** -Запрос переводов позволяет держать первый проход узким и удобным для пагинации. Переход по `receipt_id` даёт одну точную опорную точку в исполнении, не заставляя сразу расширяться до полной истории аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. +Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` даёт точную опорную точку в исполнении, не затягивая вас сразу в полную историю аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. + +## Частые задачи + +### Найти исходящие переводы одного аккаунта в узком окне времени + +**Начните здесь** + +- [Запрос переводов](/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. + +**Следующая страница при необходимости** + +- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. + +**Остановитесь, когда** + +- Уже можно ответить, кто что отправил, когда и в каком активе. + +**Переходите дальше, когда** + +- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](/tx). + +### Листать ленту переводов дальше и не потерять своё место + +**Начните здесь** + +- [Запрос переводов](/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. + +**Следующая страница при необходимости** + +- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. +- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. + +**Остановитесь, когда** + +- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. + +**Переходите дальше, когда** + +- Пользователь просит метаданные транзакции сверх самих переводов. +- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](/api). + +### Перейти от истории переводов к полному расследованию транзакции + +**Начните здесь** + +- [Запрос переводов](/transfers/query), чтобы выделить конкретные интересующие переводы. + +**Следующая страница при необходимости** + +- [История аккаунта в Transactions API](/tx/account), если нужна окружающая история исполнения для того же аккаунта. +- [Transactions by Hash](/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. + +**Остановитесь, когда** + +- Уже определено правильное событие перевода и понятно, какой API открывать следующим. + +**Переходите дальше, когда** + +- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](/tx), затем к [RPC Reference](/rpc), если потребуется. ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. - Считать историю переводов полной историей исполнения. - Переиспользовать `resume_token` с другими фильтрами. -- Начинать здесь с вопросов про testnet, хотя эта поверхность сегодня работает только в mainnet. +- Начинать здесь с вопросов про testnet, хотя этот API сегодня работает только в mainnet. ## Полезные связанные страницы diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index ba2577c..b9892af 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -2,167 +2,464 @@ sidebar_label: Examples slug: /tx/examples title: "Примеры Transactions API" -description: "Пошаговые сценарии использования Transactions API для поиска транзакций, расследования квитанций, истории аккаунтов и анализа диапазонов блоков." +description: "Пошаговые расследования для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents." displayed_sidebar: transactionsApiSidebar page_actions: - markdown --- -# Примеры Transactions API +## Готовые расследования -Используйте эту страницу, когда вопрос звучит как «что произошло?» и нужен индексированный исторический слой до того, как вы перейдёте к каноническому подтверждению через RPC. Начинайте с того идентификатора, который уже есть на руках, объясняйте историю исполнения в читаемом порядке и расширяйте её только тогда, когда действительно понадобятся точные RPC-семантики. +### Проследить асинхронную promise-цепочку и доказать порядок callback-ов -## Когда начинать здесь +Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» -- У вас уже есть хеш транзакции, ID квитанции, ID аккаунта или ограниченный диапазон блоков. -- Пользователю нужен исторический контекст исполнения, разбор для поддержки или отладки либо читаемая временная шкала. -- Нужна индексированная история без ручной сборки из сырых RPC-вызовов. -- Первый ответ должен объяснить, что произошло, до углубления в протокольные детали. +**Цель** -## Минимальные входные данные +- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. + +Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: + +- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом +- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке +- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и +- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору + +```mermaid +flowchart LR + Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] + H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] + R --> C["Async cross-contract callback-и"] + C --> B["Recorder state
beta"] + B --> A["Recorder state
alpha"] + A --> G["Recorder state
gamma"] + Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] + R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] + G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] +``` -- сеть: mainnet или testnet -- основной идентификатор: хеш транзакции, ID квитанции, `account_id` или блок/диапазон блоков -- расследуете ли вы один объект или целое окно истории -- требуется ли точное каноническое подтверждение через RPC до завершения ответа +Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. -## Частые задачи +Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. -### Найти одну транзакцию +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | +| Проверка готовности promise-работы | RPC [`query(call_function)`](/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | +| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | +| Снимки downstream-состояния | RPC [`query(call_function)`](/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | +| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | +| Реконструкция по блокам | Transactions API [`POST /v0/block`](/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | +| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | +| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | -**Начните здесь** +**Что должен включать полезный ответ** -- [Transactions by Hash](/tx/transactions), когда идентификатор транзакции уже известен. +- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» +- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования +- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался +- в каких блоках стали видны изменения состояния +- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования -**Следующая страница при необходимости** +### Превратить один страшный receipt ID из логов в понятную человеческую историю -- [Receipt Lookup](/tx/receipt), если важной стала последующая квитанция. -- [Block](/tx/block), если нужен контекст блока. -- [Transaction Status](/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. +Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. -**Остановитесь, когда** +**Цель** -- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. +- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. -**Расширяйте, когда** +Для этого зафиксированного примера «страшный receipt ID из логов» такой: -- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. -- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. +- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` +- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- высота блока транзакции: `194263342` +- высота блока исполнения receipt: `194263343` -### Исследовать квитанцию +Человеческая история за этим receipt простая: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. -**Начните здесь** +```mermaid +flowchart LR + L["Один страшный receipt ID
5GhZcpfK..."] --> R["Ищем receipt"] + R --> T["Восстанавливаем tx hash
AdgNifPY..."] + T --> S["Читаем действия транзакции"] + S --> H["Человеческая история:
mike.near отправил 5 NEAR в global-counter.mike.near"] +``` -- [Receipt Lookup](/tx/receipt), когда ID квитанции — лучший якорь для расследования. +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Якорь по квитанции | Transactions API [`POST /v0/receipt`](/tx/receipt) | Сначала ищем ID квитанции и печатаем аккаунты, блок исполнения, флаг успеха и связанный хеш транзакции | Даёт самый короткий путь от сырого receipt ID к пониманию, что вообще за объект перед вами | +| История транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | +| Каноническое продолжение | RPC [`tx`](/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | -**Следующая страница при необходимости** +**Что должен включать полезный ответ** -- [Transactions by Hash](/tx/transactions), чтобы связать квитанцию с исходной транзакцией. -- [Account History](/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. +- какие аккаунты создали и исполнили квитанцию +- к какой транзакции относится эта квитанция +- что транзакция на самом деле сделала +- была ли квитанция главным событием или только шагом в большом каскаде +- одно предложение простым языком, которое можно без правок вставить коллеге в чат -**Остановитесь, когда** +### Доказать, что одно неудачное действие сорвало весь пакет -- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. +Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? -**Расширяйте, когда** +В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. -- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](/rpc). -- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. +**Цель** -### Посмотреть недавнюю активность аккаунта +- На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. -**Начните здесь** +**Официальные ссылки** -- [Account History](/tx/account) для ленты активности по аккаунту. +- [Основы транзакций](/transaction-flow/foundations) +- [Исполнение в рантайме](/transaction-flow/runtime-execution) + +Этот зафиксированный сбой был получен в **testnet 18 апреля 2026 года**: + +- хеш транзакции: `CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M` +- аккаунт signer: `temp.mike.testnet` +- целевой новый аккаунт: `rollback-mo4vmkig.temp.mike.testnet` +- высота включающего блока: `246365118` +- хеш включающего блока: `6f5zTKDqQRwrxMywzvxeRvYcCERJmAnatJaqUEtQYUNM` +- порядок действий: `CreateAccount -> Transfer -> AddKey -> FunctionCall` +- упавший метод: `definitely_missing_method` +- RPC-ошибка: `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet` + +```mermaid +flowchart LR + T["Одна подписанная транзакция"] --> A["CreateAccount"] + A --> B["Transfer 0.01 NEAR"] + B --> C["AddKey"] + C --> D["FunctionCall definitely_missing_method()"] + D --> E["Сбой: CodeDoesNotExist"] + E --> R["Весь пакет не закрепился"] + R --> N["Новый аккаунт не появился"] + R --> K["Новый ключ не закрепился"] + R --> F["У получателя нет профинансированного состояния"] +``` -**Следующая страница при необходимости** +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Задуманный пакет | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем зафиксированный хеш транзакции и печатаем упорядоченный список действий, получателя и метаданные включающего блока | Показывает, что именно signer пытался сделать, ещё до разговора о том, что закрепилось | +| Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | +| Доказательство по состоянию после исполнения | RPC [`query(view_account)`](/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | -- [Transactions by Hash](/tx/transactions) для конкретной транзакции из ленты. -- [Receipt Lookup](/tx/receipt), если фокус смещается на одну квитанцию. +Перед shell-сценарием важно отметить одну деталь: индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. -**Остановитесь, когда** +**Что должен включать полезный ответ** -- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. +- точный порядок действий, который отправил signer +- какой индекс действия упал и почему +- высоту и хеш включающего блока для этого батча +- доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality +- короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` -**Расширяйте, когда** +### Shell-сценарий неудачной транзакции с пакетом действий -- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](/transfers). -- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](/rpc) или [FastNear API](/api). +Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. -### Восстановить ограниченное окно по блокам +**Что вы делаете** -**Начните здесь** +- Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. +- Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. +- Через один RPC-запрос к состоянию после исполнения доказываете, что новый аккаунт так и не появился после finality. -- [Blocks](/tx/blocks) для ограниченного просмотра диапазона блоков. -- [Block](/tx/block), когда известен точный блок, который нужно исследовать. +```bash +TX_BASE_URL=https://tx.test.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M +SIGNER_ACCOUNT_ID=temp.mike.testnet +NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +``` -**Следующая страница при необходимости** +1. Получите транзакцию и распечатайте задуманный пакет действий. -- [Transactions by Hash](/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. -- [Receipt Lookup](/tx/receipt), если одной квитанции достаточно для следующего шага расследования. +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/failed-batch-transaction.json >/dev/null -**Остановитесь, когда** +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height, + included_block_hash: .transactions[0].execution_outcome.block_hash + }, + batch: { + action_count: (.transactions[0].transaction.actions | length), + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + final_function_call_method_name: ( + .transactions[0].transaction.actions[3].FunctionCall.method_name + ) + }, + first_receipt_handoff: .transactions[0].transaction_outcome.outcome.status +}' /tmp/failed-batch-transaction.json + +# Ожидаемый порядок действий: +# 1. CreateAccount +# 2. Transfer +# 3. AddKey +# 4. FunctionCall +``` -- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. +2. Запросите RPC transaction status и посмотрите точную верхнеуровневую ошибку. -**Расширяйте, когда** +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/failed-batch-rpc-status.json >/dev/null -- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](/rpc). -- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](/neardata). +jq '{ + final_execution_status: .result.final_execution_status, + failed_action_index: .result.status.Failure.ActionError.index, + failure: .result.status.Failure.ActionError.kind.FunctionCallError.CompilationError.CodeDoesNotExist +}' /tmp/failed-batch-rpc-status.json -## Готовые расследования +# Ожидаемый failed_action_index: 3 +# Ожидаемый failure account_id: rollback-mo4vmkig.temp.mike.testnet +``` -### Доказать порядок callback-ов в staged/release-сценарии +3. Запросите предполагаемый новый аккаунт после finality и докажите, что его всё ещё нет. -Используйте это расследование, когда сначала была стадия с асинхронной подготовкой работы, потом отдельный release, и нужно доказать не только успешность транзакций, но и конкретный порядок выполнения последующих обратных вызовов. +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/failed-batch-view-account.json >/dev/null -**Цель** +jq '{ + error: .error.cause.name, + message: .error.data, + requested_account_id: .error.cause.info.requested_account_id, + proof_block_height: .error.cause.info.block_height +}' /tmp/failed-batch-view-account.json -- Превратить два хеша транзакций в устойчивый артефакт для расследования, который включает граф квитанций, привязки к блокам и изменения состояния контракта. +# Ожидаемая ошибка: "UNKNOWN_ACCOUNT" +``` -В staged/release-сценариях именно stage-транзакция обычно остаётся главной опорной транзакцией расследования, потому что отложенные callback-и живут на её исходном дереве транзакции, а не на дереве release-транзакции. +Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Захват трассы stage и release | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Запрашиваем хеш stage-транзакции и хеш release-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому дереву транзакции | -| Проверка материализации stage | RPC [`query(call_function)`](/rpc/contract/call-function) | Опрашиваем view-метод staging-контракта, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления ожидаемых отложенных шагов | Подтверждает, что callback-и действительно стали доступны до того, как release-транзакция попытается их разбудить | -| Обогащение транзакций | Transactions API [`POST /v0/transactions`](/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id` и индексированный статус исполнения | Даёт каждой транзакции устойчивую привязку к блоку, чтобы дальнейший анализ не зависел от памяти или ручных заметок | -| Снимки состояния контракта recorder | RPC [`query(call_function)`](/rpc/contract/call-function) | Читаем состояние контракта recorder до release, а затем опрашиваем его после release до появления ожидаемых записей | Доказывает реальный порядок последующих эффектов в состоянии контракта, а не только в метаданных дерева квитанций | -| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | -| Реконструкция по блокам | Transactions API [`POST /v0/block`](/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | -| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | -| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | +**Зачем нужен следующий шаг?** -**Что должен включать полезный ответ** +Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. -- почему именно stage-транзакция, а не release-транзакция, обычно является главной опорной транзакцией расследования -- какой порядок callback-ов вы наблюдали -- в каких блоках стали видны изменения состояния -- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования +### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? -### Начать с receipt ID и восстановить историю исполнения +Используйте это расследование, когда один вызов контракта залогировал успех, изменил своё локальное состояние, и даже верхнеуровневый RPC `status` выглядит успешным, но приложение всё равно сломалось, потому что позже упал отдельный cross-contract receipt. -Используйте это расследование, когда на руках есть только receipt ID из трассы, лога ошибки или дерева callback-ов и нужно вернуться к понятной человеку истории того, что произошло. +Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. **Цель** -- Перейти от одной квитанции к исходной транзакции, а затем расширить расследование ровно настолько, чтобы объяснить окружающее исполнение и эффекты в состоянии. +- Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. + +**Официальные ссылки** + +- [Основы транзакций](/transaction-flow/foundations) +- [Исполнение в рантайме](/transaction-flow/runtime-execution) + +Этот зафиксированный асинхронный сбой был получен в **testnet 18 апреля 2026 года**: + +- хеш транзакции: `AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz` +- аккаунт signer: `temp.mike.testnet` +- первый контракт-получатель: `seq-dr.mike.testnet` +- аккаунт detached-цели: `asyncfail-in2hwikn.temp.mike.testnet` +- блок включения транзакции: `246368568` +- успешная первая receipt: `6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS` в блоке `246368569` +- более поздняя упавшая receipt: `2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es` в блоке `246368570` +- первый метод: `kickoff_append` +- более поздний упавший метод: `append` +- верхнеуровневый RPC `status`: `SuccessValue` + +```mermaid +flowchart LR + T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] + R --> S["Роутер сохраняет локальное состояние
kicked += late-failure"] + R --> D["Detached cross-contract receipt
append(...)"] + D --> F["Более поздний сбой
CodeDoesNotExist"] + S -. "состояние из первой receipt всё равно закрепилось" .-> K["kicked() всё ещё содержит late-failure"] +``` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Якорь по квитанции | Transactions API [`POST /v0/receipt`](/tx/receipt) | Сначала ищем ID квитанции и определяем её содержимое, статус и связанный контекст транзакции | ID квитанции часто появляется в трассах и логах раньше, чем у человека складывается цельная история транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | После перехода из поиска квитанции забираем исходную транзакцию по хешу | Превращает одну квитанцию в читаемую историю исполнения с контекстом по получателю, блоку и статусу | -| Каноническое подтверждение | RPC [`tx`](/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Подтверждаем результат на уровне протокола, когда индексированного вида недостаточно или нужны точные RPC-семантики | Полезно, когда важно различить индексированную интерпретацию и точное поведение RPC | -| Контекст блока | Transactions API [`POST /v0/block`](/tx/block) | Загружаем содержащий блок и при необходимости расширяемся на соседние каскадные блоки, если исполнение растянулось по нескольким высотам | Помещает квитанцию во временную шкалу по блокам, которую проще объяснить | -| Окно активности аккаунта | Transactions API [`POST /v0/account`](/tx/account) | Забираем недавнюю активность аккаунтов, которых коснулась квитанция | Помогает связать квитанцию с окружающей историей по аккаунтам | -| Повторное чтение состояния | RPC [`query(call_function)`](/rpc/contract/call-function) | Повторно запускаем нужный view-метод с закреплённым `block_id`, если квитанция изменила видимое состояние контракта | Позволяет доказать, что квитанция не только существовала в метаданных, но и изменила устойчивое состояние контракта | +| Каркас транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | +| Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | +| Текущее состояние контракта | RPC [`query(call_function)`](/rpc/contract/call-function) | Вызываем `seq-dr.mike.testnet.kicked()` | Показывает, что локальное изменение состояния из первой receipt закрепилось, хотя более поздняя detached-receipt упала | + +Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не откатил более раннее изменение состояния роутера. **Что должен включать полезный ответ** -- какую исходную транзакцию вы восстановили из квитанции -- была ли квитанция главным событием или только одним шагом в большом каскаде -- какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить -- был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым +- что подписанная транзакция успешно передала управление в первую router-receipt +- что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` +- что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` +- что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился +- одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции + +### Shell-сценарий более позднего сбоя receipt + +Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». + +**Что вы делаете** + +- Читаете транзакцию и её таймлайн receipt из индексированного представления. +- Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. +- Читаете текущее состояние роутера, чтобы показать: локальный побочный эффект первой receipt закрепился. + +```bash +TX_BASE_URL=https://tx.test.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz +SIGNER_ACCOUNT_ID=temp.mike.testnet +ROUTER_ACCOUNT_ID=seq-dr.mike.testnet +FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS +FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es +``` + +1. Получите транзакцию и распечатайте таймлайн receipt по порядку блоков. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/later-receipt-failure-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + tx_handoff: .transactions[0].transaction_outcome.outcome.status + }, + receipts: [ + .transactions[0].receipts[] + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + block_height: .execution_outcome.block_height, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system_transfer"), + status: .execution_outcome.outcome.status + } + ] +}' /tmp/later-receipt-failure-transaction.json + +# На что смотреть: +# - первая receipt контракта на seq-dr.mike.testnet успешно прошла в блоке 246368569 +# - более поздняя receipt append(...) упала в блоке 246368570 +``` + +2. Запросите RPC transaction status и сравните верхнеуровневую историю с более поздней упавшей receipt. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/later-receipt-failure-rpc.json >/dev/null + +jq \ + --arg first_receipt_id "$FIRST_RECEIPT_ID" \ + --arg failed_receipt_id "$FAILED_RECEIPT_ID" '{ + top_level_status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status, + first_contract_receipt: ( + .result.receipts_outcome[] + | select(.id == $first_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + logs: .outcome.logs, + status: .outcome.status + } + ), + later_failed_receipt: ( + .result.receipts_outcome[] + | select(.id == $failed_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + status: .outcome.status + } + ) + }' /tmp/later-receipt-failure-rpc.json + +# На что смотреть: +# - top_level_status всё ещё равен SuccessValue +# - первая receipt контракта залогировала dishonest_router:kickoff:late-failure +# - более поздняя receipt append(...) упала с CodeDoesNotExist +``` + +3. Прочитайте текущее состояние роутера и подтвердите, что локальный побочный эффект первой receipt закрепился. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ROUTER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "kicked", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/later-receipt-failure-kicked.json >/dev/null + +jq '{ + kicked: (.result.result | implode | fromjson), + contains_late_failure: ((.result.result | implode | fromjson) | index("late-failure") != null) +}' /tmp/later-receipt-failure-kicked.json +``` + +Этот последний read и есть практическое доказательство того, что локальное изменение из первой receipt закрепилось. Более поздняя упавшая receipt не откатила более ранний `kicked.push(...)` внутри роутера. + +**Зачем нужен следующий шаг?** + +Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. ### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB @@ -546,61 +843,252 @@ jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" ' NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. -### Понять двухстороннее сопоставление `token_diff`, а затем проследить живой расчёт NEAR Intents +### Какая транзакция записала `DonateNEARtoEfiz`? + +Используйте это расследование, когда история уже более лёгкая и даже немного шуточная: «на странице RPC-примеров я уже увидел, что `efiz.near/widget/DonateNEARtoEfiz` существует и что его last-write block — `92543301`. Какая именно транзакция записала этот виджет?» + +Это весёлый пример, но рецепт доказательства здесь вполне серьёзный и знакомый по другим SocialDB-расследованиям: -Используйте это расследование, когда история звучит так: «покажи, что именно NEAR Intents делает под капотом, но привяжи разбор к публичным данным, которые можно проверить самостоятельно». +- стартуем с одного блока, к которому привязан SocialDB-ключ +- превращаем этот блок в конкретный `efiz.near -> social.near` receipt +- восстанавливаем исходную транзакцию +- декодируем payload `set` и доказываем, что в нём действительно лежит исходник виджета **Цель** -- Сначала объяснить модель сопоставления, а затем превратить один реальный расчёт через `intents.near` в читаемую историю исполнения на базе Transactions API и канонического RPC. +- Превратить last-write block виджета в один читаемый ответ: какая транзакция записала `DonateNEARtoEfiz`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. **Официальные ссылки** -- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) -- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) -- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -#### Часть 1: анатомия протокола +Это расследование специально продолжает более лёгкий RPC-сценарий про виджеты. Для этого живого якоря: -Базовая форма сопоставления здесь — это `token_diff` intent. Одна сторона объявляет, какие активы она готова отдать и получить, а вторая сторона объявляет противоположную разницу. В официальной документации verifier двухсторонний обмен USDC и USDT показан как один подписанный intent со смыслом «я отдам `-10` USDC и получу `+10` USDT» и второй intent, который описывает обратную сторону сделки. Такие подписанные intent можно собрать через Message Bus или через любой другой внешний канал координации и затем отправить вместе в `intents.near`. +- аккаунт: `efiz.near` +- виджет: `DonateNEARtoEfiz` +- блок записи в SocialDB: `92543301` +- receipt ID: `FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E` +- хеш исходной транзакции: `CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf` +- внешний блок транзакции: `92543300` -Эта концептуальная часть полезна, чтобы понять сам протокол, но подписанные примеры в официальной документации носят иллюстративный и привязанный ко времени характер. Для рабочего FastNear-сценария полезнее разбирать один реальный расчёт из mainnet, чем делать вид, будто пример из документации является готовой живой транзакцией. +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Мост от блока к receipt | Transactions API [`POST /v0/block`](/tx/block) | Берём блок `92543301` с `with_receipts: true`, а затем фильтруем его обратно до `efiz.near -> social.near` | Превращает last-write block виджета в один конкретный receipt и один конкретный хеш транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `DonateNEARtoEfiz` | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | + +**Что должен включать полезный ответ** -#### Часть 2: живая FastNear-трассировка +- высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции +- конкретный receipt ID и хеш исходной транзакции за этой записью виджета +- доказательство, что payload записи был `set` с `efiz.near/widget/DonateNEARtoEfiz` +- одно простое предложение вроде «`efiz.near` записал `DonateNEARtoEfiz` в транзакции `CUA61...`, и в payload действительно лежал исходник donation-виджета» -Для живой трассировки ниже используйте этот фиксированный якорь расчёта, зафиксированный **18 апреля 2026 года**: +### Shell-сценарий доказательства записи DonateNEARtoEfiz + +Используйте этот сценарий, когда хотите превратить один игривый блоковый якорь виджета в точную транзакцию, которая его записала. + +**Что вы делаете** + +- Стартуете с блока последней записи виджета. +- Переиспользуете эту высоту в FastNear block receipts, чтобы получить receipt и мост к транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы декодировать записанный исходник виджета. +- Завершаете сырым RPC-подтверждением, что виджет всё ещё существует сейчас. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=efiz.near +WIDGET_NAME=DonateNEARtoEfiz +WIDGET_BLOCK_HEIGHT=92543301 +``` + +1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. + +```bash +WIDGET_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$WIDGET_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/efiz-widget-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + widget_write_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/efiz-widget-block.json + +# Ожидаемый receipt ID: FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E +# Ожидаемый хеш транзакции: CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf +``` + +2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/efiz-widget-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].transaction.actions[0].FunctionCall + | { + method_name, + widget_source_head: ( + .args + | @base64d + | fromjson + | .data["efiz.near"].widget["DonateNEARtoEfiz"][""] + | split("\n")[0:12] + ) + } + ) +}' /tmp/efiz-widget-transaction.json +``` + +Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `CUA61...` вызвала `social.near set` и пронесла в `args` настоящий исходник виджета `DonateNEARtoEfiz`. + +3. Завершите каноническим подтверждением текущего состояния через сырой RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/efiz-widget-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + finality: "final", + current_widget_head: ( + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:5] + ) +}' /tmp/efiz-widget-rpc.json +``` + +Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. + +**Зачем нужен следующий шаг?** + +Last-write block виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. + +### Проследить один расчёт NEAR Intents и показать, что именно произошло + +Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». + +**Цель** + +- Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. + +**Официальные ссылки** + +- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) +- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) +- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) + +Для живой трассировки ниже используйте этот фиксированный якорь расчёта из mainnet, зафиксированный **18 апреля 2026 года**: - хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` - аккаунт `signer` и `receiver`: `intents.near` - высота включающего блока: `194573310` -Публичных FastNear-поверхностей уже достаточно, чтобы восстановить многое: +Быстрая полезная модель здесь простая: + +- `intents.near` выполняет входную точку расчёта +- последующие receipt расходятся по контрактам, которые реально переводят или выводят активы +- журналы событий показывают, какие действия расчёта случились, через имена вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` + +Для этого конкретного расчёта короткий человеческий ответ уже неплохой: + +- `intents.near` вызвал `execute_intents` +- downstream receipt ушли в `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` +- трасса выдала события `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` + +Если нужен протокольный фон, базовая форма сопоставления здесь — это двухсторонний `token_diff` intent: одна сторона подписывает желаемую разницу по активам, вторая сторона подписывает противоположную разницу, а затем совпавшая пара отправляется на расчёт. Но для рабочего расследования обычно понятнее начать с одной реальной транзакции расчёта и читать доказательства прямо с цепочки. + +```mermaid +flowchart LR + T["Одна mainnet-транзакция
4cfei8p4..."] --> I["intents.near
execute_intents"] + I --> R["Последующие receipt"] + R --> C["Подключаются другие контракты"] + R --> E["Появляются журналы событий"] + E --> TD["token_diff"] + E --> IE["intents_executed"] + E --> MT["mt_transfer / mt_withdraw"] +``` + +Публичных FastNear-поверхностей уже достаточно, чтобы ответить на практический вопрос: | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Якорь расчёта | Transactions API [`POST /v0/transactions`](/tx/transactions) | Начинаем с фиксированного хеша транзакции и получаем саму транзакцию плюс список последующих receipt | Даёт читаемый каркас расчёта без необходимости сразу декодировать сырые receipt | -| Контекст включающего блока | Transactions API [`POST /v0/block`](/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Помещает расчёт в контекст блока и показывает, какие receipt появились там | -| Канонический DAG по receipt | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` | Даёт протокольно-канонический DAG, `executor_id` и сырые логи событий | -| Классификация событий | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Извлекаем имена событий вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` из строк `EVENT_JSON` | Позволяет объяснять расчёт по семействам событий, а не по непрозрачным `receipt_id` | +| Каркас расчёта | Transactions API [`POST /v0/transactions`](/tx/transactions) | Начинаем с фиксированного хеша транзакции и печатаем основную транзакцию плюс первые последующие receipt | Даёт самый быстрый читаемый ответ на вопрос «во что этот расчёт пошёл дальше?» | +| Контекст блока | Transactions API [`POST /v0/block`](/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Показывает, в какой блок попал расчёт и какие receipt этой транзакции видны в блоке | +| Каноническое доказательство по receipt и событиям | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` плюс логи `EVENT_JSON` | Даёт протокольно-канонический DAG, `executor_id` и имена событий, которые объясняют, что реально сделал расчёт | **Что должен включать полезный ответ** -- как концептуальная двухсторонняя модель `token_diff` отображается на реальный расчёт через `execute_intents` -- какие последующие контракты и методы появились после `intents.near` +- какую входную точку расчёта вы увидели на `intents.near` +- какие downstream-контракты и методы появились сразу после неё - какие семейства событий выпустила трассировка -- какие высоты блоков сформировали основной каскад +- одно итоговое предложение простым языком о том, что произошло Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. -### Shell-сценарий для живой трассировки NEAR Intents +### Shell-сценарий расчёта NEAR Intents Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. **Что вы делаете** -- Получаете историю транзакции через Transactions API. +- Получаете читаемую историю расчёта через Transactions API. - Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. -- Подтверждаете канонический DAG по receipt и семейства логов событий через `EXPERIMENTAL_tx_status`. +- Подтверждаете канонический DAG по receipt и семейства событий через `EXPERIMENTAL_tx_status`. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -609,7 +1097,7 @@ INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 INTENTS_SIGNER_ID=intents.near ``` -1. Начните с самой транзакции расчёта. +1. Начните с самой транзакции расчёта и восстановите первый читаемый поток последующих receipt. ```bash INTENTS_BLOCK_HASH="$( @@ -643,6 +1131,8 @@ jq '{ }' /tmp/intents-transaction.json ``` +Этот первый шаг уже даёт сильный операторский ответ: `intents.near` выполнил транзакцию расчёта, а ранние последующие receipt показывают, какие контракты вошли в каскад сразу после этого. + 2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. ```bash @@ -712,24 +1202,27 @@ jq -r ' **Зачем нужен следующий шаг?** -`POST /v0/transactions` даёт читаемый каркас расчёта. `POST /v0/block` показывает, как этот расчёт расположен внутри включающего блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и сырые логи событий, а не только индексированное резюме. +`POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. -### Shell-сценарий для pivot по receipt +### Shell-сценарий: от страшного receipt ID к человеческой истории -Используйте этот сценарий, когда у вас уже есть один `receipt_id` и нужен самый короткий путь обратно к читаемой истории транзакции. +Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. **Что вы делаете** - Сначала разрешаете receipt. - Извлекаете `receipt.transaction_hash` через `jq`. - Переиспользуете этот хеш транзакции в `POST /v0/transactions`. +- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID=YOUR_RECEIPT_ID -# Пример receipt ID из недавнего mainnet-перевода: -# RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +``` + +1. Разрешите receipt и поймите, что за объект вы смотрите. +```bash TX_HASH="$( curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ @@ -743,26 +1236,141 @@ jq '{ receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, + receipt_type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, transaction_hash: .receipt.transaction_hash, tx_block_height: .receipt.tx_block_height } }' /tmp/receipt-lookup.json +``` +2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. + +```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - receipt_count: (.transactions[0].receipts | length) - }' + | tee /tmp/receipt-parent-transaction.json >/dev/null + +jq '{ + transaction: { + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transfer_deposit_yocto: ( + .transactions[0].transaction.actions[0].Transfer.deposit // null + ) + }, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/receipt-parent-transaction.json ``` +3. Сведите это к одному человеческому предложению. + +```bash +jq -r ' + .transactions[0] as $tx + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." +' /tmp/receipt-parent-transaction.json +``` + +Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. + +В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. + **Зачем нужен следующий шаг?** -`POST /v0/receipt` даёт точку перехода. `POST /v0/transactions` превращает эту точку в читаемую историю с контекстом по отправителю, получателю, блоку и связанным receipt-ам. И только после этого обычно стоит расширяться до окон по блоку или аккаунту. +`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. + +## Частые задачи + +### Найти одну транзакцию + +**Начните здесь** + +- [Transactions by Hash](/tx/transactions), когда идентификатор транзакции уже известен. + +**Следующая страница при необходимости** + +- [Receipt Lookup](/tx/receipt), если важной стала последующая квитанция. +- [Block](/tx/block), если нужен контекст блока. +- [Transaction Status](/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. + +**Остановитесь, когда** + +- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. +- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. + +### Исследовать квитанцию + +**Начните здесь** + +- [Receipt Lookup](/tx/receipt), когда ID квитанции — лучший якорь для расследования. + +**Следующая страница при необходимости** + +- [Transactions by Hash](/tx/transactions), чтобы связать квитанцию с исходной транзакцией. +- [Account History](/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. + +**Остановитесь, когда** + +- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. + +**Переходите дальше, когда** + +- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](/rpc). +- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. + +### Посмотреть недавнюю активность аккаунта + +**Начните здесь** + +- [Account History](/tx/account) для ленты активности по аккаунту. + +**Следующая страница при необходимости** + +- [Transactions by Hash](/tx/transactions) для конкретной транзакции из ленты. +- [Receipt Lookup](/tx/receipt), если фокус смещается на одну квитанцию. + +**Остановитесь, когда** + +- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. + +**Переходите дальше, когда** + +- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](/transfers). +- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](/rpc) или [FastNear API](/api). + +### Восстановить ограниченное окно по блокам + +**Начните здесь** + +- [Blocks](/tx/blocks) для ограниченного просмотра диапазона блоков. +- [Block](/tx/block), когда известен точный блок, который нужно исследовать. + +**Следующая страница при необходимости** + +- [Transactions by Hash](/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. +- [Receipt Lookup](/tx/receipt), если одной квитанции достаточно для следующего шага расследования. + +**Остановитесь, когда** + +- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. + +**Переходите дальше, когда** + +- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](/rpc). +- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](/neardata). ## Частые ошибки diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index 2a1441d..c08c099 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -1,87 +1,5 @@ **Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) -# Примеры FastNear API - -Используйте эту страницу, когда нужен читаемый ответ в форме сводки по аккаунту или активу и хочется пройти по документации FastNear API самым коротким путём. Начинайте с самого узкого эндпоинта, который уже может решить задачу, и расширяйтесь только тогда, когда понадобятся канонические детали RPC или история исполнения. - -## Когда начинать здесь - -- Пользователю нужны балансы, активы, стейкинг или общая сводка по аккаунту в формате кошелька. -- Нужно определить один или несколько аккаунтов по публичному ключу. -- Ответ должен выглядеть как прикладные данные, а не как сырой JSON-RPC. -- Нужен быстрый первый ответ до того, как станет понятно, требуется ли каноническое подтверждение через RPC. - -## Минимальные входные данные - -- сеть: mainnet или testnet -- основной идентификатор: `account_id` или публичный ключ -- нужен ли широкий обзор или одна конкретная категория активов -- понадобится ли затем точное каноническое подтверждение или история активности - -## Частые задачи - -### Получить сводку по аккаунту в формате кошелька - -**Начните здесь** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) для самого широкого снимка аккаунта. - -**Следующая страница при необходимости** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking) для более узкого продолжения. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?» - -**Остановитесь, когда** - -- Сводка уже отвечает на вопрос о портфеле или активах в нужной пользователю форме. - -**Расширяйте, когда** - -- Пользователь спрашивает о точной канонической семантике аккаунта или ключей доступа. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Определить аккаунты по публичному ключу - -**Начните здесь** - -- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. -- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. - -**Остановитесь, когда** - -- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. - -**Расширяйте, когда** - -- Пользователь спрашивает о точных правах ключа, nonce или каноническом состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). -- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Продолжить по одной категории активов, а не по всему аккаунту - -**Начните здесь** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft) для балансов FT-токенов. -- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) для владения NFT. -- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking) для позиций стейкинга. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если позже понадобится более широкий снимок аккаунта. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос смещается к тому, как активы менялись со временем. - -**Остановитесь, когда** - -- Эндпоинт по конкретной категории активов уже даёт готовый продуктовый ответ без дополнительной реконструкции. - -**Расширяйте, когда** - -- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - ## Готовые сценарии ### Определить аккаунт по публичному ключу, а затем получить сводку по нему @@ -122,7 +40,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Зачем нужен следующий шаг?** -Поиск по публичному ключу отвечает на вопрос об идентификации. Полный снимок аккаунта отвечает на следующий прикладной вопрос уже в продуктовой форме. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, расширяйтесь до [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. +Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. ### Проверить владение коллекцией, а затем выпустить производный NFT @@ -171,7 +89,7 @@ jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ }' /tmp/testnet-account-nfts.json ``` -2. Расширьтесь до канонического RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. +2. Перейдите к RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. ```bash NFT_TOKENS_ARGS_BASE64="$( @@ -252,7 +170,7 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Подтвердите новый токен тем же каноническим NFT view-методом. +5. Подтвердите новый токен тем же NFT view-методом. Если сразу после возврата mint-транзакции токен ещё не виден, не считайте это ошибкой сразу же: опросите view-метод несколько раз. @@ -292,7 +210,7 @@ jq '.' /tmp/derivative-token-verification.json **Зачем нужен следующий шаг?** -FastNear API быстрее всего отвечает на вопрос о допуске. Как только аккаунт проходит условие, RPC становится правильной поверхностью для точной проверки токенов и подтверждения результата, потому что напрямую открывает канонические NFT view-методы коллекции. +FastNear API — это быстрый ответ на вопрос о допуске. Как только аккаунт проходит условие, правильным следующим шагом становится RPC, потому что именно там видны точные `token_id` и собственные NFT view-методы коллекции. ### У меня обычный стейкинг или liquid staking? @@ -370,10 +288,74 @@ jq -n \ Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. +## Частые задачи + +### Что этот аккаунт вообще держит прямо сейчас? + +**Начните здесь** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» + +**Следующая страница при необходимости** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» + +**Остановитесь, когда** + +- Сводка уже отвечает на вопрос по активам в одной выдаче. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Определить аккаунты по публичному ключу + +**Начните здесь** + +- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. +- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. + +**Остановитесь, когда** + +- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). +- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? + +**Начните здесь** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. +- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft), когда вопрос конкретно про владение NFT. +- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» + +**Остановитесь, когда** + +- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. + +**Переходите дальше, когда** + +- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + ## Частые ошибки - Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. -- Использовать FastNear API, хотя пользователю прямо нужны канонические поля RPC или права доступа. +- Использовать FastNear API, хотя пользователю прямо нужны точные поля RPC или права доступа. - Оставаться на страницах сводок по аккаунту, когда вопрос уже стал вопросом об истории транзакций. - Забывать, что `?network=testnet` поддерживается только на совместимых страницах. diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index 2a1441d..c08c099 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -1,87 +1,5 @@ **Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) -# Примеры FastNear API - -Используйте эту страницу, когда нужен читаемый ответ в форме сводки по аккаунту или активу и хочется пройти по документации FastNear API самым коротким путём. Начинайте с самого узкого эндпоинта, который уже может решить задачу, и расширяйтесь только тогда, когда понадобятся канонические детали RPC или история исполнения. - -## Когда начинать здесь - -- Пользователю нужны балансы, активы, стейкинг или общая сводка по аккаунту в формате кошелька. -- Нужно определить один или несколько аккаунтов по публичному ключу. -- Ответ должен выглядеть как прикладные данные, а не как сырой JSON-RPC. -- Нужен быстрый первый ответ до того, как станет понятно, требуется ли каноническое подтверждение через RPC. - -## Минимальные входные данные - -- сеть: mainnet или testnet -- основной идентификатор: `account_id` или публичный ключ -- нужен ли широкий обзор или одна конкретная категория активов -- понадобится ли затем точное каноническое подтверждение или история активности - -## Частые задачи - -### Получить сводку по аккаунту в формате кошелька - -**Начните здесь** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) для самого широкого снимка аккаунта. - -**Следующая страница при необходимости** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking) для более узкого продолжения. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?» - -**Остановитесь, когда** - -- Сводка уже отвечает на вопрос о портфеле или активах в нужной пользователю форме. - -**Расширяйте, когда** - -- Пользователь спрашивает о точной канонической семантике аккаунта или ключей доступа. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Определить аккаунты по публичному ключу - -**Начните здесь** - -- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. -- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. - -**Остановитесь, когда** - -- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. - -**Расширяйте, когда** - -- Пользователь спрашивает о точных правах ключа, nonce или каноническом состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). -- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Продолжить по одной категории активов, а не по всему аккаунту - -**Начните здесь** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft) для балансов FT-токенов. -- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) для владения NFT. -- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking) для позиций стейкинга. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если позже понадобится более широкий снимок аккаунта. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос смещается к тому, как активы менялись со временем. - -**Остановитесь, когда** - -- Эндпоинт по конкретной категории активов уже даёт готовый продуктовый ответ без дополнительной реконструкции. - -**Расширяйте, когда** - -- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - ## Готовые сценарии ### Определить аккаунт по публичному ключу, а затем получить сводку по нему @@ -122,7 +40,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Зачем нужен следующий шаг?** -Поиск по публичному ключу отвечает на вопрос об идентификации. Полный снимок аккаунта отвечает на следующий прикладной вопрос уже в продуктовой форме. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, расширяйтесь до [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. +Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. ### Проверить владение коллекцией, а затем выпустить производный NFT @@ -171,7 +89,7 @@ jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ }' /tmp/testnet-account-nfts.json ``` -2. Расширьтесь до канонического RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. +2. Перейдите к RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. ```bash NFT_TOKENS_ARGS_BASE64="$( @@ -252,7 +170,7 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Подтвердите новый токен тем же каноническим NFT view-методом. +5. Подтвердите новый токен тем же NFT view-методом. Если сразу после возврата mint-транзакции токен ещё не виден, не считайте это ошибкой сразу же: опросите view-метод несколько раз. @@ -292,7 +210,7 @@ jq '.' /tmp/derivative-token-verification.json **Зачем нужен следующий шаг?** -FastNear API быстрее всего отвечает на вопрос о допуске. Как только аккаунт проходит условие, RPC становится правильной поверхностью для точной проверки токенов и подтверждения результата, потому что напрямую открывает канонические NFT view-методы коллекции. +FastNear API — это быстрый ответ на вопрос о допуске. Как только аккаунт проходит условие, правильным следующим шагом становится RPC, потому что именно там видны точные `token_id` и собственные NFT view-методы коллекции. ### У меня обычный стейкинг или liquid staking? @@ -370,10 +288,74 @@ jq -n \ Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. +## Частые задачи + +### Что этот аккаунт вообще держит прямо сейчас? + +**Начните здесь** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» + +**Следующая страница при необходимости** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» + +**Остановитесь, когда** + +- Сводка уже отвечает на вопрос по активам в одной выдаче. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Определить аккаунты по публичному ключу + +**Начните здесь** + +- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. +- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. + +**Остановитесь, когда** + +- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). +- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? + +**Начните здесь** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. +- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft), когда вопрос конкретно про владение NFT. +- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» + +**Остановитесь, когда** + +- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. + +**Переходите дальше, когда** + +- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + ## Частые ошибки - Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. -- Использовать FastNear API, хотя пользователю прямо нужны канонические поля RPC или права доступа. +- Использовать FastNear API, хотя пользователю прямо нужны точные поля RPC или права доступа. - Оставаться на страницах сводок по аккаунту, когда вопрос уже стал вопросом об истории транзакций. - Забывать, что `?network=testnet` поддерживается только на совместимых страницах. diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index 9a7427c..5b7ed02 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -1,22 +1,85 @@ **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) -# Примеры KV FastData API +## Готовое расследование + +### Проверить один ключ контракта, а затем пройти по его истории + +Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и финальную проверку через `view_state`. + +**Цель** + +- Объяснить, как этот ключ выглядит в индексе, как он менялся и совпадает ли с этим `view_state` прямо сейчас. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | +| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | +| Более широкий паттерн записей | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Смотрим аккаунт или предшественника, если один ключ — только часть более широкой картины | Помогает понять, менялся ли ключ сам по себе или как часть большего набора записей | +| Точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | -Используйте эту страницу, когда вопрос касается индексированного хранилища контракта и уже есть точная область поиска. Главное решение на этой поверхности — выбрать самую узкую полезную область: точный ключ, аккаунт, `predecessor_id` или набор известных ключей. Оставайтесь внутри KV FastData, пока речь идёт об индексированных данных key-value, и переходите к RPC только тогда, когда нужно каноническое состояние в цепочке. +**Что должен включать полезный ответ** + +- какой именно ключ и какая область контракта были исследованы +- как выглядит последнее индексированное значение и какие изменения видны в истории +- совпал ли `view_state` с текущим индексированным значением + +### Shell-сценарий + +Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» + +**Что вы делаете** + +- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. +- Извлекаете точный `key` через `jq`. +- Переиспользуете этот ключ в `POST /v0/history`, чтобы получить историю записей по тому же ключу. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +EXACT_KEY="$( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | tee /tmp/kv-latest.json \ + | jq -r '.entries[0].key' +)" -## Когда начинать здесь +jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' /tmp/kv-latest.json -- Нужны индексированные данные хранилища контракта, а не широкие сводки по аккаунтам или активам. -- Уже известен контракт, точный ключ, `predecessor_id` или область по аккаунту. -- Нужны последние индексированные записи или история изменений во времени. -- Нужен быстрый ответ по хранилищу до решения о дальнейшей проверке через RPC. +curl -s "$KV_BASE_URL/v0/history" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ + | jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + value + } + ] + }' +``` -## Минимальные входы +**Зачем нужен следующий шаг?** -- сеть -- ID контракта плюс одно из: точный ключ, область аккаунта, область по `predecessor_id` или известный набор ключей -- нужно ли последнее индексированное состояние или история изменений -- может ли позже понадобиться каноническая проверка +Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). ## Частые задачи @@ -34,7 +97,7 @@ - Последняя индексированная запись уже отвечает на вопрос о хранилище. -**Расширяйте, когда** +**Переходите дальше, когда** - Пользователю нужно точное текущее состояние в цепочке, а не индексированное хранилище. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). @@ -53,9 +116,9 @@ - Уже можно объяснить, как ключ менялся со временем. -**Расширяйте, когда** +**Переходите дальше, когда** -- Пользователь спрашивает, совпадает ли последнее индексированное значение с каноническим состоянием цепочки прямо сейчас. +- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. ### Проследить записи от одного `predecessor_id` @@ -72,9 +135,9 @@ - Уже можно ответить, что именно этот предшественник изменил и где. -**Расширяйте, когда** +**Переходите дальше, когда** -- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее каноническое состояние. +- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. ### Пакетно проверить несколько известных ключей @@ -90,90 +153,9 @@ - Пакетный ответ уже показывает, какие ключи действительно важны. -**Расширяйте, когда** - -- Пользователь хочет более широкий анализ контракта вместо фиксированного набора ключей. - -## Готовое расследование - -### Начать с одного индексированного ключа, затем подтвердить историю и каноническое состояние - -Используйте это расследование, когда один ключ контракта выглядит подозрительно и нужно связать его последнее индексированное значение, историю изменений и каноническую проверку через `view_state` в одну ясную историю. - -**Цель** - -- Объяснить, как ключ контракта выглядит сейчас, как он пришёл к этому состоянию в индексированной истории и совпадает ли с этим каноническое состояние RPC. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | -| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | -| Расширение области | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Расширяемся до области аккаунта или предшественника, если один ключ — только часть более широкой картины изменений | Помогает понять, менялся ли ключ изолированно или как часть более широкого набора записей | -| Каноническое подтверждение | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированный паттерн уже понятен | Разделяет индексированную историю хранилища и точное текущее состояние в цепочке | - -**Что должен включать полезный ответ** - -- какой именно ключ и какая область контракта были исследованы -- как выглядит последнее индексированное значение и какие изменения видны в истории -- совпал ли `view_state` с текущим индексированным значением - -### Shell-сценарий - -Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «какая у этого ключа более широкая индексированная история?» - -**Что вы делаете** - -- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. -- Извлекаете точный `key` через `jq`. -- Переиспользуете этот ключ в `POST /v0/history`, чтобы расшириться до истории. - -```bash -KV_BASE_URL=https://kv.main.fastnear.com -CURRENT_ACCOUNT_ID=social.near -PREDECESSOR_ID=james.near -KEY='graph/follow/sleet.near' - -ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" - -EXACT_KEY="$( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | tee /tmp/kv-latest.json \ - | jq -r '.entries[0].key' -)" - -jq '{ - latest: ( - .entries[0] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ) -}' /tmp/kv-latest.json - -curl -s "$KV_BASE_URL/v0/history" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ - | jq '{ - page_token, - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - value - } - ] - }' -``` - -**Зачем нужен следующий шаг?** +**Переходите дальше, когда** -Поиск последней записи даёт максимально узкий ответ. Повторное использование точного `key` в `POST /v0/history` показывает, не является ли этот ключ частью более широкой индексированной картины. Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). +- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. ## Частые ошибки diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index 9a7427c..5b7ed02 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -1,22 +1,85 @@ **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) -# Примеры KV FastData API +## Готовое расследование + +### Проверить один ключ контракта, а затем пройти по его истории + +Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и финальную проверку через `view_state`. + +**Цель** + +- Объяснить, как этот ключ выглядит в индексе, как он менялся и совпадает ли с этим `view_state` прямо сейчас. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | +| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | +| Более широкий паттерн записей | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Смотрим аккаунт или предшественника, если один ключ — только часть более широкой картины | Помогает понять, менялся ли ключ сам по себе или как часть большего набора записей | +| Точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | -Используйте эту страницу, когда вопрос касается индексированного хранилища контракта и уже есть точная область поиска. Главное решение на этой поверхности — выбрать самую узкую полезную область: точный ключ, аккаунт, `predecessor_id` или набор известных ключей. Оставайтесь внутри KV FastData, пока речь идёт об индексированных данных key-value, и переходите к RPC только тогда, когда нужно каноническое состояние в цепочке. +**Что должен включать полезный ответ** + +- какой именно ключ и какая область контракта были исследованы +- как выглядит последнее индексированное значение и какие изменения видны в истории +- совпал ли `view_state` с текущим индексированным значением + +### Shell-сценарий + +Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» + +**Что вы делаете** + +- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. +- Извлекаете точный `key` через `jq`. +- Переиспользуете этот ключ в `POST /v0/history`, чтобы получить историю записей по тому же ключу. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +EXACT_KEY="$( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | tee /tmp/kv-latest.json \ + | jq -r '.entries[0].key' +)" -## Когда начинать здесь +jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' /tmp/kv-latest.json -- Нужны индексированные данные хранилища контракта, а не широкие сводки по аккаунтам или активам. -- Уже известен контракт, точный ключ, `predecessor_id` или область по аккаунту. -- Нужны последние индексированные записи или история изменений во времени. -- Нужен быстрый ответ по хранилищу до решения о дальнейшей проверке через RPC. +curl -s "$KV_BASE_URL/v0/history" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ + | jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + value + } + ] + }' +``` -## Минимальные входы +**Зачем нужен следующий шаг?** -- сеть -- ID контракта плюс одно из: точный ключ, область аккаунта, область по `predecessor_id` или известный набор ключей -- нужно ли последнее индексированное состояние или история изменений -- может ли позже понадобиться каноническая проверка +Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). ## Частые задачи @@ -34,7 +97,7 @@ - Последняя индексированная запись уже отвечает на вопрос о хранилище. -**Расширяйте, когда** +**Переходите дальше, когда** - Пользователю нужно точное текущее состояние в цепочке, а не индексированное хранилище. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). @@ -53,9 +116,9 @@ - Уже можно объяснить, как ключ менялся со временем. -**Расширяйте, когда** +**Переходите дальше, когда** -- Пользователь спрашивает, совпадает ли последнее индексированное значение с каноническим состоянием цепочки прямо сейчас. +- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. ### Проследить записи от одного `predecessor_id` @@ -72,9 +135,9 @@ - Уже можно ответить, что именно этот предшественник изменил и где. -**Расширяйте, когда** +**Переходите дальше, когда** -- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее каноническое состояние. +- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. ### Пакетно проверить несколько известных ключей @@ -90,90 +153,9 @@ - Пакетный ответ уже показывает, какие ключи действительно важны. -**Расширяйте, когда** - -- Пользователь хочет более широкий анализ контракта вместо фиксированного набора ключей. - -## Готовое расследование - -### Начать с одного индексированного ключа, затем подтвердить историю и каноническое состояние - -Используйте это расследование, когда один ключ контракта выглядит подозрительно и нужно связать его последнее индексированное значение, историю изменений и каноническую проверку через `view_state` в одну ясную историю. - -**Цель** - -- Объяснить, как ключ контракта выглядит сейчас, как он пришёл к этому состоянию в индексированной истории и совпадает ли с этим каноническое состояние RPC. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | -| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | -| Расширение области | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Расширяемся до области аккаунта или предшественника, если один ключ — только часть более широкой картины изменений | Помогает понять, менялся ли ключ изолированно или как часть более широкого набора записей | -| Каноническое подтверждение | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированный паттерн уже понятен | Разделяет индексированную историю хранилища и точное текущее состояние в цепочке | - -**Что должен включать полезный ответ** - -- какой именно ключ и какая область контракта были исследованы -- как выглядит последнее индексированное значение и какие изменения видны в истории -- совпал ли `view_state` с текущим индексированным значением - -### Shell-сценарий - -Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «какая у этого ключа более широкая индексированная история?» - -**Что вы делаете** - -- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. -- Извлекаете точный `key` через `jq`. -- Переиспользуете этот ключ в `POST /v0/history`, чтобы расшириться до истории. - -```bash -KV_BASE_URL=https://kv.main.fastnear.com -CURRENT_ACCOUNT_ID=social.near -PREDECESSOR_ID=james.near -KEY='graph/follow/sleet.near' - -ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" - -EXACT_KEY="$( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | tee /tmp/kv-latest.json \ - | jq -r '.entries[0].key' -)" - -jq '{ - latest: ( - .entries[0] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ) -}' /tmp/kv-latest.json - -curl -s "$KV_BASE_URL/v0/history" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ - | jq '{ - page_token, - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - value - } - ] - }' -``` - -**Зачем нужен следующий шаг?** +**Переходите дальше, когда** -Поиск последней записи даёт максимально узкий ответ. Повторное использование точного `key` в `POST /v0/history` показывает, не является ли этот ключ частью более широкой индексированной картины. Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). +- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. ## Частые ошибки diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index b072383..a42d3e4 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -13,7 +13,7 @@ - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData API](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии использования KV FastData для точных ключей, истории ключей, анализа по предшественнику и перехода к каноническому RPC. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. - [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. @@ -29,16 +29,16 @@ ## Другие гайды -- [Примеры FastNear API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для сводок по аккаунтам, поиска по ключам и перехода к более узким представлениям активов. +- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии использования NEAR Data API для опроса блоков, маршрутов перенаправления и перехода к каноническому RPC. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для polling по оптимистичным и финализированным блокам и перехода к RPC, когда это нужно. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. -- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии использования Transfers API для узкой истории переводов, пагинации и перехода к более широкому расследованию. -- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые сценарии использования Transactions API для поиска транзакций, расследования квитанций, истории аккаунтов и анализа диапазонов блоков. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. +- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents. ## Снапшоты - [Снапшоты для валидаторов](https://docs.fastnear.com/ru/snapshots.md): Пути загрузки снапшотов FastNear для подъёма и восстановления узлов NEAR. -- [Примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples.md): Пошаговые операторские сценарии для выбора правильного пути снапшотов FastNear и выполнения типовых задач запуска и восстановления. +- [Примеры Snapshot](https://docs.fastnear.com/ru/snapshots/examples.md): Пошаговые операторские сценарии для выбора правильного пути восстановления через FastNear snapshots. - [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. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 70daca2..7da65fe 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -987,95 +987,13 @@ https://test.api.fastnear.com --- -## Примеры FastNear API +## Примеры API - HTML-маршрут: https://docs.fastnear.com/ru/api/examples - Markdown-маршрут: https://docs.fastnear.com/ru/api/examples.md **Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) -# Примеры FastNear API - -Используйте эту страницу, когда нужен читаемый ответ в форме сводки по аккаунту или активу и хочется пройти по документации FastNear API самым коротким путём. Начинайте с самого узкого эндпоинта, который уже может решить задачу, и расширяйтесь только тогда, когда понадобятся канонические детали RPC или история исполнения. - -## Когда начинать здесь - -- Пользователю нужны балансы, активы, стейкинг или общая сводка по аккаунту в формате кошелька. -- Нужно определить один или несколько аккаунтов по публичному ключу. -- Ответ должен выглядеть как прикладные данные, а не как сырой JSON-RPC. -- Нужен быстрый первый ответ до того, как станет понятно, требуется ли каноническое подтверждение через RPC. - -## Минимальные входные данные - -- сеть: mainnet или testnet -- основной идентификатор: `account_id` или публичный ключ -- нужен ли широкий обзор или одна конкретная категория активов -- понадобится ли затем точное каноническое подтверждение или история активности - -## Частые задачи - -### Получить сводку по аккаунту в формате кошелька - -**Начните здесь** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) для самого широкого снимка аккаунта. - -**Следующая страница при необходимости** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking) для более узкого продолжения. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?» - -**Остановитесь, когда** - -- Сводка уже отвечает на вопрос о портфеле или активах в нужной пользователю форме. - -**Расширяйте, когда** - -- Пользователь спрашивает о точной канонической семантике аккаунта или ключей доступа. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Определить аккаунты по публичному ключу - -**Начните здесь** - -- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. -- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. - -**Остановитесь, когда** - -- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. - -**Расширяйте, когда** - -- Пользователь спрашивает о точных правах ключа, nonce или каноническом состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). -- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Продолжить по одной категории активов, а не по всему аккаунту - -**Начните здесь** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft) для балансов FT-токенов. -- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) для владения NFT. -- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking) для позиций стейкинга. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если позже понадобится более широкий снимок аккаунта. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос смещается к тому, как активы менялись со временем. - -**Остановитесь, когда** - -- Эндпоинт по конкретной категории активов уже даёт готовый продуктовый ответ без дополнительной реконструкции. - -**Расширяйте, когда** - -- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - ## Готовые сценарии ### Определить аккаунт по публичному ключу, а затем получить сводку по нему @@ -1116,7 +1034,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Зачем нужен следующий шаг?** -Поиск по публичному ключу отвечает на вопрос об идентификации. Полный снимок аккаунта отвечает на следующий прикладной вопрос уже в продуктовой форме. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, расширяйтесь до [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. +Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. ### Проверить владение коллекцией, а затем выпустить производный NFT @@ -1165,7 +1083,7 @@ jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ }' /tmp/testnet-account-nfts.json ``` -2. Расширьтесь до канонического RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. +2. Перейдите к RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. ```bash NFT_TOKENS_ARGS_BASE64="$( @@ -1246,7 +1164,7 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Подтвердите новый токен тем же каноническим NFT view-методом. +5. Подтвердите новый токен тем же NFT view-методом. Если сразу после возврата mint-транзакции токен ещё не виден, не считайте это ошибкой сразу же: опросите view-метод несколько раз. @@ -1286,7 +1204,7 @@ jq '.' /tmp/derivative-token-verification.json **Зачем нужен следующий шаг?** -FastNear API быстрее всего отвечает на вопрос о допуске. Как только аккаунт проходит условие, RPC становится правильной поверхностью для точной проверки токенов и подтверждения результата, потому что напрямую открывает канонические NFT view-методы коллекции. +FastNear API — это быстрый ответ на вопрос о допуске. Как только аккаунт проходит условие, правильным следующим шагом становится RPC, потому что именно там видны точные `token_id` и собственные NFT view-методы коллекции. ### У меня обычный стейкинг или liquid staking? @@ -1364,10 +1282,74 @@ jq -n \ Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. +## Частые задачи + +### Что этот аккаунт вообще держит прямо сейчас? + +**Начните здесь** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» + +**Следующая страница при необходимости** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» + +**Остановитесь, когда** + +- Сводка уже отвечает на вопрос по активам в одной выдаче. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Определить аккаунты по публичному ключу + +**Начните здесь** + +- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. +- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. + +**Остановитесь, когда** + +- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). +- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? + +**Начните здесь** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. +- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft), когда вопрос конкретно про владение NFT. +- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» + +**Остановитесь, когда** + +- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. + +**Переходите дальше, когда** + +- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + ## Частые ошибки - Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. -- Использовать FastNear API, хотя пользователю прямо нужны канонические поля RPC или права доступа. +- Использовать FastNear API, хотя пользователю прямо нужны точные поля RPC или права доступа. - Оставаться на страницах сводок по аккаунту, когда вопрос уже стал вопросом об истории транзакций. - Забывать, что `?network=testnet` поддерживается только на совместимых страницах. @@ -1564,30 +1546,93 @@ https://kv.test.fastnear.com --- -## Примеры KV FastData API +## Примеры KV FastData - HTML-маршрут: https://docs.fastnear.com/ru/fastdata/kv/examples - Markdown-маршрут: https://docs.fastnear.com/ru/fastdata/kv/examples.md **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) -# Примеры KV FastData API +## Готовое расследование -Используйте эту страницу, когда вопрос касается индексированного хранилища контракта и уже есть точная область поиска. Главное решение на этой поверхности — выбрать самую узкую полезную область: точный ключ, аккаунт, `predecessor_id` или набор известных ключей. Оставайтесь внутри KV FastData, пока речь идёт об индексированных данных key-value, и переходите к RPC только тогда, когда нужно каноническое состояние в цепочке. +### Проверить один ключ контракта, а затем пройти по его истории -## Когда начинать здесь +Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и финальную проверку через `view_state`. -- Нужны индексированные данные хранилища контракта, а не широкие сводки по аккаунтам или активам. -- Уже известен контракт, точный ключ, `predecessor_id` или область по аккаунту. -- Нужны последние индексированные записи или история изменений во времени. -- Нужен быстрый ответ по хранилищу до решения о дальнейшей проверке через RPC. +**Цель** -## Минимальные входы +- Объяснить, как этот ключ выглядит в индексе, как он менялся и совпадает ли с этим `view_state` прямо сейчас. -- сеть -- ID контракта плюс одно из: точный ключ, область аккаунта, область по `predecessor_id` или известный набор ключей -- нужно ли последнее индексированное состояние или история изменений -- может ли позже понадобиться каноническая проверка +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | +| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | +| Более широкий паттерн записей | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Смотрим аккаунт или предшественника, если один ключ — только часть более широкой картины | Помогает понять, менялся ли ключ сам по себе или как часть большего набора записей | +| Точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | + +**Что должен включать полезный ответ** + +- какой именно ключ и какая область контракта были исследованы +- как выглядит последнее индексированное значение и какие изменения видны в истории +- совпал ли `view_state` с текущим индексированным значением + +### Shell-сценарий + +Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» + +**Что вы делаете** + +- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. +- Извлекаете точный `key` через `jq`. +- Переиспользуете этот ключ в `POST /v0/history`, чтобы получить историю записей по тому же ключу. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +EXACT_KEY="$( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | tee /tmp/kv-latest.json \ + | jq -r '.entries[0].key' +)" + +jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' /tmp/kv-latest.json + +curl -s "$KV_BASE_URL/v0/history" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ + | jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + value + } + ] + }' +``` + +**Зачем нужен следующий шаг?** + +Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). ## Частые задачи @@ -1605,7 +1650,7 @@ https://kv.test.fastnear.com - Последняя индексированная запись уже отвечает на вопрос о хранилище. -**Расширяйте, когда** +**Переходите дальше, когда** - Пользователю нужно точное текущее состояние в цепочке, а не индексированное хранилище. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). @@ -1624,9 +1669,9 @@ https://kv.test.fastnear.com - Уже можно объяснить, как ключ менялся со временем. -**Расширяйте, когда** +**Переходите дальше, когда** -- Пользователь спрашивает, совпадает ли последнее индексированное значение с каноническим состоянием цепочки прямо сейчас. +- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. ### Проследить записи от одного `predecessor_id` @@ -1643,9 +1688,9 @@ https://kv.test.fastnear.com - Уже можно ответить, что именно этот предшественник изменил и где. -**Расширяйте, когда** +**Переходите дальше, когда** -- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее каноническое состояние. +- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. ### Пакетно проверить несколько известных ключей @@ -1661,90 +1706,9 @@ https://kv.test.fastnear.com - Пакетный ответ уже показывает, какие ключи действительно важны. -**Расширяйте, когда** - -- Пользователь хочет более широкий анализ контракта вместо фиксированного набора ключей. - -## Готовое расследование - -### Начать с одного индексированного ключа, затем подтвердить историю и каноническое состояние - -Используйте это расследование, когда один ключ контракта выглядит подозрительно и нужно связать его последнее индексированное значение, историю изменений и каноническую проверку через `view_state` в одну ясную историю. - -**Цель** - -- Объяснить, как ключ контракта выглядит сейчас, как он пришёл к этому состоянию в индексированной истории и совпадает ли с этим каноническое состояние RPC. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | -| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | -| Расширение области | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Расширяемся до области аккаунта или предшественника, если один ключ — только часть более широкой картины изменений | Помогает понять, менялся ли ключ изолированно или как часть более широкого набора записей | -| Каноническое подтверждение | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированный паттерн уже понятен | Разделяет индексированную историю хранилища и точное текущее состояние в цепочке | - -**Что должен включать полезный ответ** - -- какой именно ключ и какая область контракта были исследованы -- как выглядит последнее индексированное значение и какие изменения видны в истории -- совпал ли `view_state` с текущим индексированным значением - -### Shell-сценарий - -Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «какая у этого ключа более широкая индексированная история?» - -**Что вы делаете** - -- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. -- Извлекаете точный `key` через `jq`. -- Переиспользуете этот ключ в `POST /v0/history`, чтобы расшириться до истории. - -```bash -KV_BASE_URL=https://kv.main.fastnear.com -CURRENT_ACCOUNT_ID=social.near -PREDECESSOR_ID=james.near -KEY='graph/follow/sleet.near' - -ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" - -EXACT_KEY="$( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | tee /tmp/kv-latest.json \ - | jq -r '.entries[0].key' -)" - -jq '{ - latest: ( - .entries[0] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ) -}' /tmp/kv-latest.json - -curl -s "$KV_BASE_URL/v0/history" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ - | jq '{ - page_token, - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - value - } - ] - }' -``` +**Переходите дальше, когда** -**Зачем нужен следующий шаг?** - -Поиск последней записи даёт максимально узкий ответ. Повторное использование точного `key` в `POST /v0/history` показывает, не является ли этот ключ частью более широкой индексированной картины. Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). +- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. ## Частые ошибки @@ -2089,35 +2053,86 @@ https://testnet.neardata.xyz --- -## Примеры NEAR Data API +## Примеры NEAR Data - HTML-маршрут: https://docs.fastnear.com/ru/neardata/examples - Markdown-маршрут: https://docs.fastnear.com/ru/neardata/examples.md **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) -# Примеры NEAR Data API +## Готовое расследование -Используйте эту страницу, когда свежесть важнее протокольной точности. NEAR Data API предназначен для опроса и чтения недавних данных по семейству блоков: начинайте с самого свежего или самого стабильного режима блоков, который подходит задаче, оставайтесь на этой поверхности для опроса, пока она отвечает на вопрос, и переходите к RPC только тогда, когда действительно нужна каноническая семантика блока или состояния. +### Поймать новый блок как можно раньше, а затем подтвердить его после finality -## Когда начинать здесь +Используйте это расследование, когда нужно заметить новый блок как можно раньше, но финальный ответ всё равно должен опираться на финализированный блок и иногда на точное чтение через RPC. -- Нужны недавние оптимистичные или финализированные данные по семейству блоков. -- Нужен клиент опроса, монитор или проверка свежести. -- Маршруты перенаправления приемлемы или полезны для клиентского сценария. -- Задача звучит как «что изменилось недавно?», а не как каноническое историческое подтверждение. +**Цель** -## Минимальные входы +- Быстро заметить недавний блок, а затем проверить то же самое, когда догонит finality. -- сеть -- режим свежести: optimistic или finalized -- есть ли конкретная высота или хеш, либо нужен самый свежий объект семейства блоков -- может ли клиент корректно следовать перенаправлениям -- потребуется ли позже проверка через RPC +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](https://docs.fastnear.com/ru/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | +| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](https://docs.fastnear.com/ru/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | +| Стабильное подтверждение | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) или [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | +| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | +| Точный разбор через RPC | RPC [Блок по ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Блок по высоте](https://docs.fastnear.com/ru/rpc/block/block-by-height) | Получаем точный блок, как только понятно, какой именно блок важен | Здесь уже имеет смысл RPC, если нужен тот самый блок-объект, который вернул бы сам протокол | -## Частые задачи +**Что должен включать полезный ответ** -### Отслеживать последний оптимистичный блок +- какое наблюдение по оптимистичному блоку впервые запустило расследование +- когда то же наблюдение стало финализированным +- изменил ли точный разбор через RPC интерпретацию + +### Shell-сценарий + +Используйте этот сценарий, когда вспомогательный маршрут сам выбирает для вас последний финализированный блок, но следующий шаг всё равно требует точной проверки через RPC. + +**Что вы делаете** + +- Смотрите redirect, который возвращает `GET /v0/last_block/final`. +- Загружаете итоговый документ блока. +- Извлекаете `block.header.height` через `jq`. +- Переиспользуете эту высоту в RPC `block` по высоте. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +RPC_URL=https://rpc.mainnet.fastnear.com + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +printf 'Redirect target: %s\n' "$FINAL_LOCATION" + +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | tee /tmp/neardata-final-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + +BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "block", + params: { + block_id: ($block_height | tonumber) + } + }')" \ + | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +``` + +**Зачем нужен следующий шаг?** + +Helper route — самый простой способ опрашивать сценарий «последний финализированный блок». Как только он сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. + +## Частые задачи + +### Отслеживать последний оптимистичный блок **Начните здесь** @@ -2131,7 +2146,7 @@ https://testnet.neardata.xyz - Уже можно сообщить о последнем оптимистичном блоке или зафиксировать отставание по свежести. -**Расширяйте, когда** +**Переходите дальше, когда** - Нужна finalized-стабильность вместо максимальной свежести. Переходите к [Финализированному блоку по высоте](https://docs.fastnear.com/ru/neardata/block) или [Перенаправлению на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final). @@ -2150,9 +2165,9 @@ https://testnet.neardata.xyz - Уже можно показывать движение финализированных блоков без перехода к более глубоким протокольным деталям. -**Расширяйте, когда** +**Переходите дальше, когда** -- Пользователю нужны точные канонические поля блока или семантика транзакций. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Пользователю нужны точные поля блока или семантика транзакций. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). ### Использовать маршруты перенаправления в клиенте опроса @@ -2162,17 +2177,17 @@ https://testnet.neardata.xyz **Следующая страница при необходимости** -- Следуйте по канонической цели, которую вернул маршрут перенаправления, и уже там читайте нужные данные по семейству блоков. +- Следуйте по URL блока, который вернул маршрут перенаправления, и уже там читайте нужные данные. **Остановитесь, когда** - Клиент надёжно проходит по маршруту перенаправления и получает нужный ресурс блока. -**Расширяйте, когда** +**Переходите дальше, когда** - Само перенаправление мешает клиенту. Тогда переходите на прямые маршруты блоков. -### Перейти от свежего опроса к каноническому разбору через RPC +### Перейти от опроса свежих блоков к точному RPC-разбору **Начните здесь** @@ -2184,86 +2199,16 @@ https://testnet.neardata.xyz **Остановитесь, когда** -- Уже можно чётко назвать недавний блок, который заслуживает канонической проверки. +- Уже можно чётко назвать недавний блок, который заслуживает проверки через RPC. -**Расширяйте, когда** +**Переходите дальше, когда** - Пользователь просит точную структуру данных в терминах протокола, а не просто свежее чтение. -## Готовое расследование - -### Начать с оптимистичного блока, затем подтвердить финализированную и каноническую картину - -Используйте это расследование, когда нужно раннее обнаружение по оптимистичному блоку, но финальный ответ всё равно должен опираться на стабильный финализированный вид и, при необходимости, на каноническое подтверждение через RPC. - -**Цель** - -- Быстро заметить недавнее изменение, а затем сузить его до финализированной и канонической истории блока без лишних запросов. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](https://docs.fastnear.com/ru/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | -| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](https://docs.fastnear.com/ru/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | -| Стабильное подтверждение | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) или [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | -| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | -| Каноническая проверка | RPC [Блок по ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Блок по высоте](https://docs.fastnear.com/ru/rpc/block/block-by-height) | Получаем точный канонический блок, как только понятно, какой именно блок важен | Переводит расследование от свежих данных к протокольно точному подтверждению только когда это действительно нужно | - -**Что должен включать полезный ответ** - -- какое наблюдение по оптимистичному блоку впервые запустило расследование -- когда то же наблюдение стало финализированным -- изменил ли канонический разбор через RPC интерпретацию - -### Shell-сценарий - -Используйте этот сценарий, когда вспомогательный маршрут должен сам выбирать для вас последний финализированный блок, но следующий шаг всё равно требует канонического подтверждения через RPC. - -**Что вы делаете** - -- Смотрите redirect, который возвращает `GET /v0/last_block/final`. -- Загружаете итоговый документ блока. -- Извлекаете `block.header.height` через `jq`. -- Переиспользуете эту высоту в RPC `block` по высоте. - -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -RPC_URL=https://rpc.mainnet.fastnear.com - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -printf 'Redirect target: %s\n' "$FINAL_LOCATION" - -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' - -BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "block", - params: { - block_id: ($block_height | tonumber) - } - }')" \ - | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' -``` - -**Зачем нужен следующий шаг?** - -Вспомогательный маршрут — самый простой путь опроса для сценария «последний финализированный блок». Как только он сообщил точную высоту блока, правильной следующей поверхностью становится RPC, если нужны канонические семантики этого блока без догадок о том, какой блок проверять. - ## Частые ошибки -- Воспринимать NEAR Data API как потоковый продукт, а не как поверхность для опроса. -- Начинать с канонического RPC, когда настоящая задача — мониторинг свежих блоков. +- Воспринимать NEAR Data как push-стрим, а не как API для опроса. +- Начинать с RPC, когда настоящая задача — мониторинг свежих блоков. - Забывать, что невалидный ключ может вернуть `401` ещё до перенаправления, а сами перенаправления подходят не каждому HTTP-клиенту. - Оставаться на NEAR Data после того, как пользователь уже попросил точные протокольные детали блока. @@ -2434,110 +2379,7 @@ https://archival-rpc.testnet.fastnear.com # Примеры RPC -Используйте эту страницу, когда уже понятно, что ответ должен опираться на каноническое поведение RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы выбрать правильную стартовую страницу, остановиться, как только RPC-ответ уже решает задачу, и расширять набор поверхностей только тогда, когда это действительно поможет. - -## Когда начинать здесь - -- Пользователь просит точное состояние в цепочке или поля в протокольной форме. -- Нужен прямой вызов view-метода контракта или сценарий отправки транзакции. -- Нужно исследовать блоки, чанки, валидаторов или метаданные протокола. -- Важна семантика узла, а не индексированное агрегированное представление. - -## Минимальные входные данные - -- сеть: mainnet или testnet -- основной идентификатор: `account_id`, публичный ключ, ID контракта плюс метод, хеш транзакции или высота/хеш блока -- нужно ли текущее состояние, историческое состояние или поведение отправки/финальности -- должен ли результат остаться каноническим или затем превратиться в более удобное для человека резюме - -## Частые задачи - -### Проверить точное состояние аккаунта или ключа доступа - -**Начните здесь** - -- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для канонических полей аккаунта. -- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. - -**Следующая страница при необходимости** - -- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после подтверждения канонического состояния нужна ещё и сводка в формате кошелька. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» - -**Остановитесь, когда** - -- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. - -**Расширяйте, когда** - -- Пользователю нужны балансы, NFT, стейкинг или другой продуктовый вид данных. -- Пользователя интересует не текущее каноническое состояние, а недавняя история активности. - -### Проверить блок или снимок состояния протокола - -**Начните здесь** - -- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height) для конкретного блока. -- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block) для текущей канонической головы цепочки. -- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info) для диагностики узла и сети. - -**Следующая страница при необходимости** - -- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если после поиска блока нужен контекст по изменениям состояния. -- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если нужна более читаемая картина исполнения в рамках блока или диапазона. - -**Остановитесь, когда** - -- Канонический ответ блока или протокола уже напрямую отвечает на вопрос. - -**Расширяйте, когда** - -- Нужны данные по свежим блокам в режиме опроса, а не один канонический снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). -- Нужна история по нескольким транзакциям, а не только ответ одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Выполнить view-вызов контракта - -**Начните здесь** - -- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function) для view-метода контракта. -- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда вопрос касается сырого хранилища контракта. -- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда на самом деле нужно понять, есть ли код и каков его хеш. - -**Следующая страница при необходимости** - -- [FastNear API](https://docs.fastnear.com/ru/api), если после сырого вызова пользователю нужен продуктовый ответ, например по активам или сводке аккаунта. -- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующая задача связана с индексированной историей по ключам и значениям, а не с точным RPC-чтением. - -**Остановитесь, когда** - -- Результат view-вызова уже отвечает на вопрос в канонической форме. - -**Расширяйте, когда** - -- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. -- Вопрос смещается от «что метод возвращает сейчас?» к «что менялось со временем?» - -### Отправить транзакцию и подтвердить результат - -**Начните здесь** - -- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужно каноническое поведение отправки с явной семантикой ожидания. -- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить канонический результат. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. - -**Остановитесь, когда** - -- У вас уже есть результат отправки и нужный канонический финальный статус. - -**Расширяйте, когда** - -- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. -- Нужен уже не единичный статус, а более широкий сценарий расследования. +Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. ## Готовые сценарии @@ -2547,7 +2389,7 @@ https://archival-rpc.testnet.fastnear.com **Что вы делаете** -- Через канонический RPC получаете полный список access key аккаунта. +- Через сам RPC получаете полный список access key аккаунта. - Сужаете этот список до function-call-ключей, привязанных к `social.near`. - Точно проверяете один выбранный ключ перед удалением. - Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. @@ -2781,7 +2623,7 @@ fi **Зачем нужен следующий шаг?** -Повторный вызов `view_access_key_list` замыкает сценарий на той же канонической поверхности, с которой вы начинали поиск. Если ключ исчез именно там, дополнительная индексированная сводка уже не нужна, чтобы подтвердить удаление. +Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. ### Проверить регистрацию FT storage и затем перевести токены @@ -2800,10 +2642,10 @@ fi **Что вы делаете** -- Через канонические RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. +- Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. - При необходимости получаете минимальный размер storage deposit. - Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. -- Подтверждаете баланс получателя тем же каноническим view-методом контракта. +- Подтверждаете баланс получателя тем же view-методом самого контракта. ```bash export NETWORK_ID=testnet @@ -3049,7 +2891,7 @@ curl -s "$RPC_URL" \ }' ``` -6. Подтвердите FT-баланс получателя каноническим view-методом контракта. +6. Подтвердите FT-баланс получателя тем же view-методом контракта. ```bash RECEIVER_BALANCE_ARGS_BASE64="$( @@ -3081,200 +2923,522 @@ curl -s "$RPC_URL" \ **Зачем нужен следующий шаг?** -Это канонический RPC-сценарий, потому что каждый шаг остаётся на точном состоянии контракта и точной семантике отправки транзакций: сначала вы доказываете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. - -## Частые ошибки - -- Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. -- Забывать переключаться с обычного RPC на архивный RPC для старого состояния. -- Воспринимать браузерную аутентификацию в интерфейсе документации как продовый паттерн для бэкенда. -- Продолжать пользоваться низкоуровневыми статусами транзакций, когда вопрос уже превратился в расследование или исторический разбор. +Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. -## Полезные связанные страницы +### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? -- [RPC Reference](https://docs.fastnear.com/ru/rpc) -- [Auth & Access](https://docs.fastnear.com/ru/auth) -- [FastNear API](https://docs.fastnear.com/ru/api) -- [Transactions API](https://docs.fastnear.com/ru/tx) -- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) -- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». ---- +Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: -## Снапшоты для валидаторов +- есть ли у целевого аккаунта storage на `social.near`? +- если есть, осталось ли там ещё место? +- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? -- HTML-маршрут: https://docs.fastnear.com/ru/snapshots -- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots.md +**Официальные ссылки** -**Источник:** [https://docs.fastnear.com/ru/snapshots](https://docs.fastnear.com/ru/snapshots) +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -# Снапшоты блокчейна +**Что вы делаете** -Этот раздел — для операторов узлов, которые поднимают или восстанавливают инфраструктуру NEAR. Это не поверхность для прикладных данных. Если задача — читать балансы, историю, блоки или состояние контракта, используйте документацию API и RPC, а не сценарии со снапшотами. +- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. +- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. +- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. +- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». -:::warning[Бесплатные снапшоты устарели] -Бесплатные снапшоты данных nearcore больше не выпускаются. +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mike.near +export SIGNER_ACCOUNT_ID=mike.near +``` -Infrastructure Committee и Near One рекомендуют Epoch Sync вместе с децентрализованной синхронизацией состояния. Актуальные рекомендации и режим подъёма смотрите на [NEAR Nodes](https://near-nodes.io). -::: +1. Сначала проверьте сам аккаунт signer. -## Используйте этот раздел, когда +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/social-publish-signer.json >/dev/null -- нужно поднять узел mainnet или testnet из данных снапшота -- идёт восстановление RPC- или архивного узла -- уже известно, что нужен путь загрузки снапшота FastNear +jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + signer_account_id: $signer_account_id, + amount: .result.amount, + locked: .result.locked, + storage_usage: .result.storage_usage +}' /tmp/social-publish-signer.json +``` -## Не используйте этот раздел, когда +Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. -- идёт запрос данных цепочки для приложения -- нужны свежие блоки, балансы, история или состояние контракта -- нужны общие рекомендации по продуктовому API, а не настройка оператором +2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. -В этих случаях используйте [Справочник 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). +```bash +SOCIAL_STORAGE_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" -## Перед загрузкой +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_account_storage", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-account-storage.json >/dev/null -- Сначала выберите сеть: mainnet или testnet. -- Решите, нужны обычные данные RPC или архивные. -- Убедитесь, что понимаете, где должны лежать горячие и холодные данные, прежде чем стартовать архивную загрузку. +jq --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + storage: (.result.result | implode | fromjson), + storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) +}' /tmp/social-account-storage.json +``` -- Установите `rclone` — скрипты загрузки от него зависят. +Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. -:::info[Установка `rclone`] -Установите `rclone` командой: +3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. ```bash -sudo -v ; curl https://rclone.org/install.sh | sudo bash +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + jq -n --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + signer_matches_target: true, + permission_granted: true, + reason: "owner write" + }' +else + WRITE_PERMISSION_ARGS_BASE64="$( + jq -nc \ + --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ + --arg key "$ACCOUNT_ID" '{ + predecessor_id: $predecessor_id, + key: $key + }' | base64 | tr -d '\n' + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + signer_matches_target: false, + permission_granted: (.result.result | implode | fromjson) + }' +fi ``` -::: -## Что покрывает каждый путь +4. Сведите проверку storage и разрешения в один читаемый итог. -- **Mainnet** включает оптимизированный `fast-rpc`, обычный RPC и архивные пути загрузки для горячих и холодных данных. -- **Testnet** включает RPC и архивные пути снапшотов для операторов testnet. +```bash +AVAILABLE_BYTES="$( + jq -r ' + .result.result + | if length == 0 then "0" + else (implode | fromjson | .available_bytes // 0 | tostring) + end + ' /tmp/social-account-storage.json +)" -Требования к узлам смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near), а исходники скриптов загрузки, которые используются в этих руководствах, — в [fastnear/static](https://github.com/fastnear/static). +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + PERMISSION_GRANTED=true +else + PERMISSION_GRANTED="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq -r '.result.result | implode | fromjson' + )" +fi -## Нужен сценарий? +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ + --argjson available_bytes "$AVAILABLE_BYTES" \ + --argjson permission_granted "$PERMISSION_GRANTED" '{ + account_id: $account_id, + signer_account_id: $signer_account_id, + storage_ready: ($available_bytes > 0), + permission_ready: $permission_granted, + ready_to_publish_now: (($available_bytes > 0) and $permission_granted) + }' +``` -Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. +Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. -## Выберите сеть +**Зачем нужен следующий шаг?** - - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) - - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) +Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. ---- +### Правда ли, что `efiz.near` опубликовал `DonateNEARtoEfiz`, и что этот виджет делает? -## Примеры снапшотов +Используйте этот сценарий, когда история более лёгкая и даже немного шуточная: «друг говорит, что `efiz.near` когда-то опубликовал виджет буквально с именем `DonateNEARtoEfiz`. Проверь это и покажи, что именно делает этот виджет, не выходя из RPC». -- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/examples -- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/examples.md +Этот пример намеренно сделан для удовольствия. Он не учит ничему особенно глубокому про async-исполнение. Он просто показывает, как точными чтениями SocialDB просматривать каталог BOS-автора и отвечать на один очень конкретный вопрос по живым on-chain-данным. -**Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) +**Официальные ссылки** -# Примеры снапшотов +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -Используйте эту страницу, когда вопрос звучит как «какой сценарий со снапшотом вообще запускать?», а не «какая команда существует?». Снапшоты — это операторские сценарии, а не API для данных: сначала выбирайте правильную цель узла, сеть и схему хранения, а затем запускайте самый узкий путь загрузки, который безопасно поднимет узел. +**Что вы делаете** -## Когда начинать здесь +- Спрашиваете у `social.near` каталог виджетов под `efiz.near`. +- Сохраняете высоты блоков, потому что они показывают, когда каждый ключ виджета последний раз переписывался. +- Подтверждаете, что `DonateNEARtoEfiz` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. +- Заканчиваете простым handoff: если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). -- Нужно поднять или восстановить узел. -- Нужно выбрать между обычным RPC, optimized `fast-rpc` и архивным сценарием с hot/cold-данными. -- Нужен самый короткий путь от задачи оператора к правильной последовательности команд. -- Уже понятно, что это инфраструктурная работа, а не чтение прикладных данных. +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=efiz.near +export WIDGET_NAME=DonateNEARtoEfiz +``` -## Минимальные входы +1. Получите каталог виджетов и сохраните высоты блоков последней записи. -- сеть: mainnet или testnet -- цель узла: обычный RPC, optimized `fast-rpc` или архивный режим -- схема хранения, особенно возможность разделить hot- и cold-архивные данные -- используется ли стандартный каталог данных nearcore или отдельные каталоги для hot/cold -- целевой путь к данным и примерные ограничения по загрузке +```bash +WIDGET_KEYS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "keys", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-keys.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].widget + | to_entries + | sort_by(.value * -1) + | map({ + widget_name: .key, + last_write_block: .value + }) + | .[0:20] +' /tmp/social-widget-keys.json +``` + +Это даёт компактный BOS-каталог. На момент написания у `efiz.near` был удивительно эклектичный набор виджетов, включая `ReversedFeed`, `HelloWorld`, `PotlockDonateAll` и `DonateNEARtoEfiz`, но настоящий источник истины здесь — именно живой запрос. + +2. Подтвердите, что `DonateNEARtoEfiz` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. + +```bash +WIDGET_GET_ARGS_BASE64="$( + jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-source.json >/dev/null + +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:25] + | join("\n") + ' /tmp/social-widget-source.json +``` + +Так вы печатаете первые 25 строк исходника и быстро понимаете, что это вообще за компонент. В живой версии на момент написания код инициализирует `reciever: "efiz.near"` и строит кнопку, которая вызывает `donate` на выбранную сумму. Название виджета честно предупреждает, что будет дальше. + +3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. + +```bash +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + ' /tmp/social-widget-keys.json \ + | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +``` + +На момент написания живая высота последней записи для `efiz.near/widget/DonateNEARtoEfiz` была `92543301`. + +Если следующий вопрос уже меняется на «какая транзакция записала именно эту версию виджета?», сохраните эту высоту блока и переходите к сценариям-доказательствам NEAR Social в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). + +**Зачем нужен следующий шаг?** + +Это хорошее напоминание, что RPC может быть не только судебной экспертизой. `keys` позволяет просматривать каталог BOS-автора как разработчику, а `get` даёт возможность заглянуть в точное тело виджета, которое реально лежит on-chain. Иногда ответ и правда такой: «да, ваш друг действительно опубликовал виджет с именем `DonateNEARtoEfiz`, и вот его код». ## Частые задачи -### Поднять optimized `fast-rpc`-узел в mainnet +### Проверить точное состояние аккаунта или ключа доступа **Начните здесь** -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), конкретно путь для optimized `fast-rpc`. +- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для точных полей аккаунта. +- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. **Следующая страница при необходимости** -- Вернитесь к обычному сценарию mainnet RPC, если узел не подходит для optimized profile. +- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» **Остановитесь, когда** -- Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. +- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. -**Расширяйте, когда** +**Переходите дальше, когда** -- На самом деле требуется архивное хранение, а не просто быстрый запуск. +- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. +- Пользователя интересует не текущее состояние, а недавняя история активности. -### Восстановить обычный RPC-узел в стандартный каталог nearcore +### Проверить один точный блок или снимок состояния протокола **Начните здесь** -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) или [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) в зависимости от сети и выберите обычный RPC-сценарий для нужного окружения. +- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. +- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» +- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. **Следующая страница при необходимости** -- Настраивайте `DATA_PATH`, `THREADS` или ограничения по пропускной способности только после того, как понятен стандартный сценарий. +- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. +- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» **Остановитесь, когда** -- Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. +- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. -**Расширяйте, когда** +**Переходите дальше, когда** -- Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. +- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). -### Правильно поднять архивные hot- и cold-данные mainnet +### Что этот контракт возвращает прямо сейчас? **Начните здесь** -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), раздел архивного режима. +- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. +- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. +- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» **Следующая страница при необходимости** -- Сначала получите последнюю высоту архивного снапшота, затем запускайте отдельные загрузки hot- и cold-данных с правильными путями. +- [FastNear API](https://docs.fastnear.com/ru/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. +- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» **Остановитесь, когда** -- План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. +- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. -**Расширяйте, когда** +**Переходите дальше, когда** -- Оператору нужны уже более широкие nearcore bootstrap-guides, а не только FastNear snapshots. +- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. +- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» -### Поднять архивные hot-данные в testnet +### Отправить транзакцию и подтвердить результат **Начните здесь** -- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet), раздел архивного режима. +- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. +- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. **Следующая страница при необходимости** -- Получите последнюю высоту архивного снапшота testnet перед шагом загрузки. +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. +- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» **Остановитесь, когда** -- Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. +- У вас уже есть результат отправки и нужный финальный статус. -**Расширяйте, когда** +**Переходите дальше, когда** -- Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. +- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. +- Нужен уже не единичный статус, а более широкий сценарий расследования. + +## Частые ошибки + +- Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. +- Забывать переключаться с обычного RPC на архивный RPC для старого состояния. +- Воспринимать браузерную аутентификацию в интерфейсе документации как продовый паттерн для бэкенда. +- Продолжать пользоваться низкоуровневыми статусами транзакций, когда вопрос уже превратился в расследование или исторический разбор. + +## Полезные связанные страницы + +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [Auth & Access](https://docs.fastnear.com/ru/auth) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) + +--- + +## Снапшоты для валидаторов + +- HTML-маршрут: https://docs.fastnear.com/ru/snapshots +- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots.md + +**Источник:** [https://docs.fastnear.com/ru/snapshots](https://docs.fastnear.com/ru/snapshots) + +# Снапшоты блокчейна + +Этот раздел — для операторов узлов, которые поднимают или восстанавливают инфраструктуру NEAR. Это не поверхность для прикладных данных. Если задача — читать балансы, историю, блоки или состояние контракта, используйте документацию API и RPC, а не сценарии со снапшотами. + +:::warning[Бесплатные снапшоты устарели] +Бесплатные снапшоты данных nearcore больше не выпускаются. + +Infrastructure Committee и Near One рекомендуют Epoch Sync вместе с децентрализованной синхронизацией состояния. Актуальные рекомендации и режим подъёма смотрите на [NEAR Nodes](https://near-nodes.io). +::: + +## Используйте этот раздел, когда + +- нужно поднять узел mainnet или testnet из данных снапшота +- идёт восстановление RPC- или архивного узла +- уже известно, что нужен путь загрузки снапшота FastNear + +## Не используйте этот раздел, когда + +- идёт запрос данных цепочки для приложения +- нужны свежие блоки, балансы, история или состояние контракта +- нужны общие рекомендации по продуктовому API, а не настройка оператором + +В этих случаях используйте [Справочник 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 или архивные. +- Убедитесь, что понимаете, где должны лежать горячие и холодные данные, прежде чем стартовать архивную загрузку. + +- Установите `rclone` — скрипты загрузки от него зависят. + +:::info[Установка `rclone`] +Установите `rclone` командой: + +```bash +sudo -v ; curl https://rclone.org/install.sh | sudo bash +``` +::: + +## Что покрывает каждый путь + +- **Mainnet** включает оптимизированный `fast-rpc`, обычный RPC и архивные пути загрузки для горячих и холодных данных. +- **Testnet** включает RPC и архивные пути снапшотов для операторов testnet. + +Требования к узлам смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near), а исходники скриптов загрузки, которые используются в этих руководствах, — в [fastnear/static](https://github.com/fastnear/static). + +## Нужен сценарий? + +Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. + +## Выберите сеть + + - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) + - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) + +--- + +## Примеры Snapshot + +- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/examples.md + +**Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) ## Готовое расследование ### Выбрать и выполнить правильный сценарий восстановления mainnet -Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно быстро понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. +Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. **Цель** -- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную безопасную последовательность команд. +- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. | Путь или команда | Как используем | Зачем используем | | --- | --- | --- | @@ -3289,7 +3453,7 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash - какой сценарий восстановления выбран и почему - какие ключевые env vars важны для выбранного пути - куда на диске должны попасть данные -- должен ли оператор оставаться в FastNear snapshot docs или переходить к более широкой документации nearcore +- должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap ### Shell-сценарий @@ -3319,6 +3483,80 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. +## Частые задачи + +### Поднять optimized `fast-rpc`-узел в mainnet + +**Начните здесь** + +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), конкретно путь для optimized `fast-rpc`. + +**Следующая страница при необходимости** + +- Вернитесь к обычному сценарию mainnet RPC, если узел не подходит для optimized profile. + +**Остановитесь, когда** + +- Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. + +**Переходите дальше, когда** + +- На самом деле требуется архивное хранение, а не просто быстрый запуск. + +### Восстановить обычный RPC-узел в стандартный каталог nearcore + +**Начните здесь** + +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) или [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) в зависимости от сети и выберите обычный RPC-сценарий для нужного окружения. + +**Следующая страница при необходимости** + +- Настраивайте `DATA_PATH`, `THREADS` или ограничения по пропускной способности только после того, как понятен стандартный сценарий. + +**Остановитесь, когда** + +- Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. + +**Переходите дальше, когда** + +- Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. + +### Правильно поднять архивные hot- и cold-данные mainnet + +**Начните здесь** + +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), раздел архивного режима. + +**Следующая страница при необходимости** + +- Сначала получите последнюю высоту архивного снапшота, затем запускайте отдельные загрузки hot- и cold-данных с правильными путями. + +**Остановитесь, когда** + +- План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. + +**Переходите дальше, когда** + +- Оператору нужны уже общие гайды nearcore по bootstrap, а не только FastNear snapshots. + +### Поднять архивные hot-данные в testnet + +**Начните здесь** + +- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet), раздел архивного режима. + +**Следующая страница при необходимости** + +- Получите последнюю высоту архивного снапшота testnet перед шагом загрузки. + +**Остановитесь, когда** + +- Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. + +**Переходите дальше, когда** + +- Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. + ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. @@ -3624,119 +3862,42 @@ https://transfers.main.fastnear.com ## Типовые стартовые страницы -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) — лента по аккаунту с фильтрами по направлению, активу, сумме и времени - -## Нужен сценарий? - -Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для простых пошаговых сценариев: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. - -## Устранение неполадок - -### Нужны полные метаданные транзакции - -Переходите на [Транзакции API](https://docs.fastnear.com/ru/tx), если одной истории переводов недостаточно. - -### `resume_token` перестал работать - -Считайте токен непрозрачным и переиспользуйте его только с тем же эндпоинтом и фильтрами, которые его вернули. - ---- - -## Примеры Transfers API - -- HTML-маршрут: https://docs.fastnear.com/ru/transfers/examples -- Markdown-маршрут: https://docs.fastnear.com/ru/transfers/examples.md - -**Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) - -# Примеры Transfers API - -Используйте эту страницу, когда вопрос касается именно движения активов и нужен самый короткий путь по документации истории переводов. Эта поверхность специально узкая: начинайте с самого точного фильтра перевода, который отвечает на вопрос, держите фокус на отправках и получениях и расширяйтесь только тогда, когда вопрос перестаёт быть «только про переводы». - -## Когда начинать здесь - -- Пользователя интересуют входящие или исходящие переводы NEAR или FT. -- Нужна лента кошелька, представление для аудита или ответ для поддержки, сфокусированный на движении активов. -- Аккаунт уже известен, и пока не требуется полная история исполнения. -- Для задачи достаточно mainnet-истории переводов. - -## Минимальные входы - -- `account_id` -- выбора сети здесь нет: сегодня эта поверхность доступна только для mainnet -- опциональные фильтры по направлению, активу, сумме или времени -- нужен ли только короткий набор событий или длинный обзор истории -- может ли позже понадобиться более широкий контекст транзакций - -## Частые задачи - -### Найти исходящие переводы одного аккаунта в узком окне времени - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. - -**Следующая страница при необходимости** - -- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. - -**Остановитесь, когда** - -- Уже можно ответить, кто что отправил, когда и в каком активе. - -**Расширяйте, когда** - -- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Построить ленту переводов с пагинацией через `resume_token` - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий. - -**Следующая страница при необходимости** - -- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. - -**Остановитесь, когда** - -- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) — лента по аккаунту с фильтрами по направлению, активу, сумме и времени -**Расширяйте, когда** +## Нужен сценарий? -- Пользователь просит метаданные транзакции сверх самих переводов. -- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). +Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для простых пошаговых сценариев: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. -### Перейти от истории переводов к полному расследованию транзакции +## Устранение неполадок -**Начните здесь** +### Нужны полные метаданные транзакции -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. +Переходите на [Транзакции API](https://docs.fastnear.com/ru/tx), если одной истории переводов недостаточно. -**Следующая страница при необходимости** +### `resume_token` перестал работать -- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. +Считайте токен непрозрачным и переиспользуйте его только с тем же эндпоинтом и фильтрами, которые его вернули. -**Остановитесь, когда** +--- -- Уже определено правильное событие перевода и следующий подходящий раздел для расследования. +## Примеры Transfers API -**Расширяйте, когда** +- HTML-маршрут: https://docs.fastnear.com/ru/transfers/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/transfers/examples.md -- Пользователю прямо нужны квитанции или каноническое подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. +**Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) ## Готовый сценарий -### Запросить узкое окно переводов, а затем перейти по receipt +### Найти один подозрительный перевод, а затем пройти по его receipt -Используйте этот сценарий, когда первый вопрос всё ещё касается только переводов, но вы уже понимаете, что потом может понадобиться одна точная точка перехода в контекст исполнения. +Используйте этот сценарий, когда история звучит так: «я вижу, что средства двигались, но хочу получить точную опорную точку исполнения для этого движения, не затягивая сразу всю историю аккаунта». **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Извлекаете первый `receipt_id` через `jq`. -- Переиспользуете этот receipt ID в Transactions API, чтобы перейти от движения актива к контексту исполнения. +- Выделяете один перевод, который действительно похож на нужное вам движение. +- Переиспользуете его `receipt_id` в Transactions API, чтобы перейти от движения актива к истории исполнения. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com @@ -3791,14 +3952,73 @@ curl -s "$TX_BASE_URL/v0/receipt" \ **Зачем нужен следующий шаг?** -Запрос переводов позволяет держать первый проход узким и удобным для пагинации. Переход по `receipt_id` даёт одну точную опорную точку в исполнении, не заставляя сразу расширяться до полной истории аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. +Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` даёт точную опорную точку в исполнении, не затягивая вас сразу в полную историю аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. + +## Частые задачи + +### Найти исходящие переводы одного аккаунта в узком окне времени + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. + +**Следующая страница при необходимости** + +- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. + +**Остановитесь, когда** + +- Уже можно ответить, кто что отправил, когда и в каком активе. + +**Переходите дальше, когда** + +- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Листать ленту переводов дальше и не потерять своё место + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. + +**Следующая страница при необходимости** + +- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. +- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. + +**Остановитесь, когда** + +- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. + +**Переходите дальше, когда** + +- Пользователь просит метаданные транзакции сверх самих переводов. +- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). + +### Перейти от истории переводов к полному расследованию транзакции + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. + +**Следующая страница при необходимости** + +- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. + +**Остановитесь, когда** + +- Уже определено правильное событие перевода и понятно, какой API открывать следующим. + +**Переходите дальше, когда** + +- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. - Считать историю переводов полной историей исполнения. - Переиспользовать `resume_token` с другими фильтрами. -- Начинать здесь с вопросов про testnet, хотя эта поверхность сегодня работает только в mainnet. +- Начинать здесь с вопросов про testnet, хотя этот API сегодня работает только в mainnet. ## Полезные связанные страницы @@ -3884,161 +4104,458 @@ https://tx.test.fastnear.com **Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) -# Примеры Transactions API +## Готовые расследования -Используйте эту страницу, когда вопрос звучит как «что произошло?» и нужен индексированный исторический слой до того, как вы перейдёте к каноническому подтверждению через RPC. Начинайте с того идентификатора, который уже есть на руках, объясняйте историю исполнения в читаемом порядке и расширяйте её только тогда, когда действительно понадобятся точные RPC-семантики. +### Проследить асинхронную promise-цепочку и доказать порядок callback-ов -## Когда начинать здесь +Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» -- У вас уже есть хеш транзакции, ID квитанции, ID аккаунта или ограниченный диапазон блоков. -- Пользователю нужен исторический контекст исполнения, разбор для поддержки или отладки либо читаемая временная шкала. -- Нужна индексированная история без ручной сборки из сырых RPC-вызовов. -- Первый ответ должен объяснить, что произошло, до углубления в протокольные детали. +**Цель** -## Минимальные входные данные +- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. -- сеть: mainnet или testnet -- основной идентификатор: хеш транзакции, ID квитанции, `account_id` или блок/диапазон блоков -- расследуете ли вы один объект или целое окно истории -- требуется ли точное каноническое подтверждение через RPC до завершения ответа +Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: -## Частые задачи +- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом +- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке +- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и +- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору -### Найти одну транзакцию +```mermaid +flowchart LR + Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] + H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] + R --> C["Async cross-contract callback-и"] + C --> B["Recorder state
beta"] + B --> A["Recorder state
alpha"] + A --> G["Recorder state
gamma"] + Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] + R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] + G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] +``` -**Начните здесь** +Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. +Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. -**Следующая страница при необходимости** +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | +| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | +| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | +| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | +| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | +| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | +| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | +| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. -- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. +**Что должен включать полезный ответ** -**Остановитесь, когда** +- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» +- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования +- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался +- в каких блоках стали видны изменения состояния +- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования -- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. +### Превратить один страшный receipt ID из логов в понятную человеческую историю -**Расширяйте, когда** +Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. -- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. -- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. +**Цель** -### Исследовать квитанцию +- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. -**Начните здесь** +Для этого зафиксированного примера «страшный receipt ID из логов» такой: -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. +- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` +- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- высота блока транзакции: `194263342` +- высота блока исполнения receipt: `194263343` -**Следующая страница при необходимости** +Человеческая история за этим receipt простая: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. -- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. +```mermaid +flowchart LR + L["Один страшный receipt ID
5GhZcpfK..."] --> R["Ищем receipt"] + R --> T["Восстанавливаем tx hash
AdgNifPY..."] + T --> S["Читаем действия транзакции"] + S --> H["Человеческая история:
mike.near отправил 5 NEAR в global-counter.mike.near"] +``` -**Остановитесь, когда** +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и печатаем аккаунты, блок исполнения, флаг успеха и связанный хеш транзакции | Даёт самый короткий путь от сырого receipt ID к пониманию, что вообще за объект перед вами | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | +| Каноническое продолжение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | -- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. +**Что должен включать полезный ответ** -**Расширяйте, когда** +- какие аккаунты создали и исполнили квитанцию +- к какой транзакции относится эта квитанция +- что транзакция на самом деле сделала +- была ли квитанция главным событием или только шагом в большом каскаде +- одно предложение простым языком, которое можно без правок вставить коллеге в чат -- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. +### Доказать, что одно неудачное действие сорвало весь пакет -### Посмотреть недавнюю активность аккаунта +Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? -**Начните здесь** +В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. -- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. +**Цель** -**Следующая страница при необходимости** +- На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. +**Официальные ссылки** -**Остановитесь, когда** +- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) +- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) + +Этот зафиксированный сбой был получен в **testnet 18 апреля 2026 года**: + +- хеш транзакции: `CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M` +- аккаунт signer: `temp.mike.testnet` +- целевой новый аккаунт: `rollback-mo4vmkig.temp.mike.testnet` +- высота включающего блока: `246365118` +- хеш включающего блока: `6f5zTKDqQRwrxMywzvxeRvYcCERJmAnatJaqUEtQYUNM` +- порядок действий: `CreateAccount -> Transfer -> AddKey -> FunctionCall` +- упавший метод: `definitely_missing_method` +- RPC-ошибка: `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet` + +```mermaid +flowchart LR + T["Одна подписанная транзакция"] --> A["CreateAccount"] + A --> B["Transfer 0.01 NEAR"] + B --> C["AddKey"] + C --> D["FunctionCall definitely_missing_method()"] + D --> E["Сбой: CodeDoesNotExist"] + E --> R["Весь пакет не закрепился"] + R --> N["Новый аккаунт не появился"] + R --> K["Новый ключ не закрепился"] + R --> F["У получателя нет профинансированного состояния"] +``` -- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Задуманный пакет | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированный хеш транзакции и печатаем упорядоченный список действий, получателя и метаданные включающего блока | Показывает, что именно signer пытался сделать, ещё до разговора о том, что закрепилось | +| Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | +| Доказательство по состоянию после исполнения | RPC [`query(view_account)`](https://docs.fastnear.com/ru/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | -**Расширяйте, когда** +Перед shell-сценарием важно отметить одну деталь: индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. -- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). -- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). +**Что должен включать полезный ответ** -### Восстановить ограниченное окно по блокам +- точный порядок действий, который отправил signer +- какой индекс действия упал и почему +- высоту и хеш включающего блока для этого батча +- доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality +- короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` -**Начните здесь** +### Shell-сценарий неудачной транзакции с пакетом действий -- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. -- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. +Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. -**Следующая страница при необходимости** +**Что вы делаете** -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. +- Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. +- Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. +- Через один RPC-запрос к состоянию после исполнения доказываете, что новый аккаунт так и не появился после finality. -**Остановитесь, когда** +```bash +TX_BASE_URL=https://tx.test.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M +SIGNER_ACCOUNT_ID=temp.mike.testnet +NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +``` -- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. +1. Получите транзакцию и распечатайте задуманный пакет действий. -**Расширяйте, когда** +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/failed-batch-transaction.json >/dev/null -- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height, + included_block_hash: .transactions[0].execution_outcome.block_hash + }, + batch: { + action_count: (.transactions[0].transaction.actions | length), + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + final_function_call_method_name: ( + .transactions[0].transaction.actions[3].FunctionCall.method_name + ) + }, + first_receipt_handoff: .transactions[0].transaction_outcome.outcome.status +}' /tmp/failed-batch-transaction.json -## Готовые расследования +# Ожидаемый порядок действий: +# 1. CreateAccount +# 2. Transfer +# 3. AddKey +# 4. FunctionCall +``` + +2. Запросите RPC transaction status и посмотрите точную верхнеуровневую ошибку. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/failed-batch-rpc-status.json >/dev/null + +jq '{ + final_execution_status: .result.final_execution_status, + failed_action_index: .result.status.Failure.ActionError.index, + failure: .result.status.Failure.ActionError.kind.FunctionCallError.CompilationError.CodeDoesNotExist +}' /tmp/failed-batch-rpc-status.json + +# Ожидаемый failed_action_index: 3 +# Ожидаемый failure account_id: rollback-mo4vmkig.temp.mike.testnet +``` + +3. Запросите предполагаемый новый аккаунт после finality и докажите, что его всё ещё нет. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/failed-batch-view-account.json >/dev/null -### Доказать порядок callback-ов в staged/release-сценарии +jq '{ + error: .error.cause.name, + message: .error.data, + requested_account_id: .error.cause.info.requested_account_id, + proof_block_height: .error.cause.info.block_height +}' /tmp/failed-batch-view-account.json + +# Ожидаемая ошибка: "UNKNOWN_ACCOUNT" +``` + +Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. + +**Зачем нужен следующий шаг?** + +Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. + +### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? -Используйте это расследование, когда сначала была стадия с асинхронной подготовкой работы, потом отдельный release, и нужно доказать не только успешность транзакций, но и конкретный порядок выполнения последующих обратных вызовов. +Используйте это расследование, когда один вызов контракта залогировал успех, изменил своё локальное состояние, и даже верхнеуровневый RPC `status` выглядит успешным, но приложение всё равно сломалось, потому что позже упал отдельный cross-contract receipt. + +Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. **Цель** -- Превратить два хеша транзакций в устойчивый артефакт для расследования, который включает граф квитанций, привязки к блокам и изменения состояния контракта. +- Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. + +**Официальные ссылки** + +- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) +- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) -В staged/release-сценариях именно stage-транзакция обычно остаётся главной опорной транзакцией расследования, потому что отложенные callback-и живут на её исходном дереве транзакции, а не на дереве release-транзакции. +Этот зафиксированный асинхронный сбой был получен в **testnet 18 апреля 2026 года**: + +- хеш транзакции: `AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz` +- аккаунт signer: `temp.mike.testnet` +- первый контракт-получатель: `seq-dr.mike.testnet` +- аккаунт detached-цели: `asyncfail-in2hwikn.temp.mike.testnet` +- блок включения транзакции: `246368568` +- успешная первая receipt: `6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS` в блоке `246368569` +- более поздняя упавшая receipt: `2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es` в блоке `246368570` +- первый метод: `kickoff_append` +- более поздний упавший метод: `append` +- верхнеуровневый RPC `status`: `SuccessValue` + +```mermaid +flowchart LR + T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] + R --> S["Роутер сохраняет локальное состояние
kicked += late-failure"] + R --> D["Detached cross-contract receipt
append(...)"] + D --> F["Более поздний сбой
CodeDoesNotExist"] + S -. "состояние из первой receipt всё равно закрепилось" .-> K["kicked() всё ещё содержит late-failure"] +``` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Захват трассы stage и release | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш stage-транзакции и хеш release-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому дереву транзакции | -| Проверка материализации stage | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод staging-контракта, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления ожидаемых отложенных шагов | Подтверждает, что callback-и действительно стали доступны до того, как release-транзакция попытается их разбудить | -| Обогащение транзакций | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id` и индексированный статус исполнения | Даёт каждой транзакции устойчивую привязку к блоку, чтобы дальнейший анализ не зависел от памяти или ручных заметок | -| Снимки состояния контракта recorder | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до release, а затем опрашиваем его после release до появления ожидаемых записей | Доказывает реальный порядок последующих эффектов в состоянии контракта, а не только в метаданных дерева квитанций | -| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | -| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | -| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | -| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | +| Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | +| Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | +| Текущее состояние контракта | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `seq-dr.mike.testnet.kicked()` | Показывает, что локальное изменение состояния из первой receipt закрепилось, хотя более поздняя detached-receipt упала | + +Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не откатил более раннее изменение состояния роутера. **Что должен включать полезный ответ** -- почему именно stage-транзакция, а не release-транзакция, обычно является главной опорной транзакцией расследования -- какой порядок callback-ов вы наблюдали -- в каких блоках стали видны изменения состояния -- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования +- что подписанная транзакция успешно передала управление в первую router-receipt +- что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` +- что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` +- что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился +- одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции + +### Shell-сценарий более позднего сбоя receipt + +Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». + +**Что вы делаете** + +- Читаете транзакцию и её таймлайн receipt из индексированного представления. +- Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. +- Читаете текущее состояние роутера, чтобы показать: локальный побочный эффект первой receipt закрепился. + +```bash +TX_BASE_URL=https://tx.test.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz +SIGNER_ACCOUNT_ID=temp.mike.testnet +ROUTER_ACCOUNT_ID=seq-dr.mike.testnet +FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS +FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es +``` + +1. Получите транзакцию и распечатайте таймлайн receipt по порядку блоков. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/later-receipt-failure-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + tx_handoff: .transactions[0].transaction_outcome.outcome.status + }, + receipts: [ + .transactions[0].receipts[] + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + block_height: .execution_outcome.block_height, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system_transfer"), + status: .execution_outcome.outcome.status + } + ] +}' /tmp/later-receipt-failure-transaction.json + +# На что смотреть: +# - первая receipt контракта на seq-dr.mike.testnet успешно прошла в блоке 246368569 +# - более поздняя receipt append(...) упала в блоке 246368570 +``` + +2. Запросите RPC transaction status и сравните верхнеуровневую историю с более поздней упавшей receipt. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/later-receipt-failure-rpc.json >/dev/null + +jq \ + --arg first_receipt_id "$FIRST_RECEIPT_ID" \ + --arg failed_receipt_id "$FAILED_RECEIPT_ID" '{ + top_level_status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status, + first_contract_receipt: ( + .result.receipts_outcome[] + | select(.id == $first_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + logs: .outcome.logs, + status: .outcome.status + } + ), + later_failed_receipt: ( + .result.receipts_outcome[] + | select(.id == $failed_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + status: .outcome.status + } + ) + }' /tmp/later-receipt-failure-rpc.json -### Начать с receipt ID и восстановить историю исполнения +# На что смотреть: +# - top_level_status всё ещё равен SuccessValue +# - первая receipt контракта залогировала dishonest_router:kickoff:late-failure +# - более поздняя receipt append(...) упала с CodeDoesNotExist +``` -Используйте это расследование, когда на руках есть только receipt ID из трассы, лога ошибки или дерева callback-ов и нужно вернуться к понятной человеку истории того, что произошло. +3. Прочитайте текущее состояние роутера и подтвердите, что локальный побочный эффект первой receipt закрепился. -**Цель** +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ROUTER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "kicked", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/later-receipt-failure-kicked.json >/dev/null -- Перейти от одной квитанции к исходной транзакции, а затем расширить расследование ровно настолько, чтобы объяснить окружающее исполнение и эффекты в состоянии. +jq '{ + kicked: (.result.result | implode | fromjson), + contains_late_failure: ((.result.result | implode | fromjson) | index("late-failure") != null) +}' /tmp/later-receipt-failure-kicked.json +``` -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и определяем её содержимое, статус и связанный контекст транзакции | ID квитанции часто появляется в трассах и логах раньше, чем у человека складывается цельная история транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | После перехода из поиска квитанции забираем исходную транзакцию по хешу | Превращает одну квитанцию в читаемую историю исполнения с контекстом по получателю, блоку и статусу | -| Каноническое подтверждение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем результат на уровне протокола, когда индексированного вида недостаточно или нужны точные RPC-семантики | Полезно, когда важно различить индексированную интерпретацию и точное поведение RPC | -| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем содержащий блок и при необходимости расширяемся на соседние каскадные блоки, если исполнение растянулось по нескольким высотам | Помещает квитанцию во временную шкалу по блокам, которую проще объяснить | -| Окно активности аккаунта | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Забираем недавнюю активность аккаунтов, которых коснулась квитанция | Помогает связать квитанцию с окружающей историей по аккаунтам | -| Повторное чтение состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод с закреплённым `block_id`, если квитанция изменила видимое состояние контракта | Позволяет доказать, что квитанция не только существовала в метаданных, но и изменила устойчивое состояние контракта | +Этот последний read и есть практическое доказательство того, что локальное изменение из первой receipt закрепилось. Более поздняя упавшая receipt не откатила более ранний `kicked.push(...)` внутри роутера. -**Что должен включать полезный ответ** +**Зачем нужен следующий шаг?** -- какую исходную транзакцию вы восстановили из квитанции -- была ли квитанция главным событием или только одним шагом в большом каскаде -- какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить -- был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым +Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. ### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB @@ -4422,61 +4939,252 @@ jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" ' NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. -### Понять двухстороннее сопоставление `token_diff`, а затем проследить живой расчёт NEAR Intents +### Какая транзакция записала `DonateNEARtoEfiz`? -Используйте это расследование, когда история звучит так: «покажи, что именно NEAR Intents делает под капотом, но привяжи разбор к публичным данным, которые можно проверить самостоятельно». +Используйте это расследование, когда история уже более лёгкая и даже немного шуточная: «на странице RPC-примеров я уже увидел, что `efiz.near/widget/DonateNEARtoEfiz` существует и что его last-write block — `92543301`. Какая именно транзакция записала этот виджет?» + +Это весёлый пример, но рецепт доказательства здесь вполне серьёзный и знакомый по другим SocialDB-расследованиям: + +- стартуем с одного блока, к которому привязан SocialDB-ключ +- превращаем этот блок в конкретный `efiz.near -> social.near` receipt +- восстанавливаем исходную транзакцию +- декодируем payload `set` и доказываем, что в нём действительно лежит исходник виджета **Цель** -- Сначала объяснить модель сопоставления, а затем превратить один реальный расчёт через `intents.near` в читаемую историю исполнения на базе Transactions API и канонического RPC. +- Превратить last-write block виджета в один читаемый ответ: какая транзакция записала `DonateNEARtoEfiz`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. **Официальные ссылки** -- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) -- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) -- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +Это расследование специально продолжает более лёгкий RPC-сценарий про виджеты. Для этого живого якоря: + +- аккаунт: `efiz.near` +- виджет: `DonateNEARtoEfiz` +- блок записи в SocialDB: `92543301` +- receipt ID: `FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E` +- хеш исходной транзакции: `CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf` +- внешний блок транзакции: `92543300` + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `92543301` с `with_receipts: true`, а затем фильтруем его обратно до `efiz.near -> social.near` | Превращает last-write block виджета в один конкретный receipt и один конкретный хеш транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `DonateNEARtoEfiz` | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | + +**Что должен включать полезный ответ** + +- высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции +- конкретный receipt ID и хеш исходной транзакции за этой записью виджета +- доказательство, что payload записи был `set` с `efiz.near/widget/DonateNEARtoEfiz` +- одно простое предложение вроде «`efiz.near` записал `DonateNEARtoEfiz` в транзакции `CUA61...`, и в payload действительно лежал исходник donation-виджета» + +### Shell-сценарий доказательства записи DonateNEARtoEfiz + +Используйте этот сценарий, когда хотите превратить один игривый блоковый якорь виджета в точную транзакцию, которая его записала. + +**Что вы делаете** + +- Стартуете с блока последней записи виджета. +- Переиспользуете эту высоту в FastNear block receipts, чтобы получить receipt и мост к транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы декодировать записанный исходник виджета. +- Завершаете сырым RPC-подтверждением, что виджет всё ещё существует сейчас. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=efiz.near +WIDGET_NAME=DonateNEARtoEfiz +WIDGET_BLOCK_HEIGHT=92543301 +``` + +1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. + +```bash +WIDGET_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$WIDGET_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/efiz-widget-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + widget_write_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/efiz-widget-block.json + +# Ожидаемый receipt ID: FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E +# Ожидаемый хеш транзакции: CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf +``` + +2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/efiz-widget-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].transaction.actions[0].FunctionCall + | { + method_name, + widget_source_head: ( + .args + | @base64d + | fromjson + | .data["efiz.near"].widget["DonateNEARtoEfiz"][""] + | split("\n")[0:12] + ) + } + ) +}' /tmp/efiz-widget-transaction.json +``` + +Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `CUA61...` вызвала `social.near set` и пронесла в `args` настоящий исходник виджета `DonateNEARtoEfiz`. + +3. Завершите каноническим подтверждением текущего состояния через сырой RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/efiz-widget-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + finality: "final", + current_widget_head: ( + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:5] + ) +}' /tmp/efiz-widget-rpc.json +``` + +Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. + +**Зачем нужен следующий шаг?** + +Last-write block виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. + +### Проследить один расчёт NEAR Intents и показать, что именно произошло + +Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». -#### Часть 1: анатомия протокола +**Цель** -Базовая форма сопоставления здесь — это `token_diff` intent. Одна сторона объявляет, какие активы она готова отдать и получить, а вторая сторона объявляет противоположную разницу. В официальной документации verifier двухсторонний обмен USDC и USDT показан как один подписанный intent со смыслом «я отдам `-10` USDC и получу `+10` USDT» и второй intent, который описывает обратную сторону сделки. Такие подписанные intent можно собрать через Message Bus или через любой другой внешний канал координации и затем отправить вместе в `intents.near`. +- Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. -Эта концептуальная часть полезна, чтобы понять сам протокол, но подписанные примеры в официальной документации носят иллюстративный и привязанный ко времени характер. Для рабочего FastNear-сценария полезнее разбирать один реальный расчёт из mainnet, чем делать вид, будто пример из документации является готовой живой транзакцией. +**Официальные ссылки** -#### Часть 2: живая FastNear-трассировка +- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) +- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) +- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) -Для живой трассировки ниже используйте этот фиксированный якорь расчёта, зафиксированный **18 апреля 2026 года**: +Для живой трассировки ниже используйте этот фиксированный якорь расчёта из mainnet, зафиксированный **18 апреля 2026 года**: - хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` - аккаунт `signer` и `receiver`: `intents.near` - высота включающего блока: `194573310` -Публичных FastNear-поверхностей уже достаточно, чтобы восстановить многое: +Быстрая полезная модель здесь простая: + +- `intents.near` выполняет входную точку расчёта +- последующие receipt расходятся по контрактам, которые реально переводят или выводят активы +- журналы событий показывают, какие действия расчёта случились, через имена вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` + +Для этого конкретного расчёта короткий человеческий ответ уже неплохой: + +- `intents.near` вызвал `execute_intents` +- downstream receipt ушли в `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` +- трасса выдала события `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` + +Если нужен протокольный фон, базовая форма сопоставления здесь — это двухсторонний `token_diff` intent: одна сторона подписывает желаемую разницу по активам, вторая сторона подписывает противоположную разницу, а затем совпавшая пара отправляется на расчёт. Но для рабочего расследования обычно понятнее начать с одной реальной транзакции расчёта и читать доказательства прямо с цепочки. + +```mermaid +flowchart LR + T["Одна mainnet-транзакция
4cfei8p4..."] --> I["intents.near
execute_intents"] + I --> R["Последующие receipt"] + R --> C["Подключаются другие контракты"] + R --> E["Появляются журналы событий"] + E --> TD["token_diff"] + E --> IE["intents_executed"] + E --> MT["mt_transfer / mt_withdraw"] +``` + +Публичных FastNear-поверхностей уже достаточно, чтобы ответить на практический вопрос: | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Якорь расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и получаем саму транзакцию плюс список последующих receipt | Даёт читаемый каркас расчёта без необходимости сразу декодировать сырые receipt | -| Контекст включающего блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Помещает расчёт в контекст блока и показывает, какие receipt появились там | -| Канонический DAG по receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` | Даёт протокольно-канонический DAG, `executor_id` и сырые логи событий | -| Классификация событий | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Извлекаем имена событий вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` из строк `EVENT_JSON` | Позволяет объяснять расчёт по семействам событий, а не по непрозрачным `receipt_id` | +| Каркас расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и печатаем основную транзакцию плюс первые последующие receipt | Даёт самый быстрый читаемый ответ на вопрос «во что этот расчёт пошёл дальше?» | +| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Показывает, в какой блок попал расчёт и какие receipt этой транзакции видны в блоке | +| Каноническое доказательство по receipt и событиям | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` плюс логи `EVENT_JSON` | Даёт протокольно-канонический DAG, `executor_id` и имена событий, которые объясняют, что реально сделал расчёт | **Что должен включать полезный ответ** -- как концептуальная двухсторонняя модель `token_diff` отображается на реальный расчёт через `execute_intents` -- какие последующие контракты и методы появились после `intents.near` +- какую входную точку расчёта вы увидели на `intents.near` +- какие downstream-контракты и методы появились сразу после неё - какие семейства событий выпустила трассировка -- какие высоты блоков сформировали основной каскад +- одно итоговое предложение простым языком о том, что произошло Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. -### Shell-сценарий для живой трассировки NEAR Intents +### Shell-сценарий расчёта NEAR Intents Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. **Что вы делаете** -- Получаете историю транзакции через Transactions API. +- Получаете читаемую историю расчёта через Transactions API. - Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. -- Подтверждаете канонический DAG по receipt и семейства логов событий через `EXPERIMENTAL_tx_status`. +- Подтверждаете канонический DAG по receipt и семейства событий через `EXPERIMENTAL_tx_status`. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -4485,7 +5193,7 @@ INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 INTENTS_SIGNER_ID=intents.near ``` -1. Начните с самой транзакции расчёта. +1. Начните с самой транзакции расчёта и восстановите первый читаемый поток последующих receipt. ```bash INTENTS_BLOCK_HASH="$( @@ -4519,6 +5227,8 @@ jq '{ }' /tmp/intents-transaction.json ``` +Этот первый шаг уже даёт сильный операторский ответ: `intents.near` выполнил транзакцию расчёта, а ранние последующие receipt показывают, какие контракты вошли в каскад сразу после этого. + 2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. ```bash @@ -4588,24 +5298,27 @@ jq -r ' **Зачем нужен следующий шаг?** -`POST /v0/transactions` даёт читаемый каркас расчёта. `POST /v0/block` показывает, как этот расчёт расположен внутри включающего блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и сырые логи событий, а не только индексированное резюме. +`POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. -### Shell-сценарий для pivot по receipt +### Shell-сценарий: от страшного receipt ID к человеческой истории -Используйте этот сценарий, когда у вас уже есть один `receipt_id` и нужен самый короткий путь обратно к читаемой истории транзакции. +Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. **Что вы делаете** - Сначала разрешаете receipt. - Извлекаете `receipt.transaction_hash` через `jq`. - Переиспользуете этот хеш транзакции в `POST /v0/transactions`. +- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID=YOUR_RECEIPT_ID -# Пример receipt ID из недавнего mainnet-перевода: -# RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +``` +1. Разрешите receipt и поймите, что за объект вы смотрите. + +```bash TX_HASH="$( curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ @@ -4619,26 +5332,141 @@ jq '{ receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, + receipt_type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, transaction_hash: .receipt.transaction_hash, tx_block_height: .receipt.tx_block_height } }' /tmp/receipt-lookup.json +``` + +2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. +```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - receipt_count: (.transactions[0].receipts | length) - }' + | tee /tmp/receipt-parent-transaction.json >/dev/null + +jq '{ + transaction: { + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transfer_deposit_yocto: ( + .transactions[0].transaction.actions[0].Transfer.deposit // null + ) + }, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/receipt-parent-transaction.json +``` + +3. Сведите это к одному человеческому предложению. + +```bash +jq -r ' + .transactions[0] as $tx + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." +' /tmp/receipt-parent-transaction.json ``` +Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. + +В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. + **Зачем нужен следующий шаг?** -`POST /v0/receipt` даёт точку перехода. `POST /v0/transactions` превращает эту точку в читаемую историю с контекстом по отправителю, получателю, блоку и связанным receipt-ам. И только после этого обычно стоит расширяться до окон по блоку или аккаунту. +`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. + +## Частые задачи + +### Найти одну транзакцию + +**Начните здесь** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. + +**Следующая страница при необходимости** + +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. +- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. + +**Остановитесь, когда** + +- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. +- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. + +### Исследовать квитанцию + +**Начните здесь** + +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. +- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. + +**Остановитесь, когда** + +- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. + +**Переходите дальше, когда** + +- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. + +### Посмотреть недавнюю активность аккаунта + +**Начните здесь** + +- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. + +**Остановитесь, когда** + +- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. + +**Переходите дальше, когда** + +- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). +- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). + +### Восстановить ограниченное окно по блокам + +**Начните здесь** + +- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. +- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. + +**Остановитесь, когда** + +- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. + +**Переходите дальше, когда** + +- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). ## Частые ошибки diff --git a/static/ru/llms.txt b/static/ru/llms.txt index 9d60aa5..e69a2a9 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -16,7 +16,7 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData API](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии использования KV FastData для точных ключей, истории ключей, анализа по предшественнику и перехода к каноническому RPC. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. - [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. @@ -32,17 +32,17 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп ## Другие гайды -- [Примеры FastNear API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для сводок по аккаунтам, поиска по ключам и перехода к более узким представлениям активов. +- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии использования NEAR Data API для опроса блоков, маршрутов перенаправления и перехода к каноническому RPC. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для polling по оптимистичным и финализированным блокам и перехода к RPC, когда это нужно. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. -- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии использования Transfers API для узкой истории переводов, пагинации и перехода к более широкому расследованию. -- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые сценарии использования Transactions API для поиска транзакций, расследования квитанций, истории аккаунтов и анализа диапазонов блоков. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. +- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents. ## Снапшоты - [Снапшоты для валидаторов](https://docs.fastnear.com/ru/snapshots.md): Пути загрузки снапшотов FastNear для подъёма и восстановления узлов NEAR. -- [Примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples.md): Пошаговые операторские сценарии для выбора правильного пути снапшотов FastNear и выполнения типовых задач запуска и восстановления. +- [Примеры Snapshot](https://docs.fastnear.com/ru/snapshots/examples.md): Пошаговые операторские сценарии для выбора правильного пути восстановления через FastNear snapshots. - [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. diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index 4893091..e6cf70e 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -1,23 +1,74 @@ **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) -# Примеры NEAR Data API +## Готовое расследование + +### Поймать новый блок как можно раньше, а затем подтвердить его после finality + +Используйте это расследование, когда нужно заметить новый блок как можно раньше, но финальный ответ всё равно должен опираться на финализированный блок и иногда на точное чтение через RPC. + +**Цель** + +- Быстро заметить недавний блок, а затем проверить то же самое, когда догонит finality. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](https://docs.fastnear.com/ru/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | +| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](https://docs.fastnear.com/ru/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | +| Стабильное подтверждение | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) или [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | +| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | +| Точный разбор через RPC | RPC [Блок по ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Блок по высоте](https://docs.fastnear.com/ru/rpc/block/block-by-height) | Получаем точный блок, как только понятно, какой именно блок важен | Здесь уже имеет смысл RPC, если нужен тот самый блок-объект, который вернул бы сам протокол | + +**Что должен включать полезный ответ** + +- какое наблюдение по оптимистичному блоку впервые запустило расследование +- когда то же наблюдение стало финализированным +- изменил ли точный разбор через RPC интерпретацию + +### Shell-сценарий + +Используйте этот сценарий, когда вспомогательный маршрут сам выбирает для вас последний финализированный блок, но следующий шаг всё равно требует точной проверки через RPC. + +**Что вы делаете** + +- Смотрите redirect, который возвращает `GET /v0/last_block/final`. +- Загружаете итоговый документ блока. +- Извлекаете `block.header.height` через `jq`. +- Переиспользуете эту высоту в RPC `block` по высоте. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +RPC_URL=https://rpc.mainnet.fastnear.com -Используйте эту страницу, когда свежесть важнее протокольной точности. NEAR Data API предназначен для опроса и чтения недавних данных по семейству блоков: начинайте с самого свежего или самого стабильного режима блоков, который подходит задаче, оставайтесь на этой поверхности для опроса, пока она отвечает на вопрос, и переходите к RPC только тогда, когда действительно нужна каноническая семантика блока или состояния. +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" -## Когда начинать здесь +printf 'Redirect target: %s\n' "$FINAL_LOCATION" -- Нужны недавние оптимистичные или финализированные данные по семейству блоков. -- Нужен клиент опроса, монитор или проверка свежести. -- Маршруты перенаправления приемлемы или полезны для клиентского сценария. -- Задача звучит как «что изменилось недавно?», а не как каноническое историческое подтверждение. +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | tee /tmp/neardata-final-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + +BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "block", + params: { + block_id: ($block_height | tonumber) + } + }')" \ + | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +``` -## Минимальные входы +**Зачем нужен следующий шаг?** -- сеть -- режим свежести: optimistic или finalized -- есть ли конкретная высота или хеш, либо нужен самый свежий объект семейства блоков -- может ли клиент корректно следовать перенаправлениям -- потребуется ли позже проверка через RPC +Helper route — самый простой способ опрашивать сценарий «последний финализированный блок». Как только он сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. ## Частые задачи @@ -35,7 +86,7 @@ - Уже можно сообщить о последнем оптимистичном блоке или зафиксировать отставание по свежести. -**Расширяйте, когда** +**Переходите дальше, когда** - Нужна finalized-стабильность вместо максимальной свежести. Переходите к [Финализированному блоку по высоте](https://docs.fastnear.com/ru/neardata/block) или [Перенаправлению на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final). @@ -54,9 +105,9 @@ - Уже можно показывать движение финализированных блоков без перехода к более глубоким протокольным деталям. -**Расширяйте, когда** +**Переходите дальше, когда** -- Пользователю нужны точные канонические поля блока или семантика транзакций. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Пользователю нужны точные поля блока или семантика транзакций. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). ### Использовать маршруты перенаправления в клиенте опроса @@ -66,17 +117,17 @@ **Следующая страница при необходимости** -- Следуйте по канонической цели, которую вернул маршрут перенаправления, и уже там читайте нужные данные по семейству блоков. +- Следуйте по URL блока, который вернул маршрут перенаправления, и уже там читайте нужные данные. **Остановитесь, когда** - Клиент надёжно проходит по маршруту перенаправления и получает нужный ресурс блока. -**Расширяйте, когда** +**Переходите дальше, когда** - Само перенаправление мешает клиенту. Тогда переходите на прямые маршруты блоков. -### Перейти от свежего опроса к каноническому разбору через RPC +### Перейти от опроса свежих блоков к точному RPC-разбору **Начните здесь** @@ -88,86 +139,16 @@ **Остановитесь, когда** -- Уже можно чётко назвать недавний блок, который заслуживает канонической проверки. +- Уже можно чётко назвать недавний блок, который заслуживает проверки через RPC. -**Расширяйте, когда** +**Переходите дальше, когда** - Пользователь просит точную структуру данных в терминах протокола, а не просто свежее чтение. -## Готовое расследование - -### Начать с оптимистичного блока, затем подтвердить финализированную и каноническую картину - -Используйте это расследование, когда нужно раннее обнаружение по оптимистичному блоку, но финальный ответ всё равно должен опираться на стабильный финализированный вид и, при необходимости, на каноническое подтверждение через RPC. - -**Цель** - -- Быстро заметить недавнее изменение, а затем сузить его до финализированной и канонической истории блока без лишних запросов. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](https://docs.fastnear.com/ru/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | -| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](https://docs.fastnear.com/ru/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | -| Стабильное подтверждение | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) или [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | -| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | -| Каноническая проверка | RPC [Блок по ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Блок по высоте](https://docs.fastnear.com/ru/rpc/block/block-by-height) | Получаем точный канонический блок, как только понятно, какой именно блок важен | Переводит расследование от свежих данных к протокольно точному подтверждению только когда это действительно нужно | - -**Что должен включать полезный ответ** - -- какое наблюдение по оптимистичному блоку впервые запустило расследование -- когда то же наблюдение стало финализированным -- изменил ли канонический разбор через RPC интерпретацию - -### Shell-сценарий - -Используйте этот сценарий, когда вспомогательный маршрут должен сам выбирать для вас последний финализированный блок, но следующий шаг всё равно требует канонического подтверждения через RPC. - -**Что вы делаете** - -- Смотрите redirect, который возвращает `GET /v0/last_block/final`. -- Загружаете итоговый документ блока. -- Извлекаете `block.header.height` через `jq`. -- Переиспользуете эту высоту в RPC `block` по высоте. - -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -RPC_URL=https://rpc.mainnet.fastnear.com - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -printf 'Redirect target: %s\n' "$FINAL_LOCATION" - -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' - -BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "block", - params: { - block_id: ($block_height | tonumber) - } - }')" \ - | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' -``` - -**Зачем нужен следующий шаг?** - -Вспомогательный маршрут — самый простой путь опроса для сценария «последний финализированный блок». Как только он сообщил точную высоту блока, правильной следующей поверхностью становится RPC, если нужны канонические семантики этого блока без догадок о том, какой блок проверять. - ## Частые ошибки -- Воспринимать NEAR Data API как потоковый продукт, а не как поверхность для опроса. -- Начинать с канонического RPC, когда настоящая задача — мониторинг свежих блоков. +- Воспринимать NEAR Data как push-стрим, а не как API для опроса. +- Начинать с RPC, когда настоящая задача — мониторинг свежих блоков. - Забывать, что невалидный ключ может вернуть `401` ещё до перенаправления, а сами перенаправления подходят не каждому HTTP-клиенту. - Оставаться на NEAR Data после того, как пользователь уже попросил точные протокольные детали блока. diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index 4893091..e6cf70e 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -1,23 +1,74 @@ **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) -# Примеры NEAR Data API +## Готовое расследование + +### Поймать новый блок как можно раньше, а затем подтвердить его после finality + +Используйте это расследование, когда нужно заметить новый блок как можно раньше, но финальный ответ всё равно должен опираться на финализированный блок и иногда на точное чтение через RPC. + +**Цель** + +- Быстро заметить недавний блок, а затем проверить то же самое, когда догонит finality. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](https://docs.fastnear.com/ru/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | +| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](https://docs.fastnear.com/ru/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | +| Стабильное подтверждение | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) или [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | +| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | +| Точный разбор через RPC | RPC [Блок по ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Блок по высоте](https://docs.fastnear.com/ru/rpc/block/block-by-height) | Получаем точный блок, как только понятно, какой именно блок важен | Здесь уже имеет смысл RPC, если нужен тот самый блок-объект, который вернул бы сам протокол | + +**Что должен включать полезный ответ** + +- какое наблюдение по оптимистичному блоку впервые запустило расследование +- когда то же наблюдение стало финализированным +- изменил ли точный разбор через RPC интерпретацию + +### Shell-сценарий + +Используйте этот сценарий, когда вспомогательный маршрут сам выбирает для вас последний финализированный блок, но следующий шаг всё равно требует точной проверки через RPC. + +**Что вы делаете** + +- Смотрите redirect, который возвращает `GET /v0/last_block/final`. +- Загружаете итоговый документ блока. +- Извлекаете `block.header.height` через `jq`. +- Переиспользуете эту высоту в RPC `block` по высоте. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +RPC_URL=https://rpc.mainnet.fastnear.com -Используйте эту страницу, когда свежесть важнее протокольной точности. NEAR Data API предназначен для опроса и чтения недавних данных по семейству блоков: начинайте с самого свежего или самого стабильного режима блоков, который подходит задаче, оставайтесь на этой поверхности для опроса, пока она отвечает на вопрос, и переходите к RPC только тогда, когда действительно нужна каноническая семантика блока или состояния. +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" -## Когда начинать здесь +printf 'Redirect target: %s\n' "$FINAL_LOCATION" -- Нужны недавние оптимистичные или финализированные данные по семейству блоков. -- Нужен клиент опроса, монитор или проверка свежести. -- Маршруты перенаправления приемлемы или полезны для клиентского сценария. -- Задача звучит как «что изменилось недавно?», а не как каноническое историческое подтверждение. +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | tee /tmp/neardata-final-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + +BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "block", + params: { + block_id: ($block_height | tonumber) + } + }')" \ + | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +``` -## Минимальные входы +**Зачем нужен следующий шаг?** -- сеть -- режим свежести: optimistic или finalized -- есть ли конкретная высота или хеш, либо нужен самый свежий объект семейства блоков -- может ли клиент корректно следовать перенаправлениям -- потребуется ли позже проверка через RPC +Helper route — самый простой способ опрашивать сценарий «последний финализированный блок». Как только он сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. ## Частые задачи @@ -35,7 +86,7 @@ - Уже можно сообщить о последнем оптимистичном блоке или зафиксировать отставание по свежести. -**Расширяйте, когда** +**Переходите дальше, когда** - Нужна finalized-стабильность вместо максимальной свежести. Переходите к [Финализированному блоку по высоте](https://docs.fastnear.com/ru/neardata/block) или [Перенаправлению на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final). @@ -54,9 +105,9 @@ - Уже можно показывать движение финализированных блоков без перехода к более глубоким протокольным деталям. -**Расширяйте, когда** +**Переходите дальше, когда** -- Пользователю нужны точные канонические поля блока или семантика транзакций. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Пользователю нужны точные поля блока или семантика транзакций. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). ### Использовать маршруты перенаправления в клиенте опроса @@ -66,17 +117,17 @@ **Следующая страница при необходимости** -- Следуйте по канонической цели, которую вернул маршрут перенаправления, и уже там читайте нужные данные по семейству блоков. +- Следуйте по URL блока, который вернул маршрут перенаправления, и уже там читайте нужные данные. **Остановитесь, когда** - Клиент надёжно проходит по маршруту перенаправления и получает нужный ресурс блока. -**Расширяйте, когда** +**Переходите дальше, когда** - Само перенаправление мешает клиенту. Тогда переходите на прямые маршруты блоков. -### Перейти от свежего опроса к каноническому разбору через RPC +### Перейти от опроса свежих блоков к точному RPC-разбору **Начните здесь** @@ -88,86 +139,16 @@ **Остановитесь, когда** -- Уже можно чётко назвать недавний блок, который заслуживает канонической проверки. +- Уже можно чётко назвать недавний блок, который заслуживает проверки через RPC. -**Расширяйте, когда** +**Переходите дальше, когда** - Пользователь просит точную структуру данных в терминах протокола, а не просто свежее чтение. -## Готовое расследование - -### Начать с оптимистичного блока, затем подтвердить финализированную и каноническую картину - -Используйте это расследование, когда нужно раннее обнаружение по оптимистичному блоку, но финальный ответ всё равно должен опираться на стабильный финализированный вид и, при необходимости, на каноническое подтверждение через RPC. - -**Цель** - -- Быстро заметить недавнее изменение, а затем сузить его до финализированной и канонической истории блока без лишних запросов. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](https://docs.fastnear.com/ru/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | -| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](https://docs.fastnear.com/ru/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | -| Стабильное подтверждение | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) или [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | -| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | -| Каноническая проверка | RPC [Блок по ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Блок по высоте](https://docs.fastnear.com/ru/rpc/block/block-by-height) | Получаем точный канонический блок, как только понятно, какой именно блок важен | Переводит расследование от свежих данных к протокольно точному подтверждению только когда это действительно нужно | - -**Что должен включать полезный ответ** - -- какое наблюдение по оптимистичному блоку впервые запустило расследование -- когда то же наблюдение стало финализированным -- изменил ли канонический разбор через RPC интерпретацию - -### Shell-сценарий - -Используйте этот сценарий, когда вспомогательный маршрут должен сам выбирать для вас последний финализированный блок, но следующий шаг всё равно требует канонического подтверждения через RPC. - -**Что вы делаете** - -- Смотрите redirect, который возвращает `GET /v0/last_block/final`. -- Загружаете итоговый документ блока. -- Извлекаете `block.header.height` через `jq`. -- Переиспользуете эту высоту в RPC `block` по высоте. - -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -RPC_URL=https://rpc.mainnet.fastnear.com - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -printf 'Redirect target: %s\n' "$FINAL_LOCATION" - -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' - -BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "block", - params: { - block_id: ($block_height | tonumber) - } - }')" \ - | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' -``` - -**Зачем нужен следующий шаг?** - -Вспомогательный маршрут — самый простой путь опроса для сценария «последний финализированный блок». Как только он сообщил точную высоту блока, правильной следующей поверхностью становится RPC, если нужны канонические семантики этого блока без догадок о том, какой блок проверять. - ## Частые ошибки -- Воспринимать NEAR Data API как потоковый продукт, а не как поверхность для опроса. -- Начинать с канонического RPC, когда настоящая задача — мониторинг свежих блоков. +- Воспринимать NEAR Data как push-стрим, а не как API для опроса. +- Начинать с RPC, когда настоящая задача — мониторинг свежих блоков. - Забывать, что невалидный ключ может вернуть `401` ещё до перенаправления, а сами перенаправления подходят не каждому HTTP-клиенту. - Оставаться на NEAR Data после того, как пользователь уже попросил точные протокольные детали блока. diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index 354f45c..f9251e6 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -2,110 +2,7 @@ # Примеры RPC -Используйте эту страницу, когда уже понятно, что ответ должен опираться на каноническое поведение RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы выбрать правильную стартовую страницу, остановиться, как только RPC-ответ уже решает задачу, и расширять набор поверхностей только тогда, когда это действительно поможет. - -## Когда начинать здесь - -- Пользователь просит точное состояние в цепочке или поля в протокольной форме. -- Нужен прямой вызов view-метода контракта или сценарий отправки транзакции. -- Нужно исследовать блоки, чанки, валидаторов или метаданные протокола. -- Важна семантика узла, а не индексированное агрегированное представление. - -## Минимальные входные данные - -- сеть: mainnet или testnet -- основной идентификатор: `account_id`, публичный ключ, ID контракта плюс метод, хеш транзакции или высота/хеш блока -- нужно ли текущее состояние, историческое состояние или поведение отправки/финальности -- должен ли результат остаться каноническим или затем превратиться в более удобное для человека резюме - -## Частые задачи - -### Проверить точное состояние аккаунта или ключа доступа - -**Начните здесь** - -- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для канонических полей аккаунта. -- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. - -**Следующая страница при необходимости** - -- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после подтверждения канонического состояния нужна ещё и сводка в формате кошелька. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» - -**Остановитесь, когда** - -- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. - -**Расширяйте, когда** - -- Пользователю нужны балансы, NFT, стейкинг или другой продуктовый вид данных. -- Пользователя интересует не текущее каноническое состояние, а недавняя история активности. - -### Проверить блок или снимок состояния протокола - -**Начните здесь** - -- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height) для конкретного блока. -- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block) для текущей канонической головы цепочки. -- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info) для диагностики узла и сети. - -**Следующая страница при необходимости** - -- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если после поиска блока нужен контекст по изменениям состояния. -- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если нужна более читаемая картина исполнения в рамках блока или диапазона. - -**Остановитесь, когда** - -- Канонический ответ блока или протокола уже напрямую отвечает на вопрос. - -**Расширяйте, когда** - -- Нужны данные по свежим блокам в режиме опроса, а не один канонический снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). -- Нужна история по нескольким транзакциям, а не только ответ одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Выполнить view-вызов контракта - -**Начните здесь** - -- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function) для view-метода контракта. -- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда вопрос касается сырого хранилища контракта. -- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда на самом деле нужно понять, есть ли код и каков его хеш. - -**Следующая страница при необходимости** - -- [FastNear API](https://docs.fastnear.com/ru/api), если после сырого вызова пользователю нужен продуктовый ответ, например по активам или сводке аккаунта. -- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующая задача связана с индексированной историей по ключам и значениям, а не с точным RPC-чтением. - -**Остановитесь, когда** - -- Результат view-вызова уже отвечает на вопрос в канонической форме. - -**Расширяйте, когда** - -- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. -- Вопрос смещается от «что метод возвращает сейчас?» к «что менялось со временем?» - -### Отправить транзакцию и подтвердить результат - -**Начните здесь** - -- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужно каноническое поведение отправки с явной семантикой ожидания. -- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить канонический результат. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. - -**Остановитесь, когда** - -- У вас уже есть результат отправки и нужный канонический финальный статус. - -**Расширяйте, когда** - -- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. -- Нужен уже не единичный статус, а более широкий сценарий расследования. +Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. ## Готовые сценарии @@ -115,7 +12,7 @@ **Что вы делаете** -- Через канонический RPC получаете полный список access key аккаунта. +- Через сам RPC получаете полный список access key аккаунта. - Сужаете этот список до function-call-ключей, привязанных к `social.near`. - Точно проверяете один выбранный ключ перед удалением. - Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. @@ -349,7 +246,7 @@ fi **Зачем нужен следующий шаг?** -Повторный вызов `view_access_key_list` замыкает сценарий на той же канонической поверхности, с которой вы начинали поиск. Если ключ исчез именно там, дополнительная индексированная сводка уже не нужна, чтобы подтвердить удаление. +Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. ### Проверить регистрацию FT storage и затем перевести токены @@ -368,10 +265,10 @@ fi **Что вы делаете** -- Через канонические RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. +- Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. - При необходимости получаете минимальный размер storage deposit. - Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. -- Подтверждаете баланс получателя тем же каноническим view-методом контракта. +- Подтверждаете баланс получателя тем же view-методом самого контракта. ```bash export NETWORK_ID=testnet @@ -617,7 +514,7 @@ curl -s "$RPC_URL" \ }' ``` -6. Подтвердите FT-баланс получателя каноническим view-методом контракта. +6. Подтвердите FT-баланс получателя тем же view-методом контракта. ```bash RECEIVER_BALANCE_ARGS_BASE64="$( @@ -649,7 +546,422 @@ curl -s "$RPC_URL" \ **Зачем нужен следующий шаг?** -Это канонический RPC-сценарий, потому что каждый шаг остаётся на точном состоянии контракта и точной семантике отправки транзакций: сначала вы доказываете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. + +### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? + +Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». + +Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: + +- есть ли у целевого аккаунта storage на `social.near`? +- если есть, осталось ли там ещё место? +- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? + +**Официальные ссылки** + +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +**Что вы делаете** + +- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. +- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. +- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. +- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mike.near +export SIGNER_ACCOUNT_ID=mike.near +``` + +1. Сначала проверьте сам аккаунт signer. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/social-publish-signer.json >/dev/null + +jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + signer_account_id: $signer_account_id, + amount: .result.amount, + locked: .result.locked, + storage_usage: .result.storage_usage +}' /tmp/social-publish-signer.json +``` + +Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. + +2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. + +```bash +SOCIAL_STORAGE_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_account_storage", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-account-storage.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + storage: (.result.result | implode | fromjson), + storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) +}' /tmp/social-account-storage.json +``` + +Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. + +3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. + +```bash +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + jq -n --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + signer_matches_target: true, + permission_granted: true, + reason: "owner write" + }' +else + WRITE_PERMISSION_ARGS_BASE64="$( + jq -nc \ + --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ + --arg key "$ACCOUNT_ID" '{ + predecessor_id: $predecessor_id, + key: $key + }' | base64 | tr -d '\n' + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + signer_matches_target: false, + permission_granted: (.result.result | implode | fromjson) + }' +fi +``` + +4. Сведите проверку storage и разрешения в один читаемый итог. + +```bash +AVAILABLE_BYTES="$( + jq -r ' + .result.result + | if length == 0 then "0" + else (implode | fromjson | .available_bytes // 0 | tostring) + end + ' /tmp/social-account-storage.json +)" + +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + PERMISSION_GRANTED=true +else + PERMISSION_GRANTED="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq -r '.result.result | implode | fromjson' + )" +fi + +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ + --argjson available_bytes "$AVAILABLE_BYTES" \ + --argjson permission_granted "$PERMISSION_GRANTED" '{ + account_id: $account_id, + signer_account_id: $signer_account_id, + storage_ready: ($available_bytes > 0), + permission_ready: $permission_granted, + ready_to_publish_now: (($available_bytes > 0) and $permission_granted) + }' +``` + +Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. + +**Зачем нужен следующий шаг?** + +Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. + +### Правда ли, что `efiz.near` опубликовал `DonateNEARtoEfiz`, и что этот виджет делает? + +Используйте этот сценарий, когда история более лёгкая и даже немного шуточная: «друг говорит, что `efiz.near` когда-то опубликовал виджет буквально с именем `DonateNEARtoEfiz`. Проверь это и покажи, что именно делает этот виджет, не выходя из RPC». + +Этот пример намеренно сделан для удовольствия. Он не учит ничему особенно глубокому про async-исполнение. Он просто показывает, как точными чтениями SocialDB просматривать каталог BOS-автора и отвечать на один очень конкретный вопрос по живым on-chain-данным. + +**Официальные ссылки** + +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +**Что вы делаете** + +- Спрашиваете у `social.near` каталог виджетов под `efiz.near`. +- Сохраняете высоты блоков, потому что они показывают, когда каждый ключ виджета последний раз переписывался. +- Подтверждаете, что `DonateNEARtoEfiz` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. +- Заканчиваете простым handoff: если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=efiz.near +export WIDGET_NAME=DonateNEARtoEfiz +``` + +1. Получите каталог виджетов и сохраните высоты блоков последней записи. + +```bash +WIDGET_KEYS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "keys", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-keys.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].widget + | to_entries + | sort_by(.value * -1) + | map({ + widget_name: .key, + last_write_block: .value + }) + | .[0:20] +' /tmp/social-widget-keys.json +``` + +Это даёт компактный BOS-каталог. На момент написания у `efiz.near` был удивительно эклектичный набор виджетов, включая `ReversedFeed`, `HelloWorld`, `PotlockDonateAll` и `DonateNEARtoEfiz`, но настоящий источник истины здесь — именно живой запрос. + +2. Подтвердите, что `DonateNEARtoEfiz` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. + +```bash +WIDGET_GET_ARGS_BASE64="$( + jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-source.json >/dev/null + +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:25] + | join("\n") + ' /tmp/social-widget-source.json +``` + +Так вы печатаете первые 25 строк исходника и быстро понимаете, что это вообще за компонент. В живой версии на момент написания код инициализирует `reciever: "efiz.near"` и строит кнопку, которая вызывает `donate` на выбранную сумму. Название виджета честно предупреждает, что будет дальше. + +3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. + +```bash +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + ' /tmp/social-widget-keys.json \ + | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +``` + +На момент написания живая высота последней записи для `efiz.near/widget/DonateNEARtoEfiz` была `92543301`. + +Если следующий вопрос уже меняется на «какая транзакция записала именно эту версию виджета?», сохраните эту высоту блока и переходите к сценариям-доказательствам NEAR Social в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). + +**Зачем нужен следующий шаг?** + +Это хорошее напоминание, что RPC может быть не только судебной экспертизой. `keys` позволяет просматривать каталог BOS-автора как разработчику, а `get` даёт возможность заглянуть в точное тело виджета, которое реально лежит on-chain. Иногда ответ и правда такой: «да, ваш друг действительно опубликовал виджет с именем `DonateNEARtoEfiz`, и вот его код». + +## Частые задачи + +### Проверить точное состояние аккаунта или ключа доступа + +**Начните здесь** + +- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для точных полей аккаунта. +- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. + +**Следующая страница при необходимости** + +- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» + +**Остановитесь, когда** + +- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. + +**Переходите дальше, когда** + +- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. +- Пользователя интересует не текущее состояние, а недавняя история активности. + +### Проверить один точный блок или снимок состояния протокола + +**Начните здесь** + +- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. +- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» +- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. + +**Следующая страница при необходимости** + +- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. +- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» + +**Остановитесь, когда** + +- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. + +**Переходите дальше, когда** + +- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Что этот контракт возвращает прямо сейчас? + +**Начните здесь** + +- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. +- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. +- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» + +**Следующая страница при необходимости** + +- [FastNear API](https://docs.fastnear.com/ru/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. +- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» + +**Остановитесь, когда** + +- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. + +**Переходите дальше, когда** + +- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. +- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» + +### Отправить транзакцию и подтвердить результат + +**Начните здесь** + +- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. +- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. +- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» + +**Остановитесь, когда** + +- У вас уже есть результат отправки и нужный финальный статус. + +**Переходите дальше, когда** + +- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. +- Нужен уже не единичный статус, а более широкий сценарий расследования. ## Частые ошибки diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index 354f45c..f9251e6 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -2,110 +2,7 @@ # Примеры RPC -Используйте эту страницу, когда уже понятно, что ответ должен опираться на каноническое поведение RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы выбрать правильную стартовую страницу, остановиться, как только RPC-ответ уже решает задачу, и расширять набор поверхностей только тогда, когда это действительно поможет. - -## Когда начинать здесь - -- Пользователь просит точное состояние в цепочке или поля в протокольной форме. -- Нужен прямой вызов view-метода контракта или сценарий отправки транзакции. -- Нужно исследовать блоки, чанки, валидаторов или метаданные протокола. -- Важна семантика узла, а не индексированное агрегированное представление. - -## Минимальные входные данные - -- сеть: mainnet или testnet -- основной идентификатор: `account_id`, публичный ключ, ID контракта плюс метод, хеш транзакции или высота/хеш блока -- нужно ли текущее состояние, историческое состояние или поведение отправки/финальности -- должен ли результат остаться каноническим или затем превратиться в более удобное для человека резюме - -## Частые задачи - -### Проверить точное состояние аккаунта или ключа доступа - -**Начните здесь** - -- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для канонических полей аккаунта. -- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. - -**Следующая страница при необходимости** - -- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после подтверждения канонического состояния нужна ещё и сводка в формате кошелька. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» - -**Остановитесь, когда** - -- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. - -**Расширяйте, когда** - -- Пользователю нужны балансы, NFT, стейкинг или другой продуктовый вид данных. -- Пользователя интересует не текущее каноническое состояние, а недавняя история активности. - -### Проверить блок или снимок состояния протокола - -**Начните здесь** - -- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height) для конкретного блока. -- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block) для текущей канонической головы цепочки. -- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info) для диагностики узла и сети. - -**Следующая страница при необходимости** - -- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если после поиска блока нужен контекст по изменениям состояния. -- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если нужна более читаемая картина исполнения в рамках блока или диапазона. - -**Остановитесь, когда** - -- Канонический ответ блока или протокола уже напрямую отвечает на вопрос. - -**Расширяйте, когда** - -- Нужны данные по свежим блокам в режиме опроса, а не один канонический снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). -- Нужна история по нескольким транзакциям, а не только ответ одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Выполнить view-вызов контракта - -**Начните здесь** - -- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function) для view-метода контракта. -- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда вопрос касается сырого хранилища контракта. -- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда на самом деле нужно понять, есть ли код и каков его хеш. - -**Следующая страница при необходимости** - -- [FastNear API](https://docs.fastnear.com/ru/api), если после сырого вызова пользователю нужен продуктовый ответ, например по активам или сводке аккаунта. -- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующая задача связана с индексированной историей по ключам и значениям, а не с точным RPC-чтением. - -**Остановитесь, когда** - -- Результат view-вызова уже отвечает на вопрос в канонической форме. - -**Расширяйте, когда** - -- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. -- Вопрос смещается от «что метод возвращает сейчас?» к «что менялось со временем?» - -### Отправить транзакцию и подтвердить результат - -**Начните здесь** - -- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужно каноническое поведение отправки с явной семантикой ожидания. -- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить канонический результат. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. - -**Остановитесь, когда** - -- У вас уже есть результат отправки и нужный канонический финальный статус. - -**Расширяйте, когда** - -- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. -- Нужен уже не единичный статус, а более широкий сценарий расследования. +Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. ## Готовые сценарии @@ -115,7 +12,7 @@ **Что вы делаете** -- Через канонический RPC получаете полный список access key аккаунта. +- Через сам RPC получаете полный список access key аккаунта. - Сужаете этот список до function-call-ключей, привязанных к `social.near`. - Точно проверяете один выбранный ключ перед удалением. - Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. @@ -349,7 +246,7 @@ fi **Зачем нужен следующий шаг?** -Повторный вызов `view_access_key_list` замыкает сценарий на той же канонической поверхности, с которой вы начинали поиск. Если ключ исчез именно там, дополнительная индексированная сводка уже не нужна, чтобы подтвердить удаление. +Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. ### Проверить регистрацию FT storage и затем перевести токены @@ -368,10 +265,10 @@ fi **Что вы делаете** -- Через канонические RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. +- Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. - При необходимости получаете минимальный размер storage deposit. - Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. -- Подтверждаете баланс получателя тем же каноническим view-методом контракта. +- Подтверждаете баланс получателя тем же view-методом самого контракта. ```bash export NETWORK_ID=testnet @@ -617,7 +514,7 @@ curl -s "$RPC_URL" \ }' ``` -6. Подтвердите FT-баланс получателя каноническим view-методом контракта. +6. Подтвердите FT-баланс получателя тем же view-методом контракта. ```bash RECEIVER_BALANCE_ARGS_BASE64="$( @@ -649,7 +546,422 @@ curl -s "$RPC_URL" \ **Зачем нужен следующий шаг?** -Это канонический RPC-сценарий, потому что каждый шаг остаётся на точном состоянии контракта и точной семантике отправки транзакций: сначала вы доказываете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. + +### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? + +Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». + +Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: + +- есть ли у целевого аккаунта storage на `social.near`? +- если есть, осталось ли там ещё место? +- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? + +**Официальные ссылки** + +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +**Что вы делаете** + +- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. +- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. +- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. +- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mike.near +export SIGNER_ACCOUNT_ID=mike.near +``` + +1. Сначала проверьте сам аккаунт signer. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/social-publish-signer.json >/dev/null + +jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + signer_account_id: $signer_account_id, + amount: .result.amount, + locked: .result.locked, + storage_usage: .result.storage_usage +}' /tmp/social-publish-signer.json +``` + +Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. + +2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. + +```bash +SOCIAL_STORAGE_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_account_storage", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-account-storage.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + storage: (.result.result | implode | fromjson), + storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) +}' /tmp/social-account-storage.json +``` + +Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. + +3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. + +```bash +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + jq -n --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + signer_matches_target: true, + permission_granted: true, + reason: "owner write" + }' +else + WRITE_PERMISSION_ARGS_BASE64="$( + jq -nc \ + --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ + --arg key "$ACCOUNT_ID" '{ + predecessor_id: $predecessor_id, + key: $key + }' | base64 | tr -d '\n' + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + signer_matches_target: false, + permission_granted: (.result.result | implode | fromjson) + }' +fi +``` + +4. Сведите проверку storage и разрешения в один читаемый итог. + +```bash +AVAILABLE_BYTES="$( + jq -r ' + .result.result + | if length == 0 then "0" + else (implode | fromjson | .available_bytes // 0 | tostring) + end + ' /tmp/social-account-storage.json +)" + +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + PERMISSION_GRANTED=true +else + PERMISSION_GRANTED="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq -r '.result.result | implode | fromjson' + )" +fi + +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ + --argjson available_bytes "$AVAILABLE_BYTES" \ + --argjson permission_granted "$PERMISSION_GRANTED" '{ + account_id: $account_id, + signer_account_id: $signer_account_id, + storage_ready: ($available_bytes > 0), + permission_ready: $permission_granted, + ready_to_publish_now: (($available_bytes > 0) and $permission_granted) + }' +``` + +Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. + +**Зачем нужен следующий шаг?** + +Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. + +### Правда ли, что `efiz.near` опубликовал `DonateNEARtoEfiz`, и что этот виджет делает? + +Используйте этот сценарий, когда история более лёгкая и даже немного шуточная: «друг говорит, что `efiz.near` когда-то опубликовал виджет буквально с именем `DonateNEARtoEfiz`. Проверь это и покажи, что именно делает этот виджет, не выходя из RPC». + +Этот пример намеренно сделан для удовольствия. Он не учит ничему особенно глубокому про async-исполнение. Он просто показывает, как точными чтениями SocialDB просматривать каталог BOS-автора и отвечать на один очень конкретный вопрос по живым on-chain-данным. + +**Официальные ссылки** + +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +**Что вы делаете** + +- Спрашиваете у `social.near` каталог виджетов под `efiz.near`. +- Сохраняете высоты блоков, потому что они показывают, когда каждый ключ виджета последний раз переписывался. +- Подтверждаете, что `DonateNEARtoEfiz` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. +- Заканчиваете простым handoff: если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=efiz.near +export WIDGET_NAME=DonateNEARtoEfiz +``` + +1. Получите каталог виджетов и сохраните высоты блоков последней записи. + +```bash +WIDGET_KEYS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "keys", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-keys.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].widget + | to_entries + | sort_by(.value * -1) + | map({ + widget_name: .key, + last_write_block: .value + }) + | .[0:20] +' /tmp/social-widget-keys.json +``` + +Это даёт компактный BOS-каталог. На момент написания у `efiz.near` был удивительно эклектичный набор виджетов, включая `ReversedFeed`, `HelloWorld`, `PotlockDonateAll` и `DonateNEARtoEfiz`, но настоящий источник истины здесь — именно живой запрос. + +2. Подтвердите, что `DonateNEARtoEfiz` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. + +```bash +WIDGET_GET_ARGS_BASE64="$( + jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-source.json >/dev/null + +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:25] + | join("\n") + ' /tmp/social-widget-source.json +``` + +Так вы печатаете первые 25 строк исходника и быстро понимаете, что это вообще за компонент. В живой версии на момент написания код инициализирует `reciever: "efiz.near"` и строит кнопку, которая вызывает `donate` на выбранную сумму. Название виджета честно предупреждает, что будет дальше. + +3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. + +```bash +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + ' /tmp/social-widget-keys.json \ + | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +``` + +На момент написания живая высота последней записи для `efiz.near/widget/DonateNEARtoEfiz` была `92543301`. + +Если следующий вопрос уже меняется на «какая транзакция записала именно эту версию виджета?», сохраните эту высоту блока и переходите к сценариям-доказательствам NEAR Social в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). + +**Зачем нужен следующий шаг?** + +Это хорошее напоминание, что RPC может быть не только судебной экспертизой. `keys` позволяет просматривать каталог BOS-автора как разработчику, а `get` даёт возможность заглянуть в точное тело виджета, которое реально лежит on-chain. Иногда ответ и правда такой: «да, ваш друг действительно опубликовал виджет с именем `DonateNEARtoEfiz`, и вот его код». + +## Частые задачи + +### Проверить точное состояние аккаунта или ключа доступа + +**Начните здесь** + +- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для точных полей аккаунта. +- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. + +**Следующая страница при необходимости** + +- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» + +**Остановитесь, когда** + +- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. + +**Переходите дальше, когда** + +- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. +- Пользователя интересует не текущее состояние, а недавняя история активности. + +### Проверить один точный блок или снимок состояния протокола + +**Начните здесь** + +- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. +- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» +- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. + +**Следующая страница при необходимости** + +- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. +- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» + +**Остановитесь, когда** + +- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. + +**Переходите дальше, когда** + +- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Что этот контракт возвращает прямо сейчас? + +**Начните здесь** + +- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. +- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. +- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» + +**Следующая страница при необходимости** + +- [FastNear API](https://docs.fastnear.com/ru/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. +- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» + +**Остановитесь, когда** + +- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. + +**Переходите дальше, когда** + +- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. +- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» + +### Отправить транзакцию и подтвердить результат + +**Начните здесь** + +- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. +- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. +- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» + +**Остановитесь, когда** + +- У вас уже есть результат отправки и нужный финальный статус. + +**Переходите дальше, когда** + +- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. +- Нужен уже не единичный статус, а более широкий сценарий расследования. ## Частые ошибки diff --git a/static/ru/snapshots/examples.md b/static/ru/snapshots/examples.md index 7116617..06779c3 100644 --- a/static/ru/snapshots/examples.md +++ b/static/ru/snapshots/examples.md @@ -1,23 +1,57 @@ **Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) -# Примеры снапшотов +## Готовое расследование + +### Выбрать и выполнить правильный сценарий восстановления mainnet + +Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. + +**Цель** + +- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. + +| Путь или команда | Как используем | Зачем используем | +| --- | --- | --- | +| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | +| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | +| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | +| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | +| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | -Используйте эту страницу, когда вопрос звучит как «какой сценарий со снапшотом вообще запускать?», а не «какая команда существует?». Снапшоты — это операторские сценарии, а не API для данных: сначала выбирайте правильную цель узла, сеть и схему хранения, а затем запускайте самый узкий путь загрузки, который безопасно поднимет узел. +**Что должен включать полезный ответ** -## Когда начинать здесь +- какой сценарий восстановления выбран и почему +- какие ключевые env vars важны для выбранного пути +- куда на диске должны попасть данные +- должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap -- Нужно поднять или восстановить узел. -- Нужно выбрать между обычным RPC, optimized `fast-rpc` и архивным сценарием с hot/cold-данными. -- Нужен самый короткий путь от задачи оператора к правильной последовательности команд. -- Уже понятно, что это инфраструктурная работа, а не чтение прикладных данных. +### Shell-сценарий -## Минимальные входы +Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. -- сеть: mainnet или testnet -- цель узла: обычный RPC, optimized `fast-rpc` или архивный режим -- схема хранения, особенно возможность разделить hot- и cold-архивные данные -- используется ли стандартный каталог данных nearcore или отдельные каталоги для hot/cold -- целевой путь к данным и примерные ограничения по загрузке +**Что вы делаете** + +- Один раз получаете последнюю высоту архивного снапшота mainnet. +- Сохраняете её в `LATEST`. +- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. + +```bash +HOT_DATA_PATH=~/.near/data +COLD_DATA_PATH=/mnt/hdds/cold-data + +LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" +echo "Latest archival mainnet snapshot block: $LATEST" + +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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash +``` + +**Зачем нужен следующий шаг?** + +Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. ## Частые задачи @@ -35,7 +69,7 @@ - Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. -**Расширяйте, когда** +**Переходите дальше, когда** - На самом деле требуется архивное хранение, а не просто быстрый запуск. @@ -53,7 +87,7 @@ - Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. -**Расширяйте, когда** +**Переходите дальше, когда** - Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. @@ -71,9 +105,9 @@ - План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. -**Расширяйте, когда** +**Переходите дальше, когда** -- Оператору нужны уже более широкие nearcore bootstrap-guides, а не только FastNear snapshots. +- Оператору нужны уже общие гайды nearcore по bootstrap, а не только FastNear snapshots. ### Поднять архивные hot-данные в testnet @@ -89,63 +123,10 @@ - Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. -**Расширяйте, когда** +**Переходите дальше, когда** - Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. -## Готовое расследование - -### Выбрать и выполнить правильный сценарий восстановления mainnet - -Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно быстро понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. - -**Цель** - -- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную безопасную последовательность команд. - -| Путь или команда | Как используем | Зачем используем | -| --- | --- | --- | -| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | -| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | -| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | -| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | -| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | - -**Что должен включать полезный ответ** - -- какой сценарий восстановления выбран и почему -- какие ключевые env vars важны для выбранного пути -- куда на диске должны попасть данные -- должен ли оператор оставаться в FastNear snapshot docs или переходить к более широкой документации nearcore - -### Shell-сценарий - -Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. - -**Что вы делаете** - -- Один раз получаете последнюю высоту архивного снапшота mainnet. -- Сохраняете её в `LATEST`. -- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. - -```bash -HOT_DATA_PATH=~/.near/data -COLD_DATA_PATH=/mnt/hdds/cold-data - -LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" -echo "Latest archival mainnet snapshot block: $LATEST" - -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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash -``` - -**Зачем нужен следующий шаг?** - -Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. - ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. diff --git a/static/ru/snapshots/examples/index.md b/static/ru/snapshots/examples/index.md index 7116617..06779c3 100644 --- a/static/ru/snapshots/examples/index.md +++ b/static/ru/snapshots/examples/index.md @@ -1,23 +1,57 @@ **Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) -# Примеры снапшотов +## Готовое расследование + +### Выбрать и выполнить правильный сценарий восстановления mainnet + +Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. + +**Цель** + +- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. + +| Путь или команда | Как используем | Зачем используем | +| --- | --- | --- | +| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | +| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | +| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | +| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | +| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | -Используйте эту страницу, когда вопрос звучит как «какой сценарий со снапшотом вообще запускать?», а не «какая команда существует?». Снапшоты — это операторские сценарии, а не API для данных: сначала выбирайте правильную цель узла, сеть и схему хранения, а затем запускайте самый узкий путь загрузки, который безопасно поднимет узел. +**Что должен включать полезный ответ** -## Когда начинать здесь +- какой сценарий восстановления выбран и почему +- какие ключевые env vars важны для выбранного пути +- куда на диске должны попасть данные +- должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap -- Нужно поднять или восстановить узел. -- Нужно выбрать между обычным RPC, optimized `fast-rpc` и архивным сценарием с hot/cold-данными. -- Нужен самый короткий путь от задачи оператора к правильной последовательности команд. -- Уже понятно, что это инфраструктурная работа, а не чтение прикладных данных. +### Shell-сценарий -## Минимальные входы +Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. -- сеть: mainnet или testnet -- цель узла: обычный RPC, optimized `fast-rpc` или архивный режим -- схема хранения, особенно возможность разделить hot- и cold-архивные данные -- используется ли стандартный каталог данных nearcore или отдельные каталоги для hot/cold -- целевой путь к данным и примерные ограничения по загрузке +**Что вы делаете** + +- Один раз получаете последнюю высоту архивного снапшота mainnet. +- Сохраняете её в `LATEST`. +- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. + +```bash +HOT_DATA_PATH=~/.near/data +COLD_DATA_PATH=/mnt/hdds/cold-data + +LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" +echo "Latest archival mainnet snapshot block: $LATEST" + +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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash +``` + +**Зачем нужен следующий шаг?** + +Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. ## Частые задачи @@ -35,7 +69,7 @@ - Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. -**Расширяйте, когда** +**Переходите дальше, когда** - На самом деле требуется архивное хранение, а не просто быстрый запуск. @@ -53,7 +87,7 @@ - Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. -**Расширяйте, когда** +**Переходите дальше, когда** - Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. @@ -71,9 +105,9 @@ - План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. -**Расширяйте, когда** +**Переходите дальше, когда** -- Оператору нужны уже более широкие nearcore bootstrap-guides, а не только FastNear snapshots. +- Оператору нужны уже общие гайды nearcore по bootstrap, а не только FastNear snapshots. ### Поднять архивные hot-данные в testnet @@ -89,63 +123,10 @@ - Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. -**Расширяйте, когда** +**Переходите дальше, когда** - Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. -## Готовое расследование - -### Выбрать и выполнить правильный сценарий восстановления mainnet - -Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно быстро понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. - -**Цель** - -- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную безопасную последовательность команд. - -| Путь или команда | Как используем | Зачем используем | -| --- | --- | --- | -| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | -| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | -| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | -| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | -| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | - -**Что должен включать полезный ответ** - -- какой сценарий восстановления выбран и почему -- какие ключевые env vars важны для выбранного пути -- куда на диске должны попасть данные -- должен ли оператор оставаться в FastNear snapshot docs или переходить к более широкой документации nearcore - -### Shell-сценарий - -Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. - -**Что вы делаете** - -- Один раз получаете последнюю высоту архивного снапшота mainnet. -- Сохраняете её в `LATEST`. -- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. - -```bash -HOT_DATA_PATH=~/.near/data -COLD_DATA_PATH=/mnt/hdds/cold-data - -LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" -echo "Latest archival mainnet snapshot block: $LATEST" - -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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash -``` - -**Зачем нужен следующий шаг?** - -Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. - ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. diff --git a/static/ru/transfers/examples.md b/static/ru/transfers/examples.md index 5d01a52..9012c41 100644 --- a/static/ru/transfers/examples.md +++ b/static/ru/transfers/examples.md @@ -1,93 +1,16 @@ **Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) -# Примеры Transfers API - -Используйте эту страницу, когда вопрос касается именно движения активов и нужен самый короткий путь по документации истории переводов. Эта поверхность специально узкая: начинайте с самого точного фильтра перевода, который отвечает на вопрос, держите фокус на отправках и получениях и расширяйтесь только тогда, когда вопрос перестаёт быть «только про переводы». - -## Когда начинать здесь - -- Пользователя интересуют входящие или исходящие переводы NEAR или FT. -- Нужна лента кошелька, представление для аудита или ответ для поддержки, сфокусированный на движении активов. -- Аккаунт уже известен, и пока не требуется полная история исполнения. -- Для задачи достаточно mainnet-истории переводов. - -## Минимальные входы - -- `account_id` -- выбора сети здесь нет: сегодня эта поверхность доступна только для mainnet -- опциональные фильтры по направлению, активу, сумме или времени -- нужен ли только короткий набор событий или длинный обзор истории -- может ли позже понадобиться более широкий контекст транзакций - -## Частые задачи - -### Найти исходящие переводы одного аккаунта в узком окне времени - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. - -**Следующая страница при необходимости** - -- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. - -**Остановитесь, когда** - -- Уже можно ответить, кто что отправил, когда и в каком активе. - -**Расширяйте, когда** - -- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Построить ленту переводов с пагинацией через `resume_token` - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий. - -**Следующая страница при необходимости** - -- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. - -**Остановитесь, когда** - -- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. - -**Расширяйте, когда** - -- Пользователь просит метаданные транзакции сверх самих переводов. -- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). - -### Перейти от истории переводов к полному расследованию транзакции - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. - -**Следующая страница при необходимости** - -- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. - -**Остановитесь, когда** - -- Уже определено правильное событие перевода и следующий подходящий раздел для расследования. - -**Расширяйте, когда** - -- Пользователю прямо нужны квитанции или каноническое подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. - ## Готовый сценарий -### Запросить узкое окно переводов, а затем перейти по receipt +### Найти один подозрительный перевод, а затем пройти по его receipt -Используйте этот сценарий, когда первый вопрос всё ещё касается только переводов, но вы уже понимаете, что потом может понадобиться одна точная точка перехода в контекст исполнения. +Используйте этот сценарий, когда история звучит так: «я вижу, что средства двигались, но хочу получить точную опорную точку исполнения для этого движения, не затягивая сразу всю историю аккаунта». **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Извлекаете первый `receipt_id` через `jq`. -- Переиспользуете этот receipt ID в Transactions API, чтобы перейти от движения актива к контексту исполнения. +- Выделяете один перевод, который действительно похож на нужное вам движение. +- Переиспользуете его `receipt_id` в Transactions API, чтобы перейти от движения актива к истории исполнения. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com @@ -142,14 +65,73 @@ curl -s "$TX_BASE_URL/v0/receipt" \ **Зачем нужен следующий шаг?** -Запрос переводов позволяет держать первый проход узким и удобным для пагинации. Переход по `receipt_id` даёт одну точную опорную точку в исполнении, не заставляя сразу расширяться до полной истории аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. +Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` даёт точную опорную точку в исполнении, не затягивая вас сразу в полную историю аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. + +## Частые задачи + +### Найти исходящие переводы одного аккаунта в узком окне времени + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. + +**Следующая страница при необходимости** + +- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. + +**Остановитесь, когда** + +- Уже можно ответить, кто что отправил, когда и в каком активе. + +**Переходите дальше, когда** + +- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Листать ленту переводов дальше и не потерять своё место + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. + +**Следующая страница при необходимости** + +- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. +- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. + +**Остановитесь, когда** + +- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. + +**Переходите дальше, когда** + +- Пользователь просит метаданные транзакции сверх самих переводов. +- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). + +### Перейти от истории переводов к полному расследованию транзакции + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. + +**Следующая страница при необходимости** + +- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. + +**Остановитесь, когда** + +- Уже определено правильное событие перевода и понятно, какой API открывать следующим. + +**Переходите дальше, когда** + +- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. - Считать историю переводов полной историей исполнения. - Переиспользовать `resume_token` с другими фильтрами. -- Начинать здесь с вопросов про testnet, хотя эта поверхность сегодня работает только в mainnet. +- Начинать здесь с вопросов про testnet, хотя этот API сегодня работает только в mainnet. ## Полезные связанные страницы diff --git a/static/ru/transfers/examples/index.md b/static/ru/transfers/examples/index.md index 5d01a52..9012c41 100644 --- a/static/ru/transfers/examples/index.md +++ b/static/ru/transfers/examples/index.md @@ -1,93 +1,16 @@ **Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) -# Примеры Transfers API - -Используйте эту страницу, когда вопрос касается именно движения активов и нужен самый короткий путь по документации истории переводов. Эта поверхность специально узкая: начинайте с самого точного фильтра перевода, который отвечает на вопрос, держите фокус на отправках и получениях и расширяйтесь только тогда, когда вопрос перестаёт быть «только про переводы». - -## Когда начинать здесь - -- Пользователя интересуют входящие или исходящие переводы NEAR или FT. -- Нужна лента кошелька, представление для аудита или ответ для поддержки, сфокусированный на движении активов. -- Аккаунт уже известен, и пока не требуется полная история исполнения. -- Для задачи достаточно mainnet-истории переводов. - -## Минимальные входы - -- `account_id` -- выбора сети здесь нет: сегодня эта поверхность доступна только для mainnet -- опциональные фильтры по направлению, активу, сумме или времени -- нужен ли только короткий набор событий или длинный обзор истории -- может ли позже понадобиться более широкий контекст транзакций - -## Частые задачи - -### Найти исходящие переводы одного аккаунта в узком окне времени - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. - -**Следующая страница при необходимости** - -- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. - -**Остановитесь, когда** - -- Уже можно ответить, кто что отправил, когда и в каком активе. - -**Расширяйте, когда** - -- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Построить ленту переводов с пагинацией через `resume_token` - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий. - -**Следующая страница при необходимости** - -- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. - -**Остановитесь, когда** - -- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. - -**Расширяйте, когда** - -- Пользователь просит метаданные транзакции сверх самих переводов. -- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). - -### Перейти от истории переводов к полному расследованию транзакции - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. - -**Следующая страница при необходимости** - -- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. - -**Остановитесь, когда** - -- Уже определено правильное событие перевода и следующий подходящий раздел для расследования. - -**Расширяйте, когда** - -- Пользователю прямо нужны квитанции или каноническое подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. - ## Готовый сценарий -### Запросить узкое окно переводов, а затем перейти по receipt +### Найти один подозрительный перевод, а затем пройти по его receipt -Используйте этот сценарий, когда первый вопрос всё ещё касается только переводов, но вы уже понимаете, что потом может понадобиться одна точная точка перехода в контекст исполнения. +Используйте этот сценарий, когда история звучит так: «я вижу, что средства двигались, но хочу получить точную опорную точку исполнения для этого движения, не затягивая сразу всю историю аккаунта». **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Извлекаете первый `receipt_id` через `jq`. -- Переиспользуете этот receipt ID в Transactions API, чтобы перейти от движения актива к контексту исполнения. +- Выделяете один перевод, который действительно похож на нужное вам движение. +- Переиспользуете его `receipt_id` в Transactions API, чтобы перейти от движения актива к истории исполнения. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com @@ -142,14 +65,73 @@ curl -s "$TX_BASE_URL/v0/receipt" \ **Зачем нужен следующий шаг?** -Запрос переводов позволяет держать первый проход узким и удобным для пагинации. Переход по `receipt_id` даёт одну точную опорную точку в исполнении, не заставляя сразу расширяться до полной истории аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. +Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` даёт точную опорную точку в исполнении, не затягивая вас сразу в полную историю аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. + +## Частые задачи + +### Найти исходящие переводы одного аккаунта в узком окне времени + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. + +**Следующая страница при необходимости** + +- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. + +**Остановитесь, когда** + +- Уже можно ответить, кто что отправил, когда и в каком активе. + +**Переходите дальше, когда** + +- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Листать ленту переводов дальше и не потерять своё место + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. + +**Следующая страница при необходимости** + +- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. +- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. + +**Остановитесь, когда** + +- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. + +**Переходите дальше, когда** + +- Пользователь просит метаданные транзакции сверх самих переводов. +- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). + +### Перейти от истории переводов к полному расследованию транзакции + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. + +**Следующая страница при необходимости** + +- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. + +**Остановитесь, когда** + +- Уже определено правильное событие перевода и понятно, какой API открывать следующим. + +**Переходите дальше, когда** + +- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. - Считать историю переводов полной историей исполнения. - Переиспользовать `resume_token` с другими фильтрами. -- Начинать здесь с вопросов про testnet, хотя эта поверхность сегодня работает только в mainnet. +- Начинать здесь с вопросов про testnet, хотя этот API сегодня работает только в mainnet. ## Полезные связанные страницы diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index aa752a1..8022730 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -1,160 +1,457 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) -# Примеры Transactions API +## Готовые расследования -Используйте эту страницу, когда вопрос звучит как «что произошло?» и нужен индексированный исторический слой до того, как вы перейдёте к каноническому подтверждению через RPC. Начинайте с того идентификатора, который уже есть на руках, объясняйте историю исполнения в читаемом порядке и расширяйте её только тогда, когда действительно понадобятся точные RPC-семантики. +### Проследить асинхронную promise-цепочку и доказать порядок callback-ов -## Когда начинать здесь +Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» -- У вас уже есть хеш транзакции, ID квитанции, ID аккаунта или ограниченный диапазон блоков. -- Пользователю нужен исторический контекст исполнения, разбор для поддержки или отладки либо читаемая временная шкала. -- Нужна индексированная история без ручной сборки из сырых RPC-вызовов. -- Первый ответ должен объяснить, что произошло, до углубления в протокольные детали. +**Цель** -## Минимальные входные данные +- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. + +Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: + +- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом +- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке +- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и +- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору + +```mermaid +flowchart LR + Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] + H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] + R --> C["Async cross-contract callback-и"] + C --> B["Recorder state
beta"] + B --> A["Recorder state
alpha"] + A --> G["Recorder state
gamma"] + Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] + R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] + G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] +``` -- сеть: mainnet или testnet -- основной идентификатор: хеш транзакции, ID квитанции, `account_id` или блок/диапазон блоков -- расследуете ли вы один объект или целое окно истории -- требуется ли точное каноническое подтверждение через RPC до завершения ответа +Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. -## Частые задачи +Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. -### Найти одну транзакцию +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | +| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | +| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | +| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | +| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | +| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | +| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | +| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | -**Начните здесь** +**Что должен включать полезный ответ** -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. +- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» +- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования +- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался +- в каких блоках стали видны изменения состояния +- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования -**Следующая страница при необходимости** +### Превратить один страшный receipt ID из логов в понятную человеческую историю -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. -- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. +Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. -**Остановитесь, когда** +**Цель** -- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. +- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. -**Расширяйте, когда** +Для этого зафиксированного примера «страшный receipt ID из логов» такой: -- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. -- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. +- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` +- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- высота блока транзакции: `194263342` +- высота блока исполнения receipt: `194263343` -### Исследовать квитанцию +Человеческая история за этим receipt простая: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. -**Начните здесь** +```mermaid +flowchart LR + L["Один страшный receipt ID
5GhZcpfK..."] --> R["Ищем receipt"] + R --> T["Восстанавливаем tx hash
AdgNifPY..."] + T --> S["Читаем действия транзакции"] + S --> H["Человеческая история:
mike.near отправил 5 NEAR в global-counter.mike.near"] +``` -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и печатаем аккаунты, блок исполнения, флаг успеха и связанный хеш транзакции | Даёт самый короткий путь от сырого receipt ID к пониманию, что вообще за объект перед вами | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | +| Каноническое продолжение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | -**Следующая страница при необходимости** +**Что должен включать полезный ответ** -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. -- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. +- какие аккаунты создали и исполнили квитанцию +- к какой транзакции относится эта квитанция +- что транзакция на самом деле сделала +- была ли квитанция главным событием или только шагом в большом каскаде +- одно предложение простым языком, которое можно без правок вставить коллеге в чат -**Остановитесь, когда** +### Доказать, что одно неудачное действие сорвало весь пакет -- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. +Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? -**Расширяйте, когда** +В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. -- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. +**Цель** -### Посмотреть недавнюю активность аккаунта +- На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. -**Начните здесь** +**Официальные ссылки** -- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. +- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) +- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) + +Этот зафиксированный сбой был получен в **testnet 18 апреля 2026 года**: + +- хеш транзакции: `CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M` +- аккаунт signer: `temp.mike.testnet` +- целевой новый аккаунт: `rollback-mo4vmkig.temp.mike.testnet` +- высота включающего блока: `246365118` +- хеш включающего блока: `6f5zTKDqQRwrxMywzvxeRvYcCERJmAnatJaqUEtQYUNM` +- порядок действий: `CreateAccount -> Transfer -> AddKey -> FunctionCall` +- упавший метод: `definitely_missing_method` +- RPC-ошибка: `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet` + +```mermaid +flowchart LR + T["Одна подписанная транзакция"] --> A["CreateAccount"] + A --> B["Transfer 0.01 NEAR"] + B --> C["AddKey"] + C --> D["FunctionCall definitely_missing_method()"] + D --> E["Сбой: CodeDoesNotExist"] + E --> R["Весь пакет не закрепился"] + R --> N["Новый аккаунт не появился"] + R --> K["Новый ключ не закрепился"] + R --> F["У получателя нет профинансированного состояния"] +``` -**Следующая страница при необходимости** +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Задуманный пакет | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированный хеш транзакции и печатаем упорядоченный список действий, получателя и метаданные включающего блока | Показывает, что именно signer пытался сделать, ещё до разговора о том, что закрепилось | +| Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | +| Доказательство по состоянию после исполнения | RPC [`query(view_account)`](https://docs.fastnear.com/ru/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. +Перед shell-сценарием важно отметить одну деталь: индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. -**Остановитесь, когда** +**Что должен включать полезный ответ** -- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. +- точный порядок действий, который отправил signer +- какой индекс действия упал и почему +- высоту и хеш включающего блока для этого батча +- доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality +- короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` -**Расширяйте, когда** +### Shell-сценарий неудачной транзакции с пакетом действий -- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). -- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). +Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. -### Восстановить ограниченное окно по блокам +**Что вы делаете** -**Начните здесь** +- Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. +- Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. +- Через один RPC-запрос к состоянию после исполнения доказываете, что новый аккаунт так и не появился после finality. -- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. -- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. +```bash +TX_BASE_URL=https://tx.test.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M +SIGNER_ACCOUNT_ID=temp.mike.testnet +NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +``` -**Следующая страница при необходимости** +1. Получите транзакцию и распечатайте задуманный пакет действий. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/failed-batch-transaction.json >/dev/null -**Остановитесь, когда** +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height, + included_block_hash: .transactions[0].execution_outcome.block_hash + }, + batch: { + action_count: (.transactions[0].transaction.actions | length), + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + final_function_call_method_name: ( + .transactions[0].transaction.actions[3].FunctionCall.method_name + ) + }, + first_receipt_handoff: .transactions[0].transaction_outcome.outcome.status +}' /tmp/failed-batch-transaction.json + +# Ожидаемый порядок действий: +# 1. CreateAccount +# 2. Transfer +# 3. AddKey +# 4. FunctionCall +``` -- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. +2. Запросите RPC transaction status и посмотрите точную верхнеуровневую ошибку. -**Расширяйте, когда** +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/failed-batch-rpc-status.json >/dev/null -- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +jq '{ + final_execution_status: .result.final_execution_status, + failed_action_index: .result.status.Failure.ActionError.index, + failure: .result.status.Failure.ActionError.kind.FunctionCallError.CompilationError.CodeDoesNotExist +}' /tmp/failed-batch-rpc-status.json -## Готовые расследования +# Ожидаемый failed_action_index: 3 +# Ожидаемый failure account_id: rollback-mo4vmkig.temp.mike.testnet +``` -### Доказать порядок callback-ов в staged/release-сценарии +3. Запросите предполагаемый новый аккаунт после finality и докажите, что его всё ещё нет. -Используйте это расследование, когда сначала была стадия с асинхронной подготовкой работы, потом отдельный release, и нужно доказать не только успешность транзакций, но и конкретный порядок выполнения последующих обратных вызовов. +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/failed-batch-view-account.json >/dev/null -**Цель** +jq '{ + error: .error.cause.name, + message: .error.data, + requested_account_id: .error.cause.info.requested_account_id, + proof_block_height: .error.cause.info.block_height +}' /tmp/failed-batch-view-account.json -- Превратить два хеша транзакций в устойчивый артефакт для расследования, который включает граф квитанций, привязки к блокам и изменения состояния контракта. +# Ожидаемая ошибка: "UNKNOWN_ACCOUNT" +``` -В staged/release-сценариях именно stage-транзакция обычно остаётся главной опорной транзакцией расследования, потому что отложенные callback-и живут на её исходном дереве транзакции, а не на дереве release-транзакции. +Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Захват трассы stage и release | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш stage-транзакции и хеш release-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому дереву транзакции | -| Проверка материализации stage | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод staging-контракта, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления ожидаемых отложенных шагов | Подтверждает, что callback-и действительно стали доступны до того, как release-транзакция попытается их разбудить | -| Обогащение транзакций | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id` и индексированный статус исполнения | Даёт каждой транзакции устойчивую привязку к блоку, чтобы дальнейший анализ не зависел от памяти или ручных заметок | -| Снимки состояния контракта recorder | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до release, а затем опрашиваем его после release до появления ожидаемых записей | Доказывает реальный порядок последующих эффектов в состоянии контракта, а не только в метаданных дерева квитанций | -| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | -| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | -| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | -| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | +**Зачем нужен следующий шаг?** -**Что должен включать полезный ответ** +Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. -- почему именно stage-транзакция, а не release-транзакция, обычно является главной опорной транзакцией расследования -- какой порядок callback-ов вы наблюдали -- в каких блоках стали видны изменения состояния -- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования +### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? -### Начать с receipt ID и восстановить историю исполнения +Используйте это расследование, когда один вызов контракта залогировал успех, изменил своё локальное состояние, и даже верхнеуровневый RPC `status` выглядит успешным, но приложение всё равно сломалось, потому что позже упал отдельный cross-contract receipt. -Используйте это расследование, когда на руках есть только receipt ID из трассы, лога ошибки или дерева callback-ов и нужно вернуться к понятной человеку истории того, что произошло. +Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. **Цель** -- Перейти от одной квитанции к исходной транзакции, а затем расширить расследование ровно настолько, чтобы объяснить окружающее исполнение и эффекты в состоянии. +- Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. + +**Официальные ссылки** + +- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) +- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) + +Этот зафиксированный асинхронный сбой был получен в **testnet 18 апреля 2026 года**: + +- хеш транзакции: `AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz` +- аккаунт signer: `temp.mike.testnet` +- первый контракт-получатель: `seq-dr.mike.testnet` +- аккаунт detached-цели: `asyncfail-in2hwikn.temp.mike.testnet` +- блок включения транзакции: `246368568` +- успешная первая receipt: `6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS` в блоке `246368569` +- более поздняя упавшая receipt: `2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es` в блоке `246368570` +- первый метод: `kickoff_append` +- более поздний упавший метод: `append` +- верхнеуровневый RPC `status`: `SuccessValue` + +```mermaid +flowchart LR + T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] + R --> S["Роутер сохраняет локальное состояние
kicked += late-failure"] + R --> D["Detached cross-contract receipt
append(...)"] + D --> F["Более поздний сбой
CodeDoesNotExist"] + S -. "состояние из первой receipt всё равно закрепилось" .-> K["kicked() всё ещё содержит late-failure"] +``` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и определяем её содержимое, статус и связанный контекст транзакции | ID квитанции часто появляется в трассах и логах раньше, чем у человека складывается цельная история транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | После перехода из поиска квитанции забираем исходную транзакцию по хешу | Превращает одну квитанцию в читаемую историю исполнения с контекстом по получателю, блоку и статусу | -| Каноническое подтверждение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем результат на уровне протокола, когда индексированного вида недостаточно или нужны точные RPC-семантики | Полезно, когда важно различить индексированную интерпретацию и точное поведение RPC | -| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем содержащий блок и при необходимости расширяемся на соседние каскадные блоки, если исполнение растянулось по нескольким высотам | Помещает квитанцию во временную шкалу по блокам, которую проще объяснить | -| Окно активности аккаунта | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Забираем недавнюю активность аккаунтов, которых коснулась квитанция | Помогает связать квитанцию с окружающей историей по аккаунтам | -| Повторное чтение состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод с закреплённым `block_id`, если квитанция изменила видимое состояние контракта | Позволяет доказать, что квитанция не только существовала в метаданных, но и изменила устойчивое состояние контракта | +| Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | +| Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | +| Текущее состояние контракта | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `seq-dr.mike.testnet.kicked()` | Показывает, что локальное изменение состояния из первой receipt закрепилось, хотя более поздняя detached-receipt упала | + +Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не откатил более раннее изменение состояния роутера. **Что должен включать полезный ответ** -- какую исходную транзакцию вы восстановили из квитанции -- была ли квитанция главным событием или только одним шагом в большом каскаде -- какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить -- был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым +- что подписанная транзакция успешно передала управление в первую router-receipt +- что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` +- что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` +- что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился +- одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции + +### Shell-сценарий более позднего сбоя receipt + +Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». + +**Что вы делаете** + +- Читаете транзакцию и её таймлайн receipt из индексированного представления. +- Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. +- Читаете текущее состояние роутера, чтобы показать: локальный побочный эффект первой receipt закрепился. + +```bash +TX_BASE_URL=https://tx.test.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz +SIGNER_ACCOUNT_ID=temp.mike.testnet +ROUTER_ACCOUNT_ID=seq-dr.mike.testnet +FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS +FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es +``` + +1. Получите транзакцию и распечатайте таймлайн receipt по порядку блоков. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/later-receipt-failure-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + tx_handoff: .transactions[0].transaction_outcome.outcome.status + }, + receipts: [ + .transactions[0].receipts[] + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + block_height: .execution_outcome.block_height, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system_transfer"), + status: .execution_outcome.outcome.status + } + ] +}' /tmp/later-receipt-failure-transaction.json + +# На что смотреть: +# - первая receipt контракта на seq-dr.mike.testnet успешно прошла в блоке 246368569 +# - более поздняя receipt append(...) упала в блоке 246368570 +``` + +2. Запросите RPC transaction status и сравните верхнеуровневую историю с более поздней упавшей receipt. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/later-receipt-failure-rpc.json >/dev/null + +jq \ + --arg first_receipt_id "$FIRST_RECEIPT_ID" \ + --arg failed_receipt_id "$FAILED_RECEIPT_ID" '{ + top_level_status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status, + first_contract_receipt: ( + .result.receipts_outcome[] + | select(.id == $first_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + logs: .outcome.logs, + status: .outcome.status + } + ), + later_failed_receipt: ( + .result.receipts_outcome[] + | select(.id == $failed_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + status: .outcome.status + } + ) + }' /tmp/later-receipt-failure-rpc.json + +# На что смотреть: +# - top_level_status всё ещё равен SuccessValue +# - первая receipt контракта залогировала dishonest_router:kickoff:late-failure +# - более поздняя receipt append(...) упала с CodeDoesNotExist +``` + +3. Прочитайте текущее состояние роутера и подтвердите, что локальный побочный эффект первой receipt закрепился. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ROUTER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "kicked", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/later-receipt-failure-kicked.json >/dev/null + +jq '{ + kicked: (.result.result | implode | fromjson), + contains_late_failure: ((.result.result | implode | fromjson) | index("late-failure") != null) +}' /tmp/later-receipt-failure-kicked.json +``` + +Этот последний read и есть практическое доказательство того, что локальное изменение из первой receipt закрепилось. Более поздняя упавшая receipt не откатила более ранний `kicked.push(...)` внутри роутера. + +**Зачем нужен следующий шаг?** + +Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. ### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB @@ -538,61 +835,252 @@ jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" ' NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. -### Понять двухстороннее сопоставление `token_diff`, а затем проследить живой расчёт NEAR Intents +### Какая транзакция записала `DonateNEARtoEfiz`? + +Используйте это расследование, когда история уже более лёгкая и даже немного шуточная: «на странице RPC-примеров я уже увидел, что `efiz.near/widget/DonateNEARtoEfiz` существует и что его last-write block — `92543301`. Какая именно транзакция записала этот виджет?» + +Это весёлый пример, но рецепт доказательства здесь вполне серьёзный и знакомый по другим SocialDB-расследованиям: -Используйте это расследование, когда история звучит так: «покажи, что именно NEAR Intents делает под капотом, но привяжи разбор к публичным данным, которые можно проверить самостоятельно». +- стартуем с одного блока, к которому привязан SocialDB-ключ +- превращаем этот блок в конкретный `efiz.near -> social.near` receipt +- восстанавливаем исходную транзакцию +- декодируем payload `set` и доказываем, что в нём действительно лежит исходник виджета **Цель** -- Сначала объяснить модель сопоставления, а затем превратить один реальный расчёт через `intents.near` в читаемую историю исполнения на базе Transactions API и канонического RPC. +- Превратить last-write block виджета в один читаемый ответ: какая транзакция записала `DonateNEARtoEfiz`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. **Официальные ссылки** -- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) -- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) -- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -#### Часть 1: анатомия протокола +Это расследование специально продолжает более лёгкий RPC-сценарий про виджеты. Для этого живого якоря: -Базовая форма сопоставления здесь — это `token_diff` intent. Одна сторона объявляет, какие активы она готова отдать и получить, а вторая сторона объявляет противоположную разницу. В официальной документации verifier двухсторонний обмен USDC и USDT показан как один подписанный intent со смыслом «я отдам `-10` USDC и получу `+10` USDT» и второй intent, который описывает обратную сторону сделки. Такие подписанные intent можно собрать через Message Bus или через любой другой внешний канал координации и затем отправить вместе в `intents.near`. +- аккаунт: `efiz.near` +- виджет: `DonateNEARtoEfiz` +- блок записи в SocialDB: `92543301` +- receipt ID: `FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E` +- хеш исходной транзакции: `CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf` +- внешний блок транзакции: `92543300` -Эта концептуальная часть полезна, чтобы понять сам протокол, но подписанные примеры в официальной документации носят иллюстративный и привязанный ко времени характер. Для рабочего FastNear-сценария полезнее разбирать один реальный расчёт из mainnet, чем делать вид, будто пример из документации является готовой живой транзакцией. +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `92543301` с `with_receipts: true`, а затем фильтруем его обратно до `efiz.near -> social.near` | Превращает last-write block виджета в один конкретный receipt и один конкретный хеш транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `DonateNEARtoEfiz` | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | + +**Что должен включать полезный ответ** -#### Часть 2: живая FastNear-трассировка +- высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции +- конкретный receipt ID и хеш исходной транзакции за этой записью виджета +- доказательство, что payload записи был `set` с `efiz.near/widget/DonateNEARtoEfiz` +- одно простое предложение вроде «`efiz.near` записал `DonateNEARtoEfiz` в транзакции `CUA61...`, и в payload действительно лежал исходник donation-виджета» -Для живой трассировки ниже используйте этот фиксированный якорь расчёта, зафиксированный **18 апреля 2026 года**: +### Shell-сценарий доказательства записи DonateNEARtoEfiz + +Используйте этот сценарий, когда хотите превратить один игривый блоковый якорь виджета в точную транзакцию, которая его записала. + +**Что вы делаете** + +- Стартуете с блока последней записи виджета. +- Переиспользуете эту высоту в FastNear block receipts, чтобы получить receipt и мост к транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы декодировать записанный исходник виджета. +- Завершаете сырым RPC-подтверждением, что виджет всё ещё существует сейчас. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=efiz.near +WIDGET_NAME=DonateNEARtoEfiz +WIDGET_BLOCK_HEIGHT=92543301 +``` + +1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. + +```bash +WIDGET_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$WIDGET_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/efiz-widget-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + widget_write_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/efiz-widget-block.json + +# Ожидаемый receipt ID: FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E +# Ожидаемый хеш транзакции: CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf +``` + +2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/efiz-widget-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].transaction.actions[0].FunctionCall + | { + method_name, + widget_source_head: ( + .args + | @base64d + | fromjson + | .data["efiz.near"].widget["DonateNEARtoEfiz"][""] + | split("\n")[0:12] + ) + } + ) +}' /tmp/efiz-widget-transaction.json +``` + +Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `CUA61...` вызвала `social.near set` и пронесла в `args` настоящий исходник виджета `DonateNEARtoEfiz`. + +3. Завершите каноническим подтверждением текущего состояния через сырой RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/efiz-widget-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + finality: "final", + current_widget_head: ( + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:5] + ) +}' /tmp/efiz-widget-rpc.json +``` + +Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. + +**Зачем нужен следующий шаг?** + +Last-write block виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. + +### Проследить один расчёт NEAR Intents и показать, что именно произошло + +Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». + +**Цель** + +- Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. + +**Официальные ссылки** + +- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) +- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) +- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) + +Для живой трассировки ниже используйте этот фиксированный якорь расчёта из mainnet, зафиксированный **18 апреля 2026 года**: - хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` - аккаунт `signer` и `receiver`: `intents.near` - высота включающего блока: `194573310` -Публичных FastNear-поверхностей уже достаточно, чтобы восстановить многое: +Быстрая полезная модель здесь простая: + +- `intents.near` выполняет входную точку расчёта +- последующие receipt расходятся по контрактам, которые реально переводят или выводят активы +- журналы событий показывают, какие действия расчёта случились, через имена вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` + +Для этого конкретного расчёта короткий человеческий ответ уже неплохой: + +- `intents.near` вызвал `execute_intents` +- downstream receipt ушли в `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` +- трасса выдала события `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` + +Если нужен протокольный фон, базовая форма сопоставления здесь — это двухсторонний `token_diff` intent: одна сторона подписывает желаемую разницу по активам, вторая сторона подписывает противоположную разницу, а затем совпавшая пара отправляется на расчёт. Но для рабочего расследования обычно понятнее начать с одной реальной транзакции расчёта и читать доказательства прямо с цепочки. + +```mermaid +flowchart LR + T["Одна mainnet-транзакция
4cfei8p4..."] --> I["intents.near
execute_intents"] + I --> R["Последующие receipt"] + R --> C["Подключаются другие контракты"] + R --> E["Появляются журналы событий"] + E --> TD["token_diff"] + E --> IE["intents_executed"] + E --> MT["mt_transfer / mt_withdraw"] +``` + +Публичных FastNear-поверхностей уже достаточно, чтобы ответить на практический вопрос: | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Якорь расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и получаем саму транзакцию плюс список последующих receipt | Даёт читаемый каркас расчёта без необходимости сразу декодировать сырые receipt | -| Контекст включающего блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Помещает расчёт в контекст блока и показывает, какие receipt появились там | -| Канонический DAG по receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` | Даёт протокольно-канонический DAG, `executor_id` и сырые логи событий | -| Классификация событий | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Извлекаем имена событий вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` из строк `EVENT_JSON` | Позволяет объяснять расчёт по семействам событий, а не по непрозрачным `receipt_id` | +| Каркас расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и печатаем основную транзакцию плюс первые последующие receipt | Даёт самый быстрый читаемый ответ на вопрос «во что этот расчёт пошёл дальше?» | +| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Показывает, в какой блок попал расчёт и какие receipt этой транзакции видны в блоке | +| Каноническое доказательство по receipt и событиям | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` плюс логи `EVENT_JSON` | Даёт протокольно-канонический DAG, `executor_id` и имена событий, которые объясняют, что реально сделал расчёт | **Что должен включать полезный ответ** -- как концептуальная двухсторонняя модель `token_diff` отображается на реальный расчёт через `execute_intents` -- какие последующие контракты и методы появились после `intents.near` +- какую входную точку расчёта вы увидели на `intents.near` +- какие downstream-контракты и методы появились сразу после неё - какие семейства событий выпустила трассировка -- какие высоты блоков сформировали основной каскад +- одно итоговое предложение простым языком о том, что произошло Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. -### Shell-сценарий для живой трассировки NEAR Intents +### Shell-сценарий расчёта NEAR Intents Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. **Что вы делаете** -- Получаете историю транзакции через Transactions API. +- Получаете читаемую историю расчёта через Transactions API. - Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. -- Подтверждаете канонический DAG по receipt и семейства логов событий через `EXPERIMENTAL_tx_status`. +- Подтверждаете канонический DAG по receipt и семейства событий через `EXPERIMENTAL_tx_status`. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -601,7 +1089,7 @@ INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 INTENTS_SIGNER_ID=intents.near ``` -1. Начните с самой транзакции расчёта. +1. Начните с самой транзакции расчёта и восстановите первый читаемый поток последующих receipt. ```bash INTENTS_BLOCK_HASH="$( @@ -635,6 +1123,8 @@ jq '{ }' /tmp/intents-transaction.json ``` +Этот первый шаг уже даёт сильный операторский ответ: `intents.near` выполнил транзакцию расчёта, а ранние последующие receipt показывают, какие контракты вошли в каскад сразу после этого. + 2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. ```bash @@ -704,24 +1194,27 @@ jq -r ' **Зачем нужен следующий шаг?** -`POST /v0/transactions` даёт читаемый каркас расчёта. `POST /v0/block` показывает, как этот расчёт расположен внутри включающего блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и сырые логи событий, а не только индексированное резюме. +`POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. -### Shell-сценарий для pivot по receipt +### Shell-сценарий: от страшного receipt ID к человеческой истории -Используйте этот сценарий, когда у вас уже есть один `receipt_id` и нужен самый короткий путь обратно к читаемой истории транзакции. +Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. **Что вы делаете** - Сначала разрешаете receipt. - Извлекаете `receipt.transaction_hash` через `jq`. - Переиспользуете этот хеш транзакции в `POST /v0/transactions`. +- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID=YOUR_RECEIPT_ID -# Пример receipt ID из недавнего mainnet-перевода: -# RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +``` + +1. Разрешите receipt и поймите, что за объект вы смотрите. +```bash TX_HASH="$( curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ @@ -735,26 +1228,141 @@ jq '{ receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, + receipt_type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, transaction_hash: .receipt.transaction_hash, tx_block_height: .receipt.tx_block_height } }' /tmp/receipt-lookup.json +``` +2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. + +```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - receipt_count: (.transactions[0].receipts | length) - }' + | tee /tmp/receipt-parent-transaction.json >/dev/null + +jq '{ + transaction: { + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transfer_deposit_yocto: ( + .transactions[0].transaction.actions[0].Transfer.deposit // null + ) + }, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/receipt-parent-transaction.json ``` +3. Сведите это к одному человеческому предложению. + +```bash +jq -r ' + .transactions[0] as $tx + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." +' /tmp/receipt-parent-transaction.json +``` + +Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. + +В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. + **Зачем нужен следующий шаг?** -`POST /v0/receipt` даёт точку перехода. `POST /v0/transactions` превращает эту точку в читаемую историю с контекстом по отправителю, получателю, блоку и связанным receipt-ам. И только после этого обычно стоит расширяться до окон по блоку или аккаунту. +`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. + +## Частые задачи + +### Найти одну транзакцию + +**Начните здесь** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. + +**Следующая страница при необходимости** + +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. +- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. + +**Остановитесь, когда** + +- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. +- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. + +### Исследовать квитанцию + +**Начните здесь** + +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. +- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. + +**Остановитесь, когда** + +- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. + +**Переходите дальше, когда** + +- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. + +### Посмотреть недавнюю активность аккаунта + +**Начните здесь** + +- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. + +**Остановитесь, когда** + +- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. + +**Переходите дальше, когда** + +- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). +- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). + +### Восстановить ограниченное окно по блокам + +**Начните здесь** + +- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. +- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. + +**Остановитесь, когда** + +- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. + +**Переходите дальше, когда** + +- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). ## Частые ошибки diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index aa752a1..8022730 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -1,160 +1,457 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) -# Примеры Transactions API +## Готовые расследования -Используйте эту страницу, когда вопрос звучит как «что произошло?» и нужен индексированный исторический слой до того, как вы перейдёте к каноническому подтверждению через RPC. Начинайте с того идентификатора, который уже есть на руках, объясняйте историю исполнения в читаемом порядке и расширяйте её только тогда, когда действительно понадобятся точные RPC-семантики. +### Проследить асинхронную promise-цепочку и доказать порядок callback-ов -## Когда начинать здесь +Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» -- У вас уже есть хеш транзакции, ID квитанции, ID аккаунта или ограниченный диапазон блоков. -- Пользователю нужен исторический контекст исполнения, разбор для поддержки или отладки либо читаемая временная шкала. -- Нужна индексированная история без ручной сборки из сырых RPC-вызовов. -- Первый ответ должен объяснить, что произошло, до углубления в протокольные детали. +**Цель** -## Минимальные входные данные +- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. + +Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: + +- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом +- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке +- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и +- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору + +```mermaid +flowchart LR + Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] + H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] + R --> C["Async cross-contract callback-и"] + C --> B["Recorder state
beta"] + B --> A["Recorder state
alpha"] + A --> G["Recorder state
gamma"] + Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] + R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] + G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] +``` -- сеть: mainnet или testnet -- основной идентификатор: хеш транзакции, ID квитанции, `account_id` или блок/диапазон блоков -- расследуете ли вы один объект или целое окно истории -- требуется ли точное каноническое подтверждение через RPC до завершения ответа +Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. -## Частые задачи +Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. -### Найти одну транзакцию +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | +| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | +| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | +| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | +| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | +| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | +| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | +| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | -**Начните здесь** +**Что должен включать полезный ответ** -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. +- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» +- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования +- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался +- в каких блоках стали видны изменения состояния +- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования -**Следующая страница при необходимости** +### Превратить один страшный receipt ID из логов в понятную человеческую историю -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. -- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. +Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. -**Остановитесь, когда** +**Цель** -- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. +- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. -**Расширяйте, когда** +Для этого зафиксированного примера «страшный receipt ID из логов» такой: -- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. -- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. +- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` +- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- высота блока транзакции: `194263342` +- высота блока исполнения receipt: `194263343` -### Исследовать квитанцию +Человеческая история за этим receipt простая: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. -**Начните здесь** +```mermaid +flowchart LR + L["Один страшный receipt ID
5GhZcpfK..."] --> R["Ищем receipt"] + R --> T["Восстанавливаем tx hash
AdgNifPY..."] + T --> S["Читаем действия транзакции"] + S --> H["Человеческая история:
mike.near отправил 5 NEAR в global-counter.mike.near"] +``` -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и печатаем аккаунты, блок исполнения, флаг успеха и связанный хеш транзакции | Даёт самый короткий путь от сырого receipt ID к пониманию, что вообще за объект перед вами | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | +| Каноническое продолжение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | -**Следующая страница при необходимости** +**Что должен включать полезный ответ** -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. -- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. +- какие аккаунты создали и исполнили квитанцию +- к какой транзакции относится эта квитанция +- что транзакция на самом деле сделала +- была ли квитанция главным событием или только шагом в большом каскаде +- одно предложение простым языком, которое можно без правок вставить коллеге в чат -**Остановитесь, когда** +### Доказать, что одно неудачное действие сорвало весь пакет -- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. +Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? -**Расширяйте, когда** +В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. -- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. +**Цель** -### Посмотреть недавнюю активность аккаунта +- На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. -**Начните здесь** +**Официальные ссылки** -- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. +- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) +- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) + +Этот зафиксированный сбой был получен в **testnet 18 апреля 2026 года**: + +- хеш транзакции: `CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M` +- аккаунт signer: `temp.mike.testnet` +- целевой новый аккаунт: `rollback-mo4vmkig.temp.mike.testnet` +- высота включающего блока: `246365118` +- хеш включающего блока: `6f5zTKDqQRwrxMywzvxeRvYcCERJmAnatJaqUEtQYUNM` +- порядок действий: `CreateAccount -> Transfer -> AddKey -> FunctionCall` +- упавший метод: `definitely_missing_method` +- RPC-ошибка: `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet` + +```mermaid +flowchart LR + T["Одна подписанная транзакция"] --> A["CreateAccount"] + A --> B["Transfer 0.01 NEAR"] + B --> C["AddKey"] + C --> D["FunctionCall definitely_missing_method()"] + D --> E["Сбой: CodeDoesNotExist"] + E --> R["Весь пакет не закрепился"] + R --> N["Новый аккаунт не появился"] + R --> K["Новый ключ не закрепился"] + R --> F["У получателя нет профинансированного состояния"] +``` -**Следующая страница при необходимости** +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Задуманный пакет | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированный хеш транзакции и печатаем упорядоченный список действий, получателя и метаданные включающего блока | Показывает, что именно signer пытался сделать, ещё до разговора о том, что закрепилось | +| Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | +| Доказательство по состоянию после исполнения | RPC [`query(view_account)`](https://docs.fastnear.com/ru/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. +Перед shell-сценарием важно отметить одну деталь: индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. -**Остановитесь, когда** +**Что должен включать полезный ответ** -- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. +- точный порядок действий, который отправил signer +- какой индекс действия упал и почему +- высоту и хеш включающего блока для этого батча +- доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality +- короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` -**Расширяйте, когда** +### Shell-сценарий неудачной транзакции с пакетом действий -- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). -- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). +Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. -### Восстановить ограниченное окно по блокам +**Что вы делаете** -**Начните здесь** +- Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. +- Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. +- Через один RPC-запрос к состоянию после исполнения доказываете, что новый аккаунт так и не появился после finality. -- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. -- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. +```bash +TX_BASE_URL=https://tx.test.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M +SIGNER_ACCOUNT_ID=temp.mike.testnet +NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +``` -**Следующая страница при необходимости** +1. Получите транзакцию и распечатайте задуманный пакет действий. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/failed-batch-transaction.json >/dev/null -**Остановитесь, когда** +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height, + included_block_hash: .transactions[0].execution_outcome.block_hash + }, + batch: { + action_count: (.transactions[0].transaction.actions | length), + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + final_function_call_method_name: ( + .transactions[0].transaction.actions[3].FunctionCall.method_name + ) + }, + first_receipt_handoff: .transactions[0].transaction_outcome.outcome.status +}' /tmp/failed-batch-transaction.json + +# Ожидаемый порядок действий: +# 1. CreateAccount +# 2. Transfer +# 3. AddKey +# 4. FunctionCall +``` -- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. +2. Запросите RPC transaction status и посмотрите точную верхнеуровневую ошибку. -**Расширяйте, когда** +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/failed-batch-rpc-status.json >/dev/null -- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +jq '{ + final_execution_status: .result.final_execution_status, + failed_action_index: .result.status.Failure.ActionError.index, + failure: .result.status.Failure.ActionError.kind.FunctionCallError.CompilationError.CodeDoesNotExist +}' /tmp/failed-batch-rpc-status.json -## Готовые расследования +# Ожидаемый failed_action_index: 3 +# Ожидаемый failure account_id: rollback-mo4vmkig.temp.mike.testnet +``` -### Доказать порядок callback-ов в staged/release-сценарии +3. Запросите предполагаемый новый аккаунт после finality и докажите, что его всё ещё нет. -Используйте это расследование, когда сначала была стадия с асинхронной подготовкой работы, потом отдельный release, и нужно доказать не только успешность транзакций, но и конкретный порядок выполнения последующих обратных вызовов. +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/failed-batch-view-account.json >/dev/null -**Цель** +jq '{ + error: .error.cause.name, + message: .error.data, + requested_account_id: .error.cause.info.requested_account_id, + proof_block_height: .error.cause.info.block_height +}' /tmp/failed-batch-view-account.json -- Превратить два хеша транзакций в устойчивый артефакт для расследования, который включает граф квитанций, привязки к блокам и изменения состояния контракта. +# Ожидаемая ошибка: "UNKNOWN_ACCOUNT" +``` -В staged/release-сценариях именно stage-транзакция обычно остаётся главной опорной транзакцией расследования, потому что отложенные callback-и живут на её исходном дереве транзакции, а не на дереве release-транзакции. +Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Захват трассы stage и release | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш stage-транзакции и хеш release-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому дереву транзакции | -| Проверка материализации stage | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод staging-контракта, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления ожидаемых отложенных шагов | Подтверждает, что callback-и действительно стали доступны до того, как release-транзакция попытается их разбудить | -| Обогащение транзакций | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id` и индексированный статус исполнения | Даёт каждой транзакции устойчивую привязку к блоку, чтобы дальнейший анализ не зависел от памяти или ручных заметок | -| Снимки состояния контракта recorder | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до release, а затем опрашиваем его после release до появления ожидаемых записей | Доказывает реальный порядок последующих эффектов в состоянии контракта, а не только в метаданных дерева квитанций | -| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | -| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | -| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | -| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | +**Зачем нужен следующий шаг?** -**Что должен включать полезный ответ** +Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. -- почему именно stage-транзакция, а не release-транзакция, обычно является главной опорной транзакцией расследования -- какой порядок callback-ов вы наблюдали -- в каких блоках стали видны изменения состояния -- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования +### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? -### Начать с receipt ID и восстановить историю исполнения +Используйте это расследование, когда один вызов контракта залогировал успех, изменил своё локальное состояние, и даже верхнеуровневый RPC `status` выглядит успешным, но приложение всё равно сломалось, потому что позже упал отдельный cross-contract receipt. -Используйте это расследование, когда на руках есть только receipt ID из трассы, лога ошибки или дерева callback-ов и нужно вернуться к понятной человеку истории того, что произошло. +Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. **Цель** -- Перейти от одной квитанции к исходной транзакции, а затем расширить расследование ровно настолько, чтобы объяснить окружающее исполнение и эффекты в состоянии. +- Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. + +**Официальные ссылки** + +- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) +- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) + +Этот зафиксированный асинхронный сбой был получен в **testnet 18 апреля 2026 года**: + +- хеш транзакции: `AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz` +- аккаунт signer: `temp.mike.testnet` +- первый контракт-получатель: `seq-dr.mike.testnet` +- аккаунт detached-цели: `asyncfail-in2hwikn.temp.mike.testnet` +- блок включения транзакции: `246368568` +- успешная первая receipt: `6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS` в блоке `246368569` +- более поздняя упавшая receipt: `2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es` в блоке `246368570` +- первый метод: `kickoff_append` +- более поздний упавший метод: `append` +- верхнеуровневый RPC `status`: `SuccessValue` + +```mermaid +flowchart LR + T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] + R --> S["Роутер сохраняет локальное состояние
kicked += late-failure"] + R --> D["Detached cross-contract receipt
append(...)"] + D --> F["Более поздний сбой
CodeDoesNotExist"] + S -. "состояние из первой receipt всё равно закрепилось" .-> K["kicked() всё ещё содержит late-failure"] +``` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и определяем её содержимое, статус и связанный контекст транзакции | ID квитанции часто появляется в трассах и логах раньше, чем у человека складывается цельная история транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | После перехода из поиска квитанции забираем исходную транзакцию по хешу | Превращает одну квитанцию в читаемую историю исполнения с контекстом по получателю, блоку и статусу | -| Каноническое подтверждение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем результат на уровне протокола, когда индексированного вида недостаточно или нужны точные RPC-семантики | Полезно, когда важно различить индексированную интерпретацию и точное поведение RPC | -| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем содержащий блок и при необходимости расширяемся на соседние каскадные блоки, если исполнение растянулось по нескольким высотам | Помещает квитанцию во временную шкалу по блокам, которую проще объяснить | -| Окно активности аккаунта | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Забираем недавнюю активность аккаунтов, которых коснулась квитанция | Помогает связать квитанцию с окружающей историей по аккаунтам | -| Повторное чтение состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод с закреплённым `block_id`, если квитанция изменила видимое состояние контракта | Позволяет доказать, что квитанция не только существовала в метаданных, но и изменила устойчивое состояние контракта | +| Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | +| Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | +| Текущее состояние контракта | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `seq-dr.mike.testnet.kicked()` | Показывает, что локальное изменение состояния из первой receipt закрепилось, хотя более поздняя detached-receipt упала | + +Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не откатил более раннее изменение состояния роутера. **Что должен включать полезный ответ** -- какую исходную транзакцию вы восстановили из квитанции -- была ли квитанция главным событием или только одним шагом в большом каскаде -- какой минимальный контекст по блоку и аккаунтам нужен, чтобы её объяснить -- был ли эффект на состояние устойчивым и на какой высоте блока он стал видимым +- что подписанная транзакция успешно передала управление в первую router-receipt +- что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` +- что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` +- что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился +- одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции + +### Shell-сценарий более позднего сбоя receipt + +Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». + +**Что вы делаете** + +- Читаете транзакцию и её таймлайн receipt из индексированного представления. +- Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. +- Читаете текущее состояние роутера, чтобы показать: локальный побочный эффект первой receipt закрепился. + +```bash +TX_BASE_URL=https://tx.test.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz +SIGNER_ACCOUNT_ID=temp.mike.testnet +ROUTER_ACCOUNT_ID=seq-dr.mike.testnet +FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS +FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es +``` + +1. Получите транзакцию и распечатайте таймлайн receipt по порядку блоков. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/later-receipt-failure-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + tx_handoff: .transactions[0].transaction_outcome.outcome.status + }, + receipts: [ + .transactions[0].receipts[] + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + block_height: .execution_outcome.block_height, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system_transfer"), + status: .execution_outcome.outcome.status + } + ] +}' /tmp/later-receipt-failure-transaction.json + +# На что смотреть: +# - первая receipt контракта на seq-dr.mike.testnet успешно прошла в блоке 246368569 +# - более поздняя receipt append(...) упала в блоке 246368570 +``` + +2. Запросите RPC transaction status и сравните верхнеуровневую историю с более поздней упавшей receipt. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/later-receipt-failure-rpc.json >/dev/null + +jq \ + --arg first_receipt_id "$FIRST_RECEIPT_ID" \ + --arg failed_receipt_id "$FAILED_RECEIPT_ID" '{ + top_level_status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status, + first_contract_receipt: ( + .result.receipts_outcome[] + | select(.id == $first_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + logs: .outcome.logs, + status: .outcome.status + } + ), + later_failed_receipt: ( + .result.receipts_outcome[] + | select(.id == $failed_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + status: .outcome.status + } + ) + }' /tmp/later-receipt-failure-rpc.json + +# На что смотреть: +# - top_level_status всё ещё равен SuccessValue +# - первая receipt контракта залогировала dishonest_router:kickoff:late-failure +# - более поздняя receipt append(...) упала с CodeDoesNotExist +``` + +3. Прочитайте текущее состояние роутера и подтвердите, что локальный побочный эффект первой receipt закрепился. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ROUTER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "kicked", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/later-receipt-failure-kicked.json >/dev/null + +jq '{ + kicked: (.result.result | implode | fromjson), + contains_late_failure: ((.result.result | implode | fromjson) | index("late-failure") != null) +}' /tmp/later-receipt-failure-kicked.json +``` + +Этот последний read и есть практическое доказательство того, что локальное изменение из первой receipt закрепилось. Более поздняя упавшая receipt не откатила более ранний `kicked.push(...)` внутри роутера. + +**Зачем нужен следующий шаг?** + +Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. ### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB @@ -538,61 +835,252 @@ jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" ' NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. -### Понять двухстороннее сопоставление `token_diff`, а затем проследить живой расчёт NEAR Intents +### Какая транзакция записала `DonateNEARtoEfiz`? + +Используйте это расследование, когда история уже более лёгкая и даже немного шуточная: «на странице RPC-примеров я уже увидел, что `efiz.near/widget/DonateNEARtoEfiz` существует и что его last-write block — `92543301`. Какая именно транзакция записала этот виджет?» + +Это весёлый пример, но рецепт доказательства здесь вполне серьёзный и знакомый по другим SocialDB-расследованиям: -Используйте это расследование, когда история звучит так: «покажи, что именно NEAR Intents делает под капотом, но привяжи разбор к публичным данным, которые можно проверить самостоятельно». +- стартуем с одного блока, к которому привязан SocialDB-ключ +- превращаем этот блок в конкретный `efiz.near -> social.near` receipt +- восстанавливаем исходную транзакцию +- декодируем payload `set` и доказываем, что в нём действительно лежит исходник виджета **Цель** -- Сначала объяснить модель сопоставления, а затем превратить один реальный расчёт через `intents.near` в читаемую историю исполнения на базе Transactions API и канонического RPC. +- Превратить last-write block виджета в один читаемый ответ: какая транзакция записала `DonateNEARtoEfiz`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. **Официальные ссылки** -- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) -- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) -- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -#### Часть 1: анатомия протокола +Это расследование специально продолжает более лёгкий RPC-сценарий про виджеты. Для этого живого якоря: -Базовая форма сопоставления здесь — это `token_diff` intent. Одна сторона объявляет, какие активы она готова отдать и получить, а вторая сторона объявляет противоположную разницу. В официальной документации verifier двухсторонний обмен USDC и USDT показан как один подписанный intent со смыслом «я отдам `-10` USDC и получу `+10` USDT» и второй intent, который описывает обратную сторону сделки. Такие подписанные intent можно собрать через Message Bus или через любой другой внешний канал координации и затем отправить вместе в `intents.near`. +- аккаунт: `efiz.near` +- виджет: `DonateNEARtoEfiz` +- блок записи в SocialDB: `92543301` +- receipt ID: `FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E` +- хеш исходной транзакции: `CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf` +- внешний блок транзакции: `92543300` -Эта концептуальная часть полезна, чтобы понять сам протокол, но подписанные примеры в официальной документации носят иллюстративный и привязанный ко времени характер. Для рабочего FastNear-сценария полезнее разбирать один реальный расчёт из mainnet, чем делать вид, будто пример из документации является готовой живой транзакцией. +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `92543301` с `with_receipts: true`, а затем фильтруем его обратно до `efiz.near -> social.near` | Превращает last-write block виджета в один конкретный receipt и один конкретный хеш транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `DonateNEARtoEfiz` | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | + +**Что должен включать полезный ответ** -#### Часть 2: живая FastNear-трассировка +- высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции +- конкретный receipt ID и хеш исходной транзакции за этой записью виджета +- доказательство, что payload записи был `set` с `efiz.near/widget/DonateNEARtoEfiz` +- одно простое предложение вроде «`efiz.near` записал `DonateNEARtoEfiz` в транзакции `CUA61...`, и в payload действительно лежал исходник donation-виджета» -Для живой трассировки ниже используйте этот фиксированный якорь расчёта, зафиксированный **18 апреля 2026 года**: +### Shell-сценарий доказательства записи DonateNEARtoEfiz + +Используйте этот сценарий, когда хотите превратить один игривый блоковый якорь виджета в точную транзакцию, которая его записала. + +**Что вы делаете** + +- Стартуете с блока последней записи виджета. +- Переиспользуете эту высоту в FastNear block receipts, чтобы получить receipt и мост к транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы декодировать записанный исходник виджета. +- Завершаете сырым RPC-подтверждением, что виджет всё ещё существует сейчас. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=efiz.near +WIDGET_NAME=DonateNEARtoEfiz +WIDGET_BLOCK_HEIGHT=92543301 +``` + +1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. + +```bash +WIDGET_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$WIDGET_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/efiz-widget-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + widget_write_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/efiz-widget-block.json + +# Ожидаемый receipt ID: FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E +# Ожидаемый хеш транзакции: CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf +``` + +2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/efiz-widget-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].transaction.actions[0].FunctionCall + | { + method_name, + widget_source_head: ( + .args + | @base64d + | fromjson + | .data["efiz.near"].widget["DonateNEARtoEfiz"][""] + | split("\n")[0:12] + ) + } + ) +}' /tmp/efiz-widget-transaction.json +``` + +Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `CUA61...` вызвала `social.near set` и пронесла в `args` настоящий исходник виджета `DonateNEARtoEfiz`. + +3. Завершите каноническим подтверждением текущего состояния через сырой RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/efiz-widget-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + finality: "final", + current_widget_head: ( + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:5] + ) +}' /tmp/efiz-widget-rpc.json +``` + +Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. + +**Зачем нужен следующий шаг?** + +Last-write block виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. + +### Проследить один расчёт NEAR Intents и показать, что именно произошло + +Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». + +**Цель** + +- Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. + +**Официальные ссылки** + +- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) +- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) +- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) + +Для живой трассировки ниже используйте этот фиксированный якорь расчёта из mainnet, зафиксированный **18 апреля 2026 года**: - хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` - аккаунт `signer` и `receiver`: `intents.near` - высота включающего блока: `194573310` -Публичных FastNear-поверхностей уже достаточно, чтобы восстановить многое: +Быстрая полезная модель здесь простая: + +- `intents.near` выполняет входную точку расчёта +- последующие receipt расходятся по контрактам, которые реально переводят или выводят активы +- журналы событий показывают, какие действия расчёта случились, через имена вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` + +Для этого конкретного расчёта короткий человеческий ответ уже неплохой: + +- `intents.near` вызвал `execute_intents` +- downstream receipt ушли в `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` +- трасса выдала события `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` + +Если нужен протокольный фон, базовая форма сопоставления здесь — это двухсторонний `token_diff` intent: одна сторона подписывает желаемую разницу по активам, вторая сторона подписывает противоположную разницу, а затем совпавшая пара отправляется на расчёт. Но для рабочего расследования обычно понятнее начать с одной реальной транзакции расчёта и читать доказательства прямо с цепочки. + +```mermaid +flowchart LR + T["Одна mainnet-транзакция
4cfei8p4..."] --> I["intents.near
execute_intents"] + I --> R["Последующие receipt"] + R --> C["Подключаются другие контракты"] + R --> E["Появляются журналы событий"] + E --> TD["token_diff"] + E --> IE["intents_executed"] + E --> MT["mt_transfer / mt_withdraw"] +``` + +Публичных FastNear-поверхностей уже достаточно, чтобы ответить на практический вопрос: | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Якорь расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и получаем саму транзакцию плюс список последующих receipt | Даёт читаемый каркас расчёта без необходимости сразу декодировать сырые receipt | -| Контекст включающего блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Помещает расчёт в контекст блока и показывает, какие receipt появились там | -| Канонический DAG по receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` | Даёт протокольно-канонический DAG, `executor_id` и сырые логи событий | -| Классификация событий | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Извлекаем имена событий вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` из строк `EVENT_JSON` | Позволяет объяснять расчёт по семействам событий, а не по непрозрачным `receipt_id` | +| Каркас расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и печатаем основную транзакцию плюс первые последующие receipt | Даёт самый быстрый читаемый ответ на вопрос «во что этот расчёт пошёл дальше?» | +| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Показывает, в какой блок попал расчёт и какие receipt этой транзакции видны в блоке | +| Каноническое доказательство по receipt и событиям | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` плюс логи `EVENT_JSON` | Даёт протокольно-канонический DAG, `executor_id` и имена событий, которые объясняют, что реально сделал расчёт | **Что должен включать полезный ответ** -- как концептуальная двухсторонняя модель `token_diff` отображается на реальный расчёт через `execute_intents` -- какие последующие контракты и методы появились после `intents.near` +- какую входную точку расчёта вы увидели на `intents.near` +- какие downstream-контракты и методы появились сразу после неё - какие семейства событий выпустила трассировка -- какие высоты блоков сформировали основной каскад +- одно итоговое предложение простым языком о том, что произошло Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. -### Shell-сценарий для живой трассировки NEAR Intents +### Shell-сценарий расчёта NEAR Intents Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. **Что вы делаете** -- Получаете историю транзакции через Transactions API. +- Получаете читаемую историю расчёта через Transactions API. - Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. -- Подтверждаете канонический DAG по receipt и семейства логов событий через `EXPERIMENTAL_tx_status`. +- Подтверждаете канонический DAG по receipt и семейства событий через `EXPERIMENTAL_tx_status`. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -601,7 +1089,7 @@ INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 INTENTS_SIGNER_ID=intents.near ``` -1. Начните с самой транзакции расчёта. +1. Начните с самой транзакции расчёта и восстановите первый читаемый поток последующих receipt. ```bash INTENTS_BLOCK_HASH="$( @@ -635,6 +1123,8 @@ jq '{ }' /tmp/intents-transaction.json ``` +Этот первый шаг уже даёт сильный операторский ответ: `intents.near` выполнил транзакцию расчёта, а ранние последующие receipt показывают, какие контракты вошли в каскад сразу после этого. + 2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. ```bash @@ -704,24 +1194,27 @@ jq -r ' **Зачем нужен следующий шаг?** -`POST /v0/transactions` даёт читаемый каркас расчёта. `POST /v0/block` показывает, как этот расчёт расположен внутри включающего блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и сырые логи событий, а не только индексированное резюме. +`POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. -### Shell-сценарий для pivot по receipt +### Shell-сценарий: от страшного receipt ID к человеческой истории -Используйте этот сценарий, когда у вас уже есть один `receipt_id` и нужен самый короткий путь обратно к читаемой истории транзакции. +Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. **Что вы делаете** - Сначала разрешаете receipt. - Извлекаете `receipt.transaction_hash` через `jq`. - Переиспользуете этот хеш транзакции в `POST /v0/transactions`. +- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID=YOUR_RECEIPT_ID -# Пример receipt ID из недавнего mainnet-перевода: -# RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +``` + +1. Разрешите receipt и поймите, что за объект вы смотрите. +```bash TX_HASH="$( curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ @@ -735,26 +1228,141 @@ jq '{ receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, + receipt_type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, transaction_hash: .receipt.transaction_hash, tx_block_height: .receipt.tx_block_height } }' /tmp/receipt-lookup.json +``` +2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. + +```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - receipt_count: (.transactions[0].receipts | length) - }' + | tee /tmp/receipt-parent-transaction.json >/dev/null + +jq '{ + transaction: { + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transfer_deposit_yocto: ( + .transactions[0].transaction.actions[0].Transfer.deposit // null + ) + }, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/receipt-parent-transaction.json ``` +3. Сведите это к одному человеческому предложению. + +```bash +jq -r ' + .transactions[0] as $tx + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." +' /tmp/receipt-parent-transaction.json +``` + +Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. + +В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. + **Зачем нужен следующий шаг?** -`POST /v0/receipt` даёт точку перехода. `POST /v0/transactions` превращает эту точку в читаемую историю с контекстом по отправителю, получателю, блоку и связанным receipt-ам. И только после этого обычно стоит расширяться до окон по блоку или аккаунту. +`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. + +## Частые задачи + +### Найти одну транзакцию + +**Начните здесь** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. + +**Следующая страница при необходимости** + +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. +- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. + +**Остановитесь, когда** + +- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. +- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. + +### Исследовать квитанцию + +**Начните здесь** + +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. +- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. + +**Остановитесь, когда** + +- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. + +**Переходите дальше, когда** + +- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. + +### Посмотреть недавнюю активность аккаунта + +**Начните здесь** + +- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. + +**Остановитесь, когда** + +- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. + +**Переходите дальше, когда** + +- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). +- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). + +### Восстановить ограниченное окно по блокам + +**Начните здесь** + +- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. +- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. + +**Остановитесь, когда** + +- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. + +**Переходите дальше, когда** + +- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). ## Частые ошибки From de2fd8a97b207b108cd1336a0e1ddb62d9dab3ed Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 17:00:42 -0700 Subject: [PATCH 06/35] docs: polish example workflows --- docs/api/examples.md | 359 ++++---- docs/fastdata/kv/examples.md | 2 +- docs/neardata/examples.md | 2 +- docs/rpc/examples.md | 32 +- docs/snapshots/examples.mdx | 2 +- docs/tx/examples.md | 442 +++++---- .../current/api/examples.md | 361 ++++---- .../current/fastdata/kv/examples.md | 2 +- .../current/neardata/examples.md | 2 +- .../current/rpc/examples.md | 30 +- .../current/snapshots/examples.mdx | 2 +- .../current/tx/examples.md | 442 +++++---- static/ru/api/examples.md | 361 ++++---- static/ru/api/examples/index.md | 361 ++++---- static/ru/fastdata/kv/examples.md | 2 +- static/ru/fastdata/kv/examples/index.md | 2 +- static/ru/llms-full.txt | 861 +++++++++++------- static/ru/neardata/examples.md | 2 +- static/ru/neardata/examples/index.md | 2 +- static/ru/rpc/examples.md | 30 +- static/ru/rpc/examples/index.md | 30 +- static/ru/snapshots/examples.md | 2 +- static/ru/snapshots/examples/index.md | 2 +- static/ru/tx/examples.md | 442 +++++---- static/ru/tx/examples/index.md | 442 +++++---- 25 files changed, 2521 insertions(+), 1696 deletions(-) diff --git a/docs/api/examples.md b/docs/api/examples.md index 34e10e1..073e222 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -10,6 +10,8 @@ page_actions: ## Worked walkthroughs +These are ordered from the quickest read-only lookup to the more involved state-changing flow. + ### Resolve a public key, then fetch the account snapshot Use this when you have a public key first and the next user-facing question is “which account is this?” followed immediately by “what does that account look like right now?” @@ -50,125 +52,227 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ The public-key lookup tells you which account you are dealing with. The full account snapshot is the natural next read when you want balances, NFTs, staking, and pools in one response. If the key maps to multiple accounts instead of one, move to [V1 Public Key Lookup All](/api/v1/public-key-all) or loop through each returned `account_id`. -### Check collection membership, then mint a derivative NFT +### Am I locked or liquid? -Use this when the user story is “if this account already owns at least one NFT from collection X, mint one more NFT whose metadata records that relationship.” +Use this when the user story is “show me whether this wallet is exposed through direct staking pools, liquid staking tokens, or both.” **Network** -- testnet +- mainnet + +**Official references** + +- [Validator staking](https://docs.near.org/concepts/basics/staking) +- [Using liquid staking](https://docs.near.org/primitives/liquid-staking) + +This example is intentionally observational. It classifies what FastNear can see from staking positions and FT balances today. It does not prove every possible synthetic or off-platform staking exposure. + +**What you're doing** + +- Read indexed direct staking positions from the account staking endpoint. +- Read indexed FT balances from the account FT endpoint. +- Classify the account into `direct_only`, `liquid_only`, `mixed`, or `no_visible_staking_position`. +- Print the direct pool list and the liquid staking token list that informed the classification. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +``` + +1. Fetch the direct staking view. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' +``` + +2. Fetch fungible token balances so you can detect liquid staking positions. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` + +3. Classify the account from those two indexed views. + +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' +``` + +**Why this next step?** + +If the classification is `direct_only`, the next operational question is usually about unstake and withdraw timing. If it is `liquid_only`, the next question is usually about redeeming or swapping the liquid token. If it is `mixed`, you should treat those as two separate exit paths rather than assuming one flow covers both. + +### Archive a BOS widget version as a provenance NFT + +Use this when the user story is “this BOS widget is a real on-chain artifact. Mint an NFT that records exactly which version I archived.” + +**Networks** + +- mainnet for reading the widget from `social.near` +- testnet for safely minting the provenance NFT on `nft.examples.testnet` **Official references** - [Pre-deployed NFT contract](https://docs.near.org/tutorials/nfts/js/predeployed-contract) - [NEP-171 NFT standard](https://docs.near.org/primitives/nft/standard) - -Before you start, make sure the account already holds at least one token from `nft.examples.testnet`. If it does not, mint one first with the pre-deployed contract guide above and then come back to this workflow. +- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) **What you're doing** -- Use FastNear API to answer the gate question quickly. -- Switch to RPC `nft_tokens_for_owner` to recover exact token IDs and metadata from the source collection. -- Build deterministic derived metadata from that source set. -- Mint the derivative token, then verify it with the same NFT view method. +- Check whether the receiver already holds NFTs from the archive collection. +- Read one exact BOS widget from `social.near`, including its widget-level SocialDB block. +- Hash the widget source and turn that into provenance metadata. +- Mint a testnet NFT whose metadata records the author, widget path, SocialDB block, and source hash. +- Verify that the minted token still carries those provenance fields. + +Pinned source widget: + +- author account: `mob.near` +- widget path: `mob.near/widget/Profile` +- widget-level SocialDB block: `86494825` ```bash API_BASE_URL=https://test.api.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SOURCE_COLLECTION_ID=nft.examples.testnet +MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com +TESTNET_RPC_URL=https://rpc.testnet.fastnear.com +AUTHOR_ACCOUNT_ID=mob.near +WIDGET_NAME=Profile DESTINATION_COLLECTION_ID=nft.examples.testnet -SIGNER_ACCOUNT_ID="$ACCOUNT_ID" -TOKEN_ID="derivative-$(date +%s)" +RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Use FastNear API to check whether the account holds any NFT from the source collection. +1. Use FastNear API to see whether the receiver already holds NFTs from the archive collection. ```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ - | tee /tmp/testnet-account-nfts.json >/dev/null +curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ + | tee /tmp/provenance-account-nfts.json >/dev/null -jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - holds_collection: any(.tokens[]?; .contract_id == $source_collection_id), - matching_contracts: [ +jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ + existing_archive_tokens: [ .tokens[]? - | select(.contract_id == $source_collection_id) + | select(.contract_id == $destination_collection_id) + | { + contract_id, + token_id, + last_update_block_height + } ] -}' /tmp/testnet-account-nfts.json +}' /tmp/provenance-account-nfts.json ``` -2. Switch to RPC so you can recover exact token IDs and source metadata from that collection. +2. Read the exact widget body and widget-level SocialDB block from mainnet. ```bash -NFT_TOKENS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - from_index: "0", - limit: 50 +WIDGET_ARGS_BASE64="$( + jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($author_account_id + "/widget/" + $widget_name)], + options: {with_block_height: true} }' | base64 | tr -d '\n' )" -curl -s "$RPC_URL" \ +curl -s "$MAINNET_RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOURCE_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_tokens_for_owner", - args_base64: $args_base64, - finality: "final" - } - }')" \ + --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ | jq '.result.result | implode | fromjson' \ - | tee /tmp/source-collection-tokens.json >/dev/null - -jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - source_collection_id: $source_collection_id, - source_count: length, - source_token_ids: (map(.token_id) | sort | .[:5]) -}' /tmp/source-collection-tokens.json + | tee /tmp/bos-widget.json >/dev/null + +jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + widget_path: ($author_account_id + "/widget/" + $widget_name), + socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], + source_preview: ( + .[$author_account_id].widget[$widget_name][""] + | split("\n")[0:8] + ) +}' /tmp/bos-widget.json ``` -3. Build deterministic derivative metadata from that source set. +3. Hash the widget source and build deterministic provenance metadata. ```bash -DERIVATIVE_METADATA_JSON="$( - jq -c --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - title: ("Derivative witness for " + $source_collection_id), - description: - ("Minted because the holder currently owns " - + (length | tostring) - + " token(s) from " - + $source_collection_id), - media: ( - map(.metadata.media) - | map(select(. != null)) - | .[0] - ), - copies: 1, - extra: ({ - source_collection_id: $source_collection_id, - source_count: length, - source_token_ids: (map(.token_id) | sort | .[:5]) - } | @json) - }' /tmp/source-collection-tokens.json +jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][""] +' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx + +WIDGET_BLOCK_HEIGHT="$( + jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][":block"] + ' /tmp/bos-widget.json +)" + +SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" +SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" +TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" + +PROVENANCE_METADATA_JSON="$( + jq -nc \ + --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" \ + --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ + --arg block_height "$WIDGET_BLOCK_HEIGHT" \ + --arg source_sha256 "$SOURCE_SHA256" '{ + title: ("BOS widget archive: " + $widget_path), + description: ("Archived from social.near on mainnet at block " + $block_height), + copies: 1, + extra: ({ + author_account_id: $author_account_id, + widget_name: $widget_name, + widget_path: $widget_path, + source_contract_id: "social.near", + source_network: "mainnet", + socialdb_block_height: ($block_height | tonumber), + source_sha256: $source_sha256 + } | @json) + }' )" -printf '%s\n' "$DERIVATIVE_METADATA_JSON" | jq '.' +printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' ``` -4. Mint the derivative token on the destination collection. +4. Mint the provenance NFT on testnet. ```bash near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$ACCOUNT_ID" \ - --argjson metadata "$DERIVATIVE_METADATA_JSON" '{ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --argjson metadata "$PROVENANCE_METADATA_JSON" '{ token_id: $token_id, receiver_id: $receiver_id, metadata: $metadata @@ -178,123 +282,54 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Verify the new token with the same NFT view method. +5. Verify that the minted NFT carries the provenance fields you expect. Poll a few times instead of assuming failure if the token does not appear immediately after the mint transaction returns. ```bash +NFT_TOKEN_ARGS_BASE64="$( + jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ + | base64 | tr -d '\n' +)" + for attempt in 1 2 3 4 5; do - curl -s "$RPC_URL" \ + curl -s "$TESTNET_RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { request_type: "call_function", account_id: $account_id, - method_name: "nft_tokens_for_owner", + method_name: "nft_token", args_base64: $args_base64, finality: "final" } }')" \ | jq '.result.result | implode | fromjson' \ - | jq --arg token_id "$TOKEN_ID" ' - map(select(.token_id == $token_id)) - ' \ - | tee /tmp/derivative-token-verification.json >/dev/null + | tee /tmp/bos-widget-provenance-token.json >/dev/null - if jq -e 'length > 0' /tmp/derivative-token-verification.json >/dev/null; then + if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then break fi sleep 1 done -jq '.' /tmp/derivative-token-verification.json -``` - -**Why this next step?** - -FastNear API is the quick gate check. Once the account qualifies, RPC is the right next step because that is where you can read exact token IDs and call the collection's own NFT views. - -### Am I locked or liquid? - -Use this when the user story is “show me whether this wallet is exposed through direct staking pools, liquid staking tokens, or both.” - -**Network** - -- mainnet - -**Official references** - -- [Validator staking](https://docs.near.org/concepts/basics/staking) -- [Using liquid staking](https://docs.near.org/primitives/liquid-staking) - -This example is intentionally observational. It classifies what FastNear can see from staking positions and FT balances today. It does not prove every possible synthetic or off-platform staking exposure. - -**What you're doing** - -- Read indexed direct staking positions from the account staking endpoint. -- Read indexed FT balances from the account FT endpoint. -- Classify the account into `direct_only`, `liquid_only`, `mixed`, or `no_visible_staking_position`. -- Print the direct pool list and the liquid staking token list that informed the classification. - -```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -``` - -1. Fetch the direct staking view. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` - -2. Fetch fungible token balances so you can detect liquid staking positions. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null -``` - -3. Classify the account from those two indexed views. - -```bash -jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ - --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens - | { - classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) - }' +jq '{ + token_id, + owner_id, + title: .metadata.title, + provenance: (.metadata.extra | fromjson) +}' /tmp/bos-widget-provenance-token.json ``` **Why this next step?** -If the classification is `direct_only`, the next operational question is usually about unstake and withdraw timing. If it is `liquid_only`, the next question is usually about redeeming or swapping the liquid token. If it is `mixed`, you should treat those as two separate exit paths rather than assuming one flow covers both. +FastNear API gives you the quick receiver-side check. Mainnet RPC gives you the exact widget body and SocialDB block. Testnet minting turns that into a durable NFT record. If you later want to prove which historical transaction wrote the widget, hand off to the NEAR Social proof investigations on [Transactions API examples](/tx/examples). ## Common jobs diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index 256d3eb..d861e0b 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -31,7 +31,7 @@ Use this investigation when one contract storage key looks suspicious and you wa - the latest indexed value and what changed in history - whether `view_state` matched the indexed current value -### Shell walkthrough +### Exact key history shell walkthrough Use this when one fully qualified key is already known and you want to move cleanly from “what is the latest indexed row?” to “how did this exact key get here?” diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index e72ea29..a0cebd3 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -32,7 +32,7 @@ Use this investigation when you want to notice a new block as early as possible, - when the same observation became finalized - whether the exact RPC block changed the interpretation -### Shell walkthrough +### Finalized block follow-up shell walkthrough Use this when you want the helper route to pick the latest finalized block for you, but you still want to confirm the exact block in RPC. diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index 8626326..b24334d 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -748,11 +748,9 @@ If that final object says `ready_to_publish_now: true`, RPC has already answered This keeps the whole question on exact on-chain reads. `social.near` itself answers whether the target account has room left and whether a delegated signer is already allowed to write. That is a better NEAR Social readiness check than guessing from wallet state alone. -### Did `efiz.near` really publish `DonateNEARtoEfiz`, and what does it do? +### What does `mob.near/widget/Profile` actually contain right now? -Use this when the user story is lighter and more playful: “my friend says `efiz.near` once published a widget literally called `DonateNEARtoEfiz`. Check whether that is true, then show me what the widget actually does without leaving RPC.” - -This one is intentionally fun. It does not teach anything deep about async execution. It just shows how to use exact SocialDB reads to browse a BOS author's catalog and answer one very specific question from live on-chain data. +Use this when the question is simple: “show me the live source for `mob.near/widget/Profile`, tell me when that widget key was last written, and keep me on exact RPC reads.” **Official references** @@ -760,17 +758,17 @@ This one is intentionally fun. It does not teach anything deep about async execu **What you're doing** -- Ask `social.near` for the widget catalog under `efiz.near`. -- Keep the block heights, because they tell you when each widget key was last written. -- Confirm that `DonateNEARtoEfiz` is really there, then read its exact source code through the same contract. -- End with one clean handoff: if the next question becomes “which transaction wrote this widget?”, switch to the NEAR Social proof recipes in [Transactions Examples](/tx/examples). +- Ask `social.near` for the widget catalog under `mob.near`. +- Keep the block heights so you know when each widget key last changed. +- Confirm that `Profile` is really there, then read its exact source through the same contract. +- If the next question becomes “which transaction wrote this widget?”, switch to the NEAR Social proof recipes in [Transactions Examples](/tx/examples). ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=efiz.near -export WIDGET_NAME=DonateNEARtoEfiz +export ACCOUNT_ID=mob.near +export WIDGET_NAME=Profile ``` 1. List the widget catalog and keep the last-write block heights. @@ -808,7 +806,7 @@ jq --arg account_id "$ACCOUNT_ID" ' | .[$account_id].widget | to_entries | sort_by(.value * -1) - | map({ + | map({ widget_name: .key, last_write_block: .value }) @@ -816,9 +814,7 @@ jq --arg account_id "$ACCOUNT_ID" ' ' /tmp/social-widget-keys.json ``` -That gives you a compact BOS inventory. At the time of writing, `efiz.near` had a wonderfully eclectic widget catalog including names like `ReversedFeed`, `HelloWorld`, `PotlockDonateAll`, and `DonateNEARtoEfiz`, but the live query is the real source of truth. - -2. Confirm that `DonateNEARtoEfiz` is really in the catalog, then print the exact source stored in SocialDB. +2. Confirm that `Profile` is really in the catalog, then print the exact source stored in SocialDB. ```bash WIDGET_GET_ARGS_BASE64="$( @@ -859,8 +855,6 @@ jq -r \ ' /tmp/social-widget-source.json ``` -That prints the first 25 lines of the widget source so you can quickly tell what kind of component it is. In the live version at the time of writing, the source initializes `reciever: "efiz.near"` and builds a button that calls `donate` with the chosen amount. The widget name is not subtle. - 3. Pull the last-write block for the same widget so you keep one useful historical anchor. ```bash @@ -875,13 +869,11 @@ jq -r \ | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" ``` -At the time of writing, the live last-write block for `efiz.near/widget/DonateNEARtoEfiz` was `92543301`. - -If your next question becomes “which transaction wrote that version of the widget?”, keep that block height and switch to the NEAR Social proof workflows in [Transactions Examples](/tx/examples). +At the time of writing, the live last-write block for `mob.near/widget/Profile` was `86494825`. Keep that block if you later want to prove which transaction wrote this version. **Why this next step?** -This is a nice reminder that RPC can be fun, not just forensic. `keys` lets you browse a BOS author's catalog like a developer, and `get` lets you inspect the exact widget body that lives on chain. Sometimes the answer really is “yes, your friend did publish a widget called `DonateNEARtoEfiz`, and here is the code.” +Sometimes the right RPC answer is just: here is the widget, here is the live source, and here is the block height to keep if provenance matters later. ## Common jobs diff --git a/docs/snapshots/examples.mdx b/docs/snapshots/examples.mdx index 059f5a1..cfac0a2 100644 --- a/docs/snapshots/examples.mdx +++ b/docs/snapshots/examples.mdx @@ -33,7 +33,7 @@ Use this investigation when an operator says “I need this node back online” - where data should land on disk - whether the operator should stay in FastNear snapshot docs or move to general nearcore bootstrap docs -### Shell walkthrough +### Mainnet archival recovery shell walkthrough Use this when you have already decided that archival mainnet is the right path and now need the exact command sequence with one shared block anchor. diff --git a/docs/tx/examples.md b/docs/tx/examples.md index 09185a0..fd0f43f 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -10,61 +10,148 @@ page_actions: ## Worked investigations -### Trace an async promise chain and prove callback order +These are intentionally ordered from the simplest anchor to the richest forensic workflow: start with one tx hash, then one receipt, then failure and async patterns, and only after that move into deeper SocialDB and NEAR Intents investigations. -Use this investigation when one transaction creates promise work for later, a second transaction resumes it, and the real question is not “did both transactions succeed?” but “did the cross-contract callbacks actually run in the order I intended?” +### I have one transaction hash. What happened? + +Use this investigation when the user story is as plain as it gets: “someone pasted me one transaction hash. I just want to know whether it worked, what it did, and which block it landed in.” + +This is the beginner-to-intermediate on-ramp for the page. Before receipts, promise chains, or forensics, there is one simpler skill every NEAR engineer needs: turn a bare tx hash into one short human story. **Goal** -- Turn two transaction hashes into one readable proof story: what promise work was created, what order the resume call requested, and what order later showed up in downstream contract state. +- Start from one transaction hash and recover the shortest useful answer: signer, receiver, action type, included block, and whether the transaction handed off into a successful execution path. -If your codebase or helper scripts call this a “stage/release” or “yield/resume” flow, that is fine. For docs, the more useful mental model is simpler: +For this pinned example: -- **create promise work**: one transaction sets up deferred async work for later -- **resume promise work**: a later transaction asks the contract to continue that work in a requested order -- **trace the async path**: receipt trees show where the cross-contract callbacks actually ran -- **observe state**: downstream contract state shows what order became visible to users or integrators +- transaction hash: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- included block height: `194263342` +- first receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` + +The plain-English answer for this one is simple: `mike.near` submitted a single `Transfer` action to `global-counter.mike.near`, the transaction landed in block `194263342`, and the chain handed it off into one successful receipt. ```mermaid flowchart LR - Y["Tx 1
creates promise work"] --> H["Yielded promises become live
staged_calls_for(...)"] - H --> R["Tx 2
resumes promises in order beta -> alpha -> gamma"] - R --> C["Async cross-contract callbacks"] - C --> B["Recorder state
beta"] - B --> A["Recorder state
alpha"] - A --> G["Recorder state
gamma"] - Y -. "main receipt-tree evidence lives here" .-> D["Original promise DAG"] - R -. "requested order lives here" .-> P["Resume payload"] - G -. "observed order ends here" .-> O["Observed downstream order"] + H["One tx hash
AdgNifPY..."] --> T["Fetch transaction"] + T --> A["Read signer, receiver, actions, block"] + A --> S["Short human story"] + T -. "if needed later" .-> R["First receipt ID
5GhZcpfK..."] ``` -That distinction matters because a successful resume transaction still does not prove the observed order by itself. You also need evidence that the promised work was really live before resume, and evidence that downstream state changed in the same order the resume call requested. - -For NEAR engineers, the important mental model is: the resume transaction tells you the **requested order**, but the original promise transaction usually remains the primary forensic anchor because the resumed callbacks still live on that original async receipt tree. Downstream contract state is what lets you compare requested order with observed order. - | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | -| Promise-chain trace capture | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Query the original promise transaction hash and the later resume transaction hash with `wait_until: "FINAL"`, usually hot RPC first and archival RPC on `UNKNOWN_TRANSACTION` | The receipt DAG is the primary proof surface for callback order and tells you which receipts belong to which async transaction tree | -| Promise-readiness check | RPC [`query(call_function)`](/rpc/contract/call-function) | Poll the contract view that exposes deferred promise work, such as `staged_calls_for({ caller_id })`, with `finality: "final"` until the yielded promises appear | Confirms the promise work was really live before the resume transaction tried to continue it | -| Requested-order anchor | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch both transaction hashes to recover `block_height`, `block_hash`, `receiver_id`, indexed execution status, and the resume payload | Gives each transaction a durable block anchor and preserves the exact order the resume step requested | -| Downstream state snapshots | RPC [`query(call_function)`](/rpc/contract/call-function) | Read the downstream recorder state before resume, then poll it after resume until the expected entries appear | Proves actual callback order in contract state, not just metadata in the receipt tree | -| Receipt pivot | Transactions API [`POST /v0/receipt`](/tx/receipt) | Use any interesting yielded or downstream receipt ID to reconnect it to the originating transaction | Lets you move quickly from one receipt in the DAG back to the broader transaction story | -| Per-block reconstruction | Transactions API [`POST /v0/block`](/tx/block) | Fetch the included block and the cascade blocks with receipts enabled | Reconstructs the block-by-block execution timeline once you know which blocks matter | -| Account activity context | Transactions API [`POST /v0/account`](/tx/account) | Fetch function-call history for the contracts that participated in the cascade over the same window | Gives humans a simpler account-history view to compare against the trace | -| Block-pinned state replay | RPC [`query(call_function)`](/rpc/contract/call-function) | Re-run the recorder view with `block_id` pinned to the interesting heights | Turns final state into a time series so you can say when state changed, not just what it became | +| Readable transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Start from the tx hash and print signer, receiver, included block, action list, and first receipt handoff | Gives the fastest readable answer to “what did this tx do?” | +| Canonical status follow-up | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Reuse the same tx hash and signer only if you need exact protocol-native status semantics | Useful when the next question becomes “success according to RPC, exactly?” | +| Receipt handoff | Transactions API [`POST /v0/receipt`](/tx/receipt) | Reuse the first receipt ID if the next question turns into a receipt-level story | Provides the natural bridge to the next investigation when the transaction hash is no longer the best anchor | **What a useful answer should include** -- a one-sentence conclusion in plain language, such as “the first transaction created three deferred promises, the second transaction resumed them in order `beta -> alpha -> gamma`, and the recorder state later confirmed that same callback order” -- why the original promise transaction, not only the resume transaction, is usually the primary forensic anchor -- the requested callback order and the observed downstream effect order -- the blocks where the observable state changed -- any receipt or account pivots the next investigator should keep +- who signed the transaction +- which account received it +- which action type it carried +- which block included it +- one plain-English sentence that explains the transaction without receipt jargon + +### Transaction hash to human story shell walkthrough + +Use this when you want the shortest possible path from one tx hash to one readable answer. + +**What you're doing** + +- Fetch the transaction by hash and print the main story fields. +- Confirm the final status only if you need exact RPC semantics. +- Keep the first receipt ID only as the optional next step. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp +SIGNER_ACCOUNT_ID=mike.near +``` + +1. Fetch the transaction and print the basic story. + +```bash +FIRST_RECEIPT_ID="$( + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/basic-tx-story.json \ + | jq -r '.transactions[0].transaction_outcome.outcome.status.SuccessReceiptId' +)" + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/basic-tx-story.json + +# Expected action list: ["Transfer"] +# Expected first receipt ID: 5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq +``` + +2. If you need exact RPC status semantics, confirm them with `EXPERIMENTAL_tx_status`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' +``` + +3. If the next question becomes “what was that first receipt?”, pivot once and stop. + +```bash +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$FIRST_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, + transaction_hash: .receipt.transaction_hash + }' +``` + +That last step is optional on purpose. If all you wanted was the transaction story, the first step was enough. Keep going only when the receipt itself becomes the new anchor. + +**Why this next step?** + +`POST /v0/transactions` is the cleanest starting point when all you have is a tx hash and need one readable answer. RPC is the follow-up for exact status semantics. `POST /v0/receipt` is the handoff when the next question stops being about the transaction as a whole and starts being about one receipt inside it. ### Turn one ugly receipt ID from logs into a human story Use this investigation when all you have is one ugly `receipt_id` from logs, traces, or an error report, and you want to turn it into a plain-English answer a teammate can understand. +If you already have the transaction hash instead of the receipt ID, start with the simpler investigation just above and only drop down to this one when the receipt itself becomes the best anchor. + **Goal** - Start from one receipt ID and recover the shortest useful story: who created it, where it executed, which transaction spawned it, and what that transaction was actually trying to do. @@ -102,6 +189,90 @@ flowchart LR - whether the receipt was the main event or just one step in a larger cascade - one plain-English sentence that a teammate could read without decoding receipt jargon +### Ugly receipt ID to human story shell walkthrough + +Use this when you already have one raw `receipt_id` from logs and want to turn it into a readable explanation fast. + +**What you're doing** + +- Resolve the receipt first. +- Extract `receipt.transaction_hash` with `jq`. +- Reuse that transaction hash in `POST /v0/transactions`. +- Finish with one human summary you could paste into chat or a ticket. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +``` + +1. Resolve the receipt and figure out what object you are looking at. + +```bash +TX_HASH="$( + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | tee /tmp/receipt-lookup.json \ + | jq -r '.receipt.transaction_hash' +)" + +jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + receipt_type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, + transaction_hash: .receipt.transaction_hash, + tx_block_height: .receipt.tx_block_height + } +}' /tmp/receipt-lookup.json +``` + +2. Reuse the transaction hash and turn the receipt into a readable transaction story. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/receipt-parent-transaction.json >/dev/null + +jq '{ + transaction: { + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transfer_deposit_yocto: ( + .transactions[0].transaction.actions[0].Transfer.deposit // null + ) + }, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/receipt-parent-transaction.json +``` + +3. Turn that into one human sentence. + +```bash +jq -r ' + .transactions[0] as $tx + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) belongs to tx \($tx.transaction.hash): \($tx.transaction.signer_id) sent 5 NEAR to \($tx.transaction.receiver_id). The tx landed in block \($tx.execution_outcome.block_height), and the receipt executed successfully in block \($tx.receipts[0].execution_outcome.block_height)." +' /tmp/receipt-parent-transaction.json +``` + +For another receipt, keep the same pattern but change the final sentence to match the action types you just printed. + +That is the core trick: you do not need to explain every receipt field. You need to recover just enough context to say what the signer did, where the receipt executed, and whether this receipt was the main event or only one step in a bigger cascade. + +**Why this next step?** + +`POST /v0/receipt` tells you what the raw receipt is attached to. `POST /v0/transactions` tells you what the signer was actually trying to do. Once you have those two pieces together, you can usually explain the receipt in one sentence before deciding whether you really need block context, account history, or canonical RPC status. + ### Prove that one failed action reverted the whole batch Use this investigation when one transaction tried to create and fund a new account, add a key, and then call a method on that same new account. The final action failed because the fresh account had no contract code. The real question is simple: did the earlier actions stick, or did the whole batch revert? @@ -461,6 +632,57 @@ That last read is the practical proof that the first receipt's local state chang When a NEAR app “looked successful” and still broke later, the thing to ask is not just “what was the transaction status?” but “which receipt succeeded, and which later receipt failed?” This example gives you that exact split: indexed receipt timeline for the shape, RPC status for the exact semantics, and one contract-state read to prove the earlier side effect stuck. +### Trace an async promise chain and prove callback order + +Use this investigation when one transaction creates promise work for later, a second transaction resumes it, and the real question is not “did both transactions succeed?” but “did the cross-contract callbacks actually run in the order I intended?” + +**Goal** + +- Turn two transaction hashes into one readable proof story: what promise work was created, what order the resume call requested, and what order later showed up in downstream contract state. + +If your codebase or helper scripts call this a “stage/release” or “yield/resume” flow, that is fine. For docs, the more useful mental model is simpler: + +- **create promise work**: one transaction sets up deferred async work for later +- **resume promise work**: a later transaction asks the contract to continue that work in a requested order +- **trace the async path**: receipt trees show where the cross-contract callbacks actually ran +- **observe state**: downstream contract state shows what order became visible to users or integrators + +```mermaid +flowchart LR + Y["Tx 1
creates promise work"] --> H["Yielded promises become live
staged_calls_for(...)"] + H --> R["Tx 2
resumes promises in order beta -> alpha -> gamma"] + R --> C["Async cross-contract callbacks"] + C --> B["Recorder state
beta"] + B --> A["Recorder state
alpha"] + A --> G["Recorder state
gamma"] + Y -. "main receipt-tree evidence lives here" .-> D["Original promise DAG"] + R -. "requested order lives here" .-> P["Resume payload"] + G -. "observed order ends here" .-> O["Observed downstream order"] +``` + +That distinction matters because a successful resume transaction still does not prove the observed order by itself. You also need evidence that the promised work was really live before resume, and evidence that downstream state changed in the same order the resume call requested. + +For NEAR engineers, the important mental model is: the resume transaction tells you the **requested order**, but the original promise transaction usually remains the primary forensic anchor because the resumed callbacks still live on that original async receipt tree. Downstream contract state is what lets you compare requested order with observed order. + +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Promise-chain trace capture | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Query the original promise transaction hash and the later resume transaction hash with `wait_until: "FINAL"`, usually hot RPC first and archival RPC on `UNKNOWN_TRANSACTION` | The receipt DAG is the primary proof surface for callback order and tells you which receipts belong to which async transaction tree | +| Promise-readiness check | RPC [`query(call_function)`](/rpc/contract/call-function) | Poll the contract view that exposes deferred promise work, such as `staged_calls_for({ caller_id })`, with `finality: "final"` until the yielded promises appear | Confirms the promise work was really live before the resume transaction tried to continue it | +| Requested-order anchor | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch both transaction hashes to recover `block_height`, `block_hash`, `receiver_id`, indexed execution status, and the resume payload | Gives each transaction a durable block anchor and preserves the exact order the resume step requested | +| Downstream state snapshots | RPC [`query(call_function)`](/rpc/contract/call-function) | Read the downstream recorder state before resume, then poll it after resume until the expected entries appear | Proves actual callback order in contract state, not just metadata in the receipt tree | +| Receipt pivot | Transactions API [`POST /v0/receipt`](/tx/receipt) | Use any interesting yielded or downstream receipt ID to reconnect it to the originating transaction | Lets you move quickly from one receipt in the DAG back to the broader transaction story | +| Per-block reconstruction | Transactions API [`POST /v0/block`](/tx/block) | Fetch the included block and the cascade blocks with receipts enabled | Reconstructs the block-by-block execution timeline once you know which blocks matter | +| Account activity context | Transactions API [`POST /v0/account`](/tx/account) | Fetch function-call history for the contracts that participated in the cascade over the same window | Gives humans a simpler account-history view to compare against the trace | +| Block-pinned state replay | RPC [`query(call_function)`](/rpc/contract/call-function) | Re-run the recorder view with `block_id` pinned to the interesting heights | Turns final state into a time series so you can say when state changed, not just what it became | + +**What a useful answer should include** + +- a one-sentence conclusion in plain language, such as “the first transaction created three deferred promises, the second transaction resumed them in order `beta -> alpha -> gamma`, and the recorder state later confirmed that same callback order” +- why the original promise transaction, not only the resume transaction, is usually the primary forensic anchor +- the requested callback order and the observed downstream effect order +- the blocks where the observable state changed +- any receipt or account pivots the next investigator should keep + ### Prove that `mike.near` set `profile.name` to `Mike Purvis`, then recover the SocialDB profile write transaction Use this investigation when the user story is “I can see `Mike Purvis` on `mike.near`'s NEAR Social profile, but I want to prove exactly when that field was written and which transaction wrote it.” @@ -843,50 +1065,50 @@ That last step confirms the follow edge still exists now. The earlier NEAR Socia NEAR Social gives you the semantic edge. FastNear block receipts give you the bridge to a specific write. FastNear transaction lookup turns that write into a readable story. RPC gives you canonical current-state confirmation. -### Which transaction wrote `DonateNEARtoEfiz`? +### Which transaction wrote `mob.near/widget/Profile`? -Use this investigation when the user story is lighter and more playful: “the RPC examples page just showed me that `efiz.near/widget/DonateNEARtoEfiz` exists and that its last-write block is `92543301`. Which transaction actually wrote that widget?” +Use this investigation when the question is “I already know `mob.near/widget/Profile` exists right now. Which transaction wrote the widget version I am looking at?” -This one is fun, but the proof recipe is the same serious NEAR Social pattern: +This is the natural tx-side companion to the lighter RPC widget inspection and the provenance-NFT workflow. The job is straightforward: -- start from one SocialDB block anchor -- bridge that block to one `efiz.near -> social.near` receipt +- start from the widget's own SocialDB block +- turn that block into one `mob.near -> social.near` receipt - recover the originating transaction -- decode the `set` payload and prove it really stored the widget source +- decode the `set` payload and prove it really carried the widget source **Goal** -- Turn the widget's last-write block into one readable answer: which transaction wrote `DonateNEARtoEfiz`, which receipt executed the write, and what exact widget source was stored in that payload. +- Turn one widget-level SocialDB block into one readable answer: which transaction wrote `mob.near/widget/Profile`, which receipt executed the write, and what exact widget source appeared in that payload. **Official references** - [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) -This investigation picks up right where the playful RPC widget example leaves off. For this live anchor: +For this live anchor: -- account: `efiz.near` -- widget: `DonateNEARtoEfiz` -- SocialDB write block: `92543301` -- receipt ID: `FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E` -- originating transaction hash: `CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf` -- outer transaction block: `92543300` +- account: `mob.near` +- widget: `Profile` +- SocialDB write block: `86494825` +- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` +- originating transaction hash: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- outer transaction block: `86494824` | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | -| Block-to-receipt bridge | Transactions API [`POST /v0/block`](/tx/block) | Start from block `92543301` with `with_receipts: true`, then filter back down to `efiz.near -> social.near` | Turns the widget's last-write block into one concrete receipt and one concrete transaction hash | -| Transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the originating transaction and decode the `FunctionCall.args` payload | Proves the write was a `social.near set` call carrying the `DonateNEARtoEfiz` source code | +| Block-to-receipt bridge | Transactions API [`POST /v0/block`](/tx/block) | Start from block `86494825` with `with_receipts: true`, then filter back down to `mob.near -> social.near` | Turns the widget's write block into one concrete receipt and one concrete transaction hash | +| Transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the originating transaction and decode the `FunctionCall.args` payload | Proves the write was a `social.near set` call carrying the `mob.near/widget/Profile` source code | | Canonical current-state confirmation | RPC [`query(call_function)`](/rpc/contract/call-function) | Call `social.near get` directly at `final` for the same widget path | Confirms that the widget still exists now, even though the earlier steps already proved which historical transaction wrote it | **What a useful answer should include** - the write block height and why it is the receipt execution block, not the outer transaction block - the specific receipt ID and originating transaction hash behind the widget write -- proof that the write payload was a `set` call carrying `efiz.near/widget/DonateNEARtoEfiz` -- one plain-English sentence like “`efiz.near` wrote `DonateNEARtoEfiz` in tx `CUA61...`, and the payload really did store the donation widget source” +- proof that the write payload was a `set` call carrying `mob.near/widget/Profile` +- one plain-English sentence like “`mob.near` wrote `widget/Profile` in tx `9QDup...`, and the payload really did store the current profile widget source” -### DonateNEARtoEfiz write-proof shell walkthrough +### NEAR Social widget write-proof shell walkthrough -Use this when you want to turn one playful widget block anchor into the exact transaction that wrote it. +Use this when you want to turn one widget block anchor into the exact transaction that wrote it. **What you're doing** @@ -898,9 +1120,9 @@ Use this when you want to turn one playful widget block anchor into the exact tr ```bash TX_BASE_URL=https://tx.main.fastnear.com RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=efiz.near -WIDGET_NAME=DonateNEARtoEfiz -WIDGET_BLOCK_HEIGHT=92543301 +ACCOUNT_ID=mob.near +WIDGET_NAME=Profile +WIDGET_BLOCK_HEIGHT=86494825 ``` 1. Start from the widget's last-write block and recover the SocialDB receipt plus transaction hash. @@ -914,7 +1136,7 @@ WIDGET_TX_HASH="$( with_transactions: false, with_receipts: true }')" \ - | tee /tmp/efiz-widget-block.json \ + | tee /tmp/mob-widget-block.json \ | jq -r --arg account_id "$ACCOUNT_ID" ' first( .block_receipts[] @@ -936,10 +1158,10 @@ jq --arg account_id "$ACCOUNT_ID" '{ } ) ) -}' /tmp/efiz-widget-block.json +}' /tmp/mob-widget-block.json -# Expected receipt ID: FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E -# Expected transaction hash: CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf +# Expected receipt ID: CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6 +# Expected transaction hash: 9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia ``` 2. Reuse the transaction hash and decode the SocialDB `set` payload. @@ -948,7 +1170,7 @@ jq --arg account_id "$ACCOUNT_ID" '{ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/efiz-widget-transaction.json >/dev/null + | tee /tmp/mob-widget-transaction.json >/dev/null jq '{ transaction: { @@ -965,15 +1187,15 @@ jq '{ .args | @base64d | fromjson - | .data["efiz.near"].widget["DonateNEARtoEfiz"][""] + | .data["mob.near"].widget.Profile[""] | split("\n")[0:12] ) } ) -}' /tmp/efiz-widget-transaction.json +}' /tmp/mob-widget-transaction.json ``` -That second step is the payoff. You are no longer just saying “something in that block updated SocialDB.” You are proving that tx `CUA61...` called `social.near set` and carried the actual `DonateNEARtoEfiz` widget body in its args. +That second step is the payoff. You are no longer just saying “something in that block updated SocialDB.” You are proving that tx `9QDup...` called `social.near set` and carried the actual `mob.near/widget/Profile` widget body in its args. 3. Finish with canonical current-state confirmation via raw RPC. @@ -998,7 +1220,7 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/efiz-widget-rpc.json >/dev/null + | tee /tmp/mob-widget-rpc.json >/dev/null jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ finality: "final", @@ -1009,14 +1231,14 @@ jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ | .[$account_id].widget[$widget_name] | split("\n")[0:5] ) -}' /tmp/efiz-widget-rpc.json +}' /tmp/mob-widget-rpc.json ``` That last step confirms the widget still exists now. The earlier block and transaction steps are what proved which historical write created it. **Why this next step?** -The widget's last-write block gives you the bridge. FastNear block receipts turn that bridge into one receipt and one transaction hash. FastNear transaction lookup turns the hash into readable write proof. RPC then confirms that the widget still exists now. +The widget's write block gives you the bridge. FastNear block receipts turn that bridge into one receipt and one transaction hash. FastNear transaction lookup turns the hash into readable write proof. RPC then confirms that the widget still exists now. ### Trace one NEAR Intents settlement and show what actually happened @@ -1204,90 +1426,6 @@ jq -r ' `POST /v0/transactions` tells you what the settlement called into. `POST /v0/block` shows where that settlement landed in block context. `EXPERIMENTAL_tx_status` is the canonical follow-up when you need executor IDs, receipt-DAG structure, and raw event names to explain what actually happened. -### Ugly receipt ID to human story shell walkthrough - -Use this when you already have one raw `receipt_id` from logs and want to turn it into a readable explanation fast. - -**What you're doing** - -- Resolve the receipt first. -- Extract `receipt.transaction_hash` with `jq`. -- Reuse that transaction hash in `POST /v0/transactions`. -- Finish with one human summary you could paste into chat or a ticket. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' -``` - -1. Resolve the receipt and figure out what object you are looking at. - -```bash -TX_HASH="$( - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | tee /tmp/receipt-lookup.json \ - | jq -r '.receipt.transaction_hash' -)" - -jq '{ - receipt: { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - receipt_type: .receipt.receipt_type, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash, - tx_block_height: .receipt.tx_block_height - } -}' /tmp/receipt-lookup.json -``` - -2. Reuse the transaction hash and turn the receipt into a readable transaction story. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/receipt-parent-transaction.json >/dev/null - -jq '{ - transaction: { - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transfer_deposit_yocto: ( - .transactions[0].transaction.actions[0].Transfer.deposit // null - ) - }, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/receipt-parent-transaction.json -``` - -3. Turn that into one human sentence. - -```bash -jq -r ' - .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) belongs to tx \($tx.transaction.hash): \($tx.transaction.signer_id) sent 5 NEAR to \($tx.transaction.receiver_id). The tx landed in block \($tx.execution_outcome.block_height), and the receipt executed successfully in block \($tx.receipts[0].execution_outcome.block_height)." -' /tmp/receipt-parent-transaction.json -``` - -For another receipt, keep the same pattern but change the final sentence to match the action types you just printed. - -That is the core trick: you do not need to explain every receipt field. You need to recover just enough context to say what the signer did, where the receipt executed, and whether this receipt was the main event or only one step in a bigger cascade. - -**Why this next step?** - -`POST /v0/receipt` tells you what the raw receipt is attached to. `POST /v0/transactions` tells you what the signer was actually trying to do. Once you have those two pieces together, you can usually explain the receipt in one sentence before deciding whether you really need block context, account history, or canonical RPC status. - ## Common jobs ### Look up one transaction diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index 332119d..212f237 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -10,6 +10,8 @@ page_actions: ## Готовые сценарии +Эти сценарии выстроены от самого быстрого read-only-запроса к более насыщенному сценарию с изменением состояния. + ### Определить аккаунт по публичному ключу, а затем получить сводку по нему Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» @@ -50,125 +52,227 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. -### Проверить владение коллекцией, а затем выпустить производный NFT +### У меня обычный стейкинг или liquid staking? -Используйте этот сценарий, когда история звучит так: «если аккаунт уже владеет хотя бы одним NFT из коллекции X, выпустить ещё один NFT, в чьих метаданных будет зафиксирована эта связь». +Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». **Сеть** -- testnet +- mainnet + +**Официальные ссылки** + +- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) +- [Liquid staking](https://docs.near.org/primitives/liquid-staking) + +Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. + +**Что вы делаете** + +- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. +- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. +- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. +- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +``` + +1. Получите представление по прямому стейкингу. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' +``` + +2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` + +3. Классифицируйте аккаунт на основе этих двух индексированных представлений. + +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' +``` + +**Зачем нужен следующий шаг?** + +Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. + +### Заархивировать версию BOS-виджета как provenance NFT + +Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». + +**Сети** + +- mainnet для чтения виджета из `social.near` +- testnet для безопасного mint provenance NFT в `nft.examples.testnet` **Официальные ссылки** - [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) - [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) - -Перед началом убедитесь, что аккаунт уже владеет хотя бы одним токеном из `nft.examples.testnet`. Если такого токена ещё нет, сначала выпустите его по гайду с предразвёрнутым контрактом, а затем вернитесь к этому сценарию. +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) **Что вы делаете** -- Используете FastNear API, чтобы быстро ответить на вопрос о допуске. -- Расширяетесь до RPC `nft_tokens_for_owner`, чтобы получить точные `token_id` и метаданные из исходной коллекции. -- Строите детерминированные производные метаданные на основе этого набора токенов. -- Выпускаете производный токен и затем подтверждаете его тем же view-методом NFT. +- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. +- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. +- Хешируете исходник виджета и превращаете его в provenance-метаданные. +- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. +- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. + +Зафиксированный исходный виджет: + +- аккаунт автора: `mob.near` +- путь виджета: `mob.near/widget/Profile` +- SocialDB-блок уровня виджета: `86494825` ```bash API_BASE_URL=https://test.api.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SOURCE_COLLECTION_ID=nft.examples.testnet +MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com +TESTNET_RPC_URL=https://rpc.testnet.fastnear.com +AUTHOR_ACCOUNT_ID=mob.near +WIDGET_NAME=Profile DESTINATION_COLLECTION_ID=nft.examples.testnet -SIGNER_ACCOUNT_ID="$ACCOUNT_ID" -TOKEN_ID="derivative-$(date +%s)" +RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Через FastNear API проверьте, есть ли у аккаунта хоть один NFT из исходной коллекции. +1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. ```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ - | tee /tmp/testnet-account-nfts.json >/dev/null +curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ + | tee /tmp/provenance-account-nfts.json >/dev/null -jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - holds_collection: any(.tokens[]?; .contract_id == $source_collection_id), - matching_contracts: [ +jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ + existing_archive_tokens: [ .tokens[]? - | select(.contract_id == $source_collection_id) + | select(.contract_id == $destination_collection_id) + | { + contract_id, + token_id, + last_update_block_height + } ] -}' /tmp/testnet-account-nfts.json +}' /tmp/provenance-account-nfts.json ``` -2. Перейдите к RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. +2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. ```bash -NFT_TOKENS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - from_index: "0", - limit: 50 +WIDGET_ARGS_BASE64="$( + jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($author_account_id + "/widget/" + $widget_name)], + options: {with_block_height: true} }' | base64 | tr -d '\n' )" -curl -s "$RPC_URL" \ +curl -s "$MAINNET_RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOURCE_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_tokens_for_owner", - args_base64: $args_base64, - finality: "final" - } - }')" \ + --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ | jq '.result.result | implode | fromjson' \ - | tee /tmp/source-collection-tokens.json >/dev/null - -jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - source_collection_id: $source_collection_id, - source_count: length, - source_token_ids: (map(.token_id) | sort | .[:5]) -}' /tmp/source-collection-tokens.json + | tee /tmp/bos-widget.json >/dev/null + +jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + widget_path: ($author_account_id + "/widget/" + $widget_name), + socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], + source_preview: ( + .[$author_account_id].widget[$widget_name][""] + | split("\n")[0:8] + ) +}' /tmp/bos-widget.json ``` -3. Постройте детерминированные производные метаданные из этого набора токенов. +3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. ```bash -DERIVATIVE_METADATA_JSON="$( - jq -c --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - title: ("Derivative witness for " + $source_collection_id), - description: - ("Minted because the holder currently owns " - + (length | tostring) - + " token(s) from " - + $source_collection_id), - media: ( - map(.metadata.media) - | map(select(. != null)) - | .[0] - ), - copies: 1, - extra: ({ - source_collection_id: $source_collection_id, - source_count: length, - source_token_ids: (map(.token_id) | sort | .[:5]) - } | @json) - }' /tmp/source-collection-tokens.json +jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][""] +' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx + +WIDGET_BLOCK_HEIGHT="$( + jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][":block"] + ' /tmp/bos-widget.json +)" + +SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" +SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" +TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" + +PROVENANCE_METADATA_JSON="$( + jq -nc \ + --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" \ + --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ + --arg block_height "$WIDGET_BLOCK_HEIGHT" \ + --arg source_sha256 "$SOURCE_SHA256" '{ + title: ("BOS widget archive: " + $widget_path), + description: ("Archived from social.near on mainnet at block " + $block_height), + copies: 1, + extra: ({ + author_account_id: $author_account_id, + widget_name: $widget_name, + widget_path: $widget_path, + source_contract_id: "social.near", + source_network: "mainnet", + socialdb_block_height: ($block_height | tonumber), + source_sha256: $source_sha256 + } | @json) + }' )" -printf '%s\n' "$DERIVATIVE_METADATA_JSON" | jq '.' +printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' ``` -4. Выпустите производный токен в целевой коллекции. +4. Выпустите provenance NFT в testnet. ```bash near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$ACCOUNT_ID" \ - --argjson metadata "$DERIVATIVE_METADATA_JSON" '{ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --argjson metadata "$PROVENANCE_METADATA_JSON" '{ token_id: $token_id, receiver_id: $receiver_id, metadata: $metadata @@ -178,123 +282,54 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Подтвердите новый токен тем же NFT view-методом. +5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. -Если сразу после возврата mint-транзакции токен ещё не виден, не считайте это ошибкой сразу же: опросите view-метод несколько раз. +Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. ```bash +NFT_TOKEN_ARGS_BASE64="$( + jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ + | base64 | tr -d '\n' +)" + for attempt in 1 2 3 4 5; do - curl -s "$RPC_URL" \ + curl -s "$TESTNET_RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { request_type: "call_function", account_id: $account_id, - method_name: "nft_tokens_for_owner", + method_name: "nft_token", args_base64: $args_base64, finality: "final" } }')" \ | jq '.result.result | implode | fromjson' \ - | jq --arg token_id "$TOKEN_ID" ' - map(select(.token_id == $token_id)) - ' \ - | tee /tmp/derivative-token-verification.json >/dev/null + | tee /tmp/bos-widget-provenance-token.json >/dev/null - if jq -e 'length > 0' /tmp/derivative-token-verification.json >/dev/null; then + if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then break fi sleep 1 done -jq '.' /tmp/derivative-token-verification.json -``` - -**Зачем нужен следующий шаг?** - -FastNear API — это быстрый ответ на вопрос о допуске. Как только аккаунт проходит условие, правильным следующим шагом становится RPC, потому что именно там видны точные `token_id` и собственные NFT view-методы коллекции. - -### У меня обычный стейкинг или liquid staking? - -Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». - -**Сеть** - -- mainnet - -**Официальные ссылки** - -- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) -- [Liquid staking](https://docs.near.org/primitives/liquid-staking) - -Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. - -**Что вы делаете** - -- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. -- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. -- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. - -```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -``` - -1. Получите представление по прямому стейкингу. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` - -2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null -``` - -3. Классифицируйте аккаунт на основе этих двух индексированных представлений. - -```bash -jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ - --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens - | { - classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) - }' +jq '{ + token_id, + owner_id, + title: .metadata.title, + provenance: (.metadata.extra | fromjson) +}' /tmp/bos-widget-provenance-token.json ``` **Зачем нужен следующий шаг?** -Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. +FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](/tx/examples). ## Частые задачи diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index 70900fa..fcea83f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -31,7 +31,7 @@ page_actions: - как выглядит последнее индексированное значение и какие изменения видны в истории - совпал ли `view_state` с текущим индексированным значением -### Shell-сценарий +### Shell-сценарий истории точного ключа Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index 99fb2be..c626749 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -32,7 +32,7 @@ page_actions: - когда то же наблюдение стало финализированным - изменил ли точный разбор через RPC интерпретацию -### Shell-сценарий +### Shell-сценарий проверки финализированного блока Используйте этот сценарий, когда вспомогательный маршрут сам выбирает для вас последний финализированный блок, но следующий шаг всё равно требует точной проверки через RPC. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index efcb769..55c8823 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -748,11 +748,9 @@ jq -n \ Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. -### Правда ли, что `efiz.near` опубликовал `DonateNEARtoEfiz`, и что этот виджет делает? +### Что прямо сейчас содержит `mob.near/widget/Profile`? -Используйте этот сценарий, когда история более лёгкая и даже немного шуточная: «друг говорит, что `efiz.near` когда-то опубликовал виджет буквально с именем `DonateNEARtoEfiz`. Проверь это и покажи, что именно делает этот виджет, не выходя из RPC». - -Этот пример намеренно сделан для удовольствия. Он не учит ничему особенно глубокому про async-исполнение. Он просто показывает, как точными чтениями SocialDB просматривать каталог BOS-автора и отвечать на один очень конкретный вопрос по живым on-chain-данным. +Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». **Официальные ссылки** @@ -760,17 +758,17 @@ jq -n \ **Что вы делаете** -- Спрашиваете у `social.near` каталог виджетов под `efiz.near`. -- Сохраняете высоты блоков, потому что они показывают, когда каждый ключ виджета последний раз переписывался. -- Подтверждаете, что `DonateNEARtoEfiz` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. -- Заканчиваете простым handoff: если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](/tx/examples). +- Спрашиваете у `social.near` каталог виджетов под `mob.near`. +- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. +- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. +- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](/tx/examples). ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=efiz.near -export WIDGET_NAME=DonateNEARtoEfiz +export ACCOUNT_ID=mob.near +export WIDGET_NAME=Profile ``` 1. Получите каталог виджетов и сохраните высоты блоков последней записи. @@ -816,9 +814,7 @@ jq --arg account_id "$ACCOUNT_ID" ' ' /tmp/social-widget-keys.json ``` -Это даёт компактный BOS-каталог. На момент написания у `efiz.near` был удивительно эклектичный набор виджетов, включая `ReversedFeed`, `HelloWorld`, `PotlockDonateAll` и `DonateNEARtoEfiz`, но настоящий источник истины здесь — именно живой запрос. - -2. Подтвердите, что `DonateNEARtoEfiz` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. +2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. ```bash WIDGET_GET_ARGS_BASE64="$( @@ -859,8 +855,6 @@ jq -r \ ' /tmp/social-widget-source.json ``` -Так вы печатаете первые 25 строк исходника и быстро понимаете, что это вообще за компонент. В живой версии на момент написания код инициализирует `reciever: "efiz.near"` и строит кнопку, которая вызывает `donate` на выбранную сумму. Название виджета честно предупреждает, что будет дальше. - 3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. ```bash @@ -875,13 +869,11 @@ jq -r \ | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" ``` -На момент написания живая высота последней записи для `efiz.near/widget/DonateNEARtoEfiz` была `92543301`. - -Если следующий вопрос уже меняется на «какая транзакция записала именно эту версию виджета?», сохраните эту высоту блока и переходите к сценариям-доказательствам NEAR Social в [Transactions Examples](/tx/examples). +На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. **Зачем нужен следующий шаг?** -Это хорошее напоминание, что RPC может быть не только судебной экспертизой. `keys` позволяет просматривать каталог BOS-автора как разработчику, а `get` даёт возможность заглянуть в точное тело виджета, которое реально лежит on-chain. Иногда ответ и правда такой: «да, ваш друг действительно опубликовал виджет с именем `DonateNEARtoEfiz`, и вот его код». +Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. ## Частые задачи diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx index 0bcc619..db5e491 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx @@ -33,7 +33,7 @@ page_actions: - куда на диске должны попасть данные - должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap -### Shell-сценарий +### Shell-сценарий архивного восстановления mainnet Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index b9892af..07ab0af 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -10,61 +10,148 @@ page_actions: ## Готовые расследования -### Проследить асинхронную promise-цепочку и доказать порядок callback-ов +Эти расследования намеренно выстроены от самого простого якоря к самой насыщенной форензике: сначала один tx hash, затем один receipt, затем паттерны с ошибками и async, и только потом более глубокие расследования по SocialDB и NEAR Intents. -Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» +### У меня есть один хеш транзакции. Что вообще произошло? + +Используйте это расследование, когда история максимально простая: «мне прислали один хеш транзакции. Я просто хочу понять, сработала ли она, что именно сделала и в какой блок попала». + +Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. **Цель** -- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. +- Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. -Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: +Для этого зафиксированного примера: -- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом -- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке -- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и -- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору +- хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- высота включающего блока: `194263342` +- ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` + +Простой человеческий ответ для этого случая такой: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. ```mermaid flowchart LR - Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] - H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] - R --> C["Async cross-contract callback-и"] - C --> B["Recorder state
beta"] - B --> A["Recorder state
alpha"] - A --> G["Recorder state
gamma"] - Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] - R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] - G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] + H["Один tx hash
AdgNifPY..."] --> T["Получаем транзакцию"] + T --> A["Читаем signer, receiver, actions, block"] + A --> S["Короткая человеческая история"] + T -. "если потом понадобится" .-> R["Первая receipt
5GhZcpfK..."] ``` -Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. - -Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. - | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | -| Проверка готовности promise-работы | RPC [`query(call_function)`](/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | -| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | -| Снимки downstream-состояния | RPC [`query(call_function)`](/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | -| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | -| Реконструкция по блокам | Transactions API [`POST /v0/block`](/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | -| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | -| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | +| Читаемая история транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Стартуем с хеша транзакции и печатаем signer, receiver, включающий блок, список действий и handoff в первую receipt | Даёт самый быстрый читаемый ответ на вопрос «что вообще сделала эта транзакция?» | +| Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | +| Переход к receipt | Transactions API [`POST /v0/receipt`](/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | **Что должен включать полезный ответ** -- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» -- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования -- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался -- в каких блоках стали видны изменения состояния -- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования +- кто подписал транзакцию +- какой аккаунт её получил +- какой тип действия она несла +- в какой блок попала +- одно простое предложение, которое объясняет транзакцию без receipt-жаргона + +### Shell-сценарий: от хеша транзакции к человеческой истории + +Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. + +**Что вы делаете** + +- Получаете транзакцию по хешу и печатаете её основные поля. +- Подтверждаете финальный статус только если нужны точные RPC-семантики. +- Сохраняете первую receipt только как необязательный следующий шаг. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp +SIGNER_ACCOUNT_ID=mike.near +``` + +1. Получите транзакцию и распечатайте базовую историю. + +```bash +FIRST_RECEIPT_ID="$( + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/basic-tx-story.json \ + | jq -r '.transactions[0].transaction_outcome.outcome.status.SuccessReceiptId' +)" + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/basic-tx-story.json + +# Ожидаемый список действий: ["Transfer"] +# Ожидаемая первая receipt: 5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq +``` + +2. Если нужны точные RPC-семантики статуса, подтвердите их через `EXPERIMENTAL_tx_status`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' +``` + +3. Если следующий вопрос уже звучит как «что это была за первая receipt?», один раз перейдите по ней и остановитесь. + +```bash +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$FIRST_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, + transaction_hash: .receipt.transaction_hash + }' +``` + +Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. + +**Зачем нужен следующий шаг?** + +`POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. ### Превратить один страшный receipt ID из логов в понятную человеческую историю Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. +Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. + **Цель** - Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. @@ -102,6 +189,90 @@ flowchart LR - была ли квитанция главным событием или только шагом в большом каскаде - одно предложение простым языком, которое можно без правок вставить коллеге в чат +### Shell-сценарий: от страшного receipt ID к человеческой истории + +Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. + +**Что вы делаете** + +- Сначала разрешаете receipt. +- Извлекаете `receipt.transaction_hash` через `jq`. +- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. +- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +``` + +1. Разрешите receipt и поймите, что за объект вы смотрите. + +```bash +TX_HASH="$( + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | tee /tmp/receipt-lookup.json \ + | jq -r '.receipt.transaction_hash' +)" + +jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + receipt_type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, + transaction_hash: .receipt.transaction_hash, + tx_block_height: .receipt.tx_block_height + } +}' /tmp/receipt-lookup.json +``` + +2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/receipt-parent-transaction.json >/dev/null + +jq '{ + transaction: { + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transfer_deposit_yocto: ( + .transactions[0].transaction.actions[0].Transfer.deposit // null + ) + }, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/receipt-parent-transaction.json +``` + +3. Сведите это к одному человеческому предложению. + +```bash +jq -r ' + .transactions[0] as $tx + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." +' /tmp/receipt-parent-transaction.json +``` + +Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. + +В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. + +**Зачем нужен следующий шаг?** + +`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. + ### Доказать, что одно неудачное действие сорвало весь пакет Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? @@ -461,6 +632,57 @@ jq '{ Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. +### Проследить асинхронную promise-цепочку и доказать порядок callback-ов + +Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» + +**Цель** + +- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. + +Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: + +- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом +- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке +- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и +- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору + +```mermaid +flowchart LR + Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] + H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] + R --> C["Async cross-contract callback-и"] + C --> B["Recorder state
beta"] + B --> A["Recorder state
alpha"] + A --> G["Recorder state
gamma"] + Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] + R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] + G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] +``` + +Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. + +Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | +| Проверка готовности promise-работы | RPC [`query(call_function)`](/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | +| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | +| Снимки downstream-состояния | RPC [`query(call_function)`](/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | +| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | +| Реконструкция по блокам | Transactions API [`POST /v0/block`](/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | +| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | +| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | + +**Что должен включать полезный ответ** + +- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» +- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования +- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался +- в каких блоках стали видны изменения состояния +- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования + ### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». @@ -843,50 +1065,50 @@ jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" ' NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. -### Какая транзакция записала `DonateNEARtoEfiz`? +### Какая транзакция записала `mob.near/widget/Profile`? -Используйте это расследование, когда история уже более лёгкая и даже немного шуточная: «на странице RPC-примеров я уже увидел, что `efiz.near/widget/DonateNEARtoEfiz` существует и что его last-write block — `92543301`. Какая именно транзакция записала этот виджет?» +Используйте это расследование, когда вопрос звучит так: «я уже знаю, что `mob.near/widget/Profile` существует прямо сейчас. Какая именно транзакция записала ту версию виджета, на которую я смотрю?» -Это весёлый пример, но рецепт доказательства здесь вполне серьёзный и знакомый по другим SocialDB-расследованиям: +Это естественное tx-продолжение к более лёгкому RPC-сценарию про виджет и к provenance-NFT-сценарию. Задача здесь прямолинейная: -- стартуем с одного блока, к которому привязан SocialDB-ключ -- превращаем этот блок в конкретный `efiz.near -> social.near` receipt +- стартуем с собственного SocialDB-блока виджета +- превращаем этот блок в один `mob.near -> social.near` receipt - восстанавливаем исходную транзакцию -- декодируем payload `set` и доказываем, что в нём действительно лежит исходник виджета +- декодируем payload `set` и доказываем, что он действительно нёс исходник виджета **Цель** -- Превратить last-write block виджета в один читаемый ответ: какая транзакция записала `DonateNEARtoEfiz`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. +- Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. **Официальные ссылки** - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -Это расследование специально продолжает более лёгкий RPC-сценарий про виджеты. Для этого живого якоря: +Для этого живого якоря: -- аккаунт: `efiz.near` -- виджет: `DonateNEARtoEfiz` -- блок записи в SocialDB: `92543301` -- receipt ID: `FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E` -- хеш исходной транзакции: `CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf` -- внешний блок транзакции: `92543300` +- аккаунт: `mob.near` +- виджет: `Profile` +- блок записи в SocialDB: `86494825` +- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` +- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- внешний блок транзакции: `86494824` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Мост от блока к receipt | Transactions API [`POST /v0/block`](/tx/block) | Берём блок `92543301` с `with_receipts: true`, а затем фильтруем его обратно до `efiz.near -> social.near` | Превращает last-write block виджета в один конкретный receipt и один конкретный хеш транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `DonateNEARtoEfiz` | +| Мост от блока к receipt | Transactions API [`POST /v0/block`](/tx/block) | Берём блок `86494825` с `with_receipts: true`, а затем фильтруем его обратно до `mob.near -> social.near` | Превращает блок записи виджета в один конкретный receipt и один конкретный хеш транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `mob.near/widget/Profile` | | Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | **Что должен включать полезный ответ** - высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции - конкретный receipt ID и хеш исходной транзакции за этой записью виджета -- доказательство, что payload записи был `set` с `efiz.near/widget/DonateNEARtoEfiz` -- одно простое предложение вроде «`efiz.near` записал `DonateNEARtoEfiz` в транзакции `CUA61...`, и в payload действительно лежал исходник donation-виджета» +- доказательство, что payload записи был `set` с `mob.near/widget/Profile` +- одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» -### Shell-сценарий доказательства записи DonateNEARtoEfiz +### Shell-сценарий доказательства записи виджета в NEAR Social -Используйте этот сценарий, когда хотите превратить один игривый блоковый якорь виджета в точную транзакцию, которая его записала. +Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. **Что вы делаете** @@ -898,9 +1120,9 @@ NEAR Social даёт семантическую связь. FastNear block recei ```bash TX_BASE_URL=https://tx.main.fastnear.com RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=efiz.near -WIDGET_NAME=DonateNEARtoEfiz -WIDGET_BLOCK_HEIGHT=92543301 +ACCOUNT_ID=mob.near +WIDGET_NAME=Profile +WIDGET_BLOCK_HEIGHT=86494825 ``` 1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. @@ -914,7 +1136,7 @@ WIDGET_TX_HASH="$( with_transactions: false, with_receipts: true }')" \ - | tee /tmp/efiz-widget-block.json \ + | tee /tmp/mob-widget-block.json \ | jq -r --arg account_id "$ACCOUNT_ID" ' first( .block_receipts[] @@ -936,10 +1158,10 @@ jq --arg account_id "$ACCOUNT_ID" '{ } ) ) -}' /tmp/efiz-widget-block.json +}' /tmp/mob-widget-block.json -# Ожидаемый receipt ID: FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E -# Ожидаемый хеш транзакции: CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf +# Ожидаемый receipt ID: CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6 +# Ожидаемый хеш транзакции: 9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia ``` 2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. @@ -948,7 +1170,7 @@ jq --arg account_id "$ACCOUNT_ID" '{ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/efiz-widget-transaction.json >/dev/null + | tee /tmp/mob-widget-transaction.json >/dev/null jq '{ transaction: { @@ -965,15 +1187,15 @@ jq '{ .args | @base64d | fromjson - | .data["efiz.near"].widget["DonateNEARtoEfiz"][""] + | .data["mob.near"].widget.Profile[""] | split("\n")[0:12] ) } ) -}' /tmp/efiz-widget-transaction.json +}' /tmp/mob-widget-transaction.json ``` -Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `CUA61...` вызвала `social.near set` и пронесла в `args` настоящий исходник виджета `DonateNEARtoEfiz`. +Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `9QDup...` вызвала `social.near set` и пронесла в `args` настоящий исходник `mob.near/widget/Profile`. 3. Завершите каноническим подтверждением текущего состояния через сырой RPC. @@ -998,7 +1220,7 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/efiz-widget-rpc.json >/dev/null + | tee /tmp/mob-widget-rpc.json >/dev/null jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ finality: "final", @@ -1009,14 +1231,14 @@ jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ | .[$account_id].widget[$widget_name] | split("\n")[0:5] ) -}' /tmp/efiz-widget-rpc.json +}' /tmp/mob-widget-rpc.json ``` Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. **Зачем нужен следующий шаг?** -Last-write block виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. +Блок записи виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. ### Проследить один расчёт NEAR Intents и показать, что именно произошло @@ -1204,90 +1426,6 @@ jq -r ' `POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. -### Shell-сценарий: от страшного receipt ID к человеческой истории - -Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. - -**Что вы делаете** - -- Сначала разрешаете receipt. -- Извлекаете `receipt.transaction_hash` через `jq`. -- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. -- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' -``` - -1. Разрешите receipt и поймите, что за объект вы смотрите. - -```bash -TX_HASH="$( - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | tee /tmp/receipt-lookup.json \ - | jq -r '.receipt.transaction_hash' -)" - -jq '{ - receipt: { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - receipt_type: .receipt.receipt_type, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash, - tx_block_height: .receipt.tx_block_height - } -}' /tmp/receipt-lookup.json -``` - -2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/receipt-parent-transaction.json >/dev/null - -jq '{ - transaction: { - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transfer_deposit_yocto: ( - .transactions[0].transaction.actions[0].Transfer.deposit // null - ) - }, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/receipt-parent-transaction.json -``` - -3. Сведите это к одному человеческому предложению. - -```bash -jq -r ' - .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." -' /tmp/receipt-parent-transaction.json -``` - -Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. - -В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. - -**Зачем нужен следующий шаг?** - -`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. - ## Частые задачи ### Найти одну транзакцию diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index c08c099..a78c846 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -2,6 +2,8 @@ ## Готовые сценарии +Эти сценарии выстроены от самого быстрого read-only-запроса к более насыщенному сценарию с изменением состояния. + ### Определить аккаунт по публичному ключу, а затем получить сводку по нему Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» @@ -42,125 +44,227 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. -### Проверить владение коллекцией, а затем выпустить производный NFT +### У меня обычный стейкинг или liquid staking? -Используйте этот сценарий, когда история звучит так: «если аккаунт уже владеет хотя бы одним NFT из коллекции X, выпустить ещё один NFT, в чьих метаданных будет зафиксирована эта связь». +Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». **Сеть** -- testnet +- mainnet + +**Официальные ссылки** + +- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) +- [Liquid staking](https://docs.near.org/primitives/liquid-staking) + +Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. + +**Что вы делаете** + +- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. +- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. +- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. +- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +``` + +1. Получите представление по прямому стейкингу. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' +``` + +2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` + +3. Классифицируйте аккаунт на основе этих двух индексированных представлений. + +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' +``` + +**Зачем нужен следующий шаг?** + +Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. + +### Заархивировать версию BOS-виджета как provenance NFT + +Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». + +**Сети** + +- mainnet для чтения виджета из `social.near` +- testnet для безопасного mint provenance NFT в `nft.examples.testnet` **Официальные ссылки** - [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) - [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) - -Перед началом убедитесь, что аккаунт уже владеет хотя бы одним токеном из `nft.examples.testnet`. Если такого токена ещё нет, сначала выпустите его по гайду с предразвёрнутым контрактом, а затем вернитесь к этому сценарию. +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) **Что вы делаете** -- Используете FastNear API, чтобы быстро ответить на вопрос о допуске. -- Расширяетесь до RPC `nft_tokens_for_owner`, чтобы получить точные `token_id` и метаданные из исходной коллекции. -- Строите детерминированные производные метаданные на основе этого набора токенов. -- Выпускаете производный токен и затем подтверждаете его тем же view-методом NFT. +- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. +- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. +- Хешируете исходник виджета и превращаете его в provenance-метаданные. +- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. +- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. + +Зафиксированный исходный виджет: + +- аккаунт автора: `mob.near` +- путь виджета: `mob.near/widget/Profile` +- SocialDB-блок уровня виджета: `86494825` ```bash API_BASE_URL=https://test.api.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SOURCE_COLLECTION_ID=nft.examples.testnet +MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com +TESTNET_RPC_URL=https://rpc.testnet.fastnear.com +AUTHOR_ACCOUNT_ID=mob.near +WIDGET_NAME=Profile DESTINATION_COLLECTION_ID=nft.examples.testnet -SIGNER_ACCOUNT_ID="$ACCOUNT_ID" -TOKEN_ID="derivative-$(date +%s)" +RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Через FastNear API проверьте, есть ли у аккаунта хоть один NFT из исходной коллекции. +1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. ```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ - | tee /tmp/testnet-account-nfts.json >/dev/null +curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ + | tee /tmp/provenance-account-nfts.json >/dev/null -jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - holds_collection: any(.tokens[]?; .contract_id == $source_collection_id), - matching_contracts: [ +jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ + existing_archive_tokens: [ .tokens[]? - | select(.contract_id == $source_collection_id) + | select(.contract_id == $destination_collection_id) + | { + contract_id, + token_id, + last_update_block_height + } ] -}' /tmp/testnet-account-nfts.json +}' /tmp/provenance-account-nfts.json ``` -2. Перейдите к RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. +2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. ```bash -NFT_TOKENS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - from_index: "0", - limit: 50 +WIDGET_ARGS_BASE64="$( + jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($author_account_id + "/widget/" + $widget_name)], + options: {with_block_height: true} }' | base64 | tr -d '\n' )" -curl -s "$RPC_URL" \ +curl -s "$MAINNET_RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOURCE_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_tokens_for_owner", - args_base64: $args_base64, - finality: "final" - } - }')" \ + --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ | jq '.result.result | implode | fromjson' \ - | tee /tmp/source-collection-tokens.json >/dev/null - -jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - source_collection_id: $source_collection_id, - source_count: length, - source_token_ids: (map(.token_id) | sort | .[:5]) -}' /tmp/source-collection-tokens.json + | tee /tmp/bos-widget.json >/dev/null + +jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + widget_path: ($author_account_id + "/widget/" + $widget_name), + socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], + source_preview: ( + .[$author_account_id].widget[$widget_name][""] + | split("\n")[0:8] + ) +}' /tmp/bos-widget.json ``` -3. Постройте детерминированные производные метаданные из этого набора токенов. +3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. ```bash -DERIVATIVE_METADATA_JSON="$( - jq -c --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - title: ("Derivative witness for " + $source_collection_id), - description: - ("Minted because the holder currently owns " - + (length | tostring) - + " token(s) from " - + $source_collection_id), - media: ( - map(.metadata.media) - | map(select(. != null)) - | .[0] - ), - copies: 1, - extra: ({ - source_collection_id: $source_collection_id, - source_count: length, - source_token_ids: (map(.token_id) | sort | .[:5]) - } | @json) - }' /tmp/source-collection-tokens.json +jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][""] +' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx + +WIDGET_BLOCK_HEIGHT="$( + jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][":block"] + ' /tmp/bos-widget.json +)" + +SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" +SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" +TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" + +PROVENANCE_METADATA_JSON="$( + jq -nc \ + --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" \ + --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ + --arg block_height "$WIDGET_BLOCK_HEIGHT" \ + --arg source_sha256 "$SOURCE_SHA256" '{ + title: ("BOS widget archive: " + $widget_path), + description: ("Archived from social.near on mainnet at block " + $block_height), + copies: 1, + extra: ({ + author_account_id: $author_account_id, + widget_name: $widget_name, + widget_path: $widget_path, + source_contract_id: "social.near", + source_network: "mainnet", + socialdb_block_height: ($block_height | tonumber), + source_sha256: $source_sha256 + } | @json) + }' )" -printf '%s\n' "$DERIVATIVE_METADATA_JSON" | jq '.' +printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' ``` -4. Выпустите производный токен в целевой коллекции. +4. Выпустите provenance NFT в testnet. ```bash near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$ACCOUNT_ID" \ - --argjson metadata "$DERIVATIVE_METADATA_JSON" '{ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --argjson metadata "$PROVENANCE_METADATA_JSON" '{ token_id: $token_id, receiver_id: $receiver_id, metadata: $metadata @@ -170,123 +274,54 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Подтвердите новый токен тем же NFT view-методом. +5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. -Если сразу после возврата mint-транзакции токен ещё не виден, не считайте это ошибкой сразу же: опросите view-метод несколько раз. +Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. ```bash +NFT_TOKEN_ARGS_BASE64="$( + jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ + | base64 | tr -d '\n' +)" + for attempt in 1 2 3 4 5; do - curl -s "$RPC_URL" \ + curl -s "$TESTNET_RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { request_type: "call_function", account_id: $account_id, - method_name: "nft_tokens_for_owner", + method_name: "nft_token", args_base64: $args_base64, finality: "final" } }')" \ | jq '.result.result | implode | fromjson' \ - | jq --arg token_id "$TOKEN_ID" ' - map(select(.token_id == $token_id)) - ' \ - | tee /tmp/derivative-token-verification.json >/dev/null + | tee /tmp/bos-widget-provenance-token.json >/dev/null - if jq -e 'length > 0' /tmp/derivative-token-verification.json >/dev/null; then + if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then break fi sleep 1 done -jq '.' /tmp/derivative-token-verification.json -``` - -**Зачем нужен следующий шаг?** - -FastNear API — это быстрый ответ на вопрос о допуске. Как только аккаунт проходит условие, правильным следующим шагом становится RPC, потому что именно там видны точные `token_id` и собственные NFT view-методы коллекции. - -### У меня обычный стейкинг или liquid staking? - -Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». - -**Сеть** - -- mainnet - -**Официальные ссылки** - -- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) -- [Liquid staking](https://docs.near.org/primitives/liquid-staking) - -Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. - -**Что вы делаете** - -- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. -- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. -- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. - -```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -``` - -1. Получите представление по прямому стейкингу. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` - -2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null -``` - -3. Классифицируйте аккаунт на основе этих двух индексированных представлений. - -```bash -jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ - --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens - | { - classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) - }' +jq '{ + token_id, + owner_id, + title: .metadata.title, + provenance: (.metadata.extra | fromjson) +}' /tmp/bos-widget-provenance-token.json ``` **Зачем нужен следующий шаг?** -Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. +FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). ## Частые задачи diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index c08c099..a78c846 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -2,6 +2,8 @@ ## Готовые сценарии +Эти сценарии выстроены от самого быстрого read-only-запроса к более насыщенному сценарию с изменением состояния. + ### Определить аккаунт по публичному ключу, а затем получить сводку по нему Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» @@ -42,125 +44,227 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. -### Проверить владение коллекцией, а затем выпустить производный NFT +### У меня обычный стейкинг или liquid staking? -Используйте этот сценарий, когда история звучит так: «если аккаунт уже владеет хотя бы одним NFT из коллекции X, выпустить ещё один NFT, в чьих метаданных будет зафиксирована эта связь». +Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». **Сеть** -- testnet +- mainnet + +**Официальные ссылки** + +- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) +- [Liquid staking](https://docs.near.org/primitives/liquid-staking) + +Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. + +**Что вы делаете** + +- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. +- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. +- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. +- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +``` + +1. Получите представление по прямому стейкингу. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' +``` + +2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` + +3. Классифицируйте аккаунт на основе этих двух индексированных представлений. + +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' +``` + +**Зачем нужен следующий шаг?** + +Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. + +### Заархивировать версию BOS-виджета как provenance NFT + +Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». + +**Сети** + +- mainnet для чтения виджета из `social.near` +- testnet для безопасного mint provenance NFT в `nft.examples.testnet` **Официальные ссылки** - [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) - [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) - -Перед началом убедитесь, что аккаунт уже владеет хотя бы одним токеном из `nft.examples.testnet`. Если такого токена ещё нет, сначала выпустите его по гайду с предразвёрнутым контрактом, а затем вернитесь к этому сценарию. +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) **Что вы делаете** -- Используете FastNear API, чтобы быстро ответить на вопрос о допуске. -- Расширяетесь до RPC `nft_tokens_for_owner`, чтобы получить точные `token_id` и метаданные из исходной коллекции. -- Строите детерминированные производные метаданные на основе этого набора токенов. -- Выпускаете производный токен и затем подтверждаете его тем же view-методом NFT. +- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. +- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. +- Хешируете исходник виджета и превращаете его в provenance-метаданные. +- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. +- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. + +Зафиксированный исходный виджет: + +- аккаунт автора: `mob.near` +- путь виджета: `mob.near/widget/Profile` +- SocialDB-блок уровня виджета: `86494825` ```bash API_BASE_URL=https://test.api.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SOURCE_COLLECTION_ID=nft.examples.testnet +MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com +TESTNET_RPC_URL=https://rpc.testnet.fastnear.com +AUTHOR_ACCOUNT_ID=mob.near +WIDGET_NAME=Profile DESTINATION_COLLECTION_ID=nft.examples.testnet -SIGNER_ACCOUNT_ID="$ACCOUNT_ID" -TOKEN_ID="derivative-$(date +%s)" +RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Через FastNear API проверьте, есть ли у аккаунта хоть один NFT из исходной коллекции. +1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. ```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ - | tee /tmp/testnet-account-nfts.json >/dev/null +curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ + | tee /tmp/provenance-account-nfts.json >/dev/null -jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - holds_collection: any(.tokens[]?; .contract_id == $source_collection_id), - matching_contracts: [ +jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ + existing_archive_tokens: [ .tokens[]? - | select(.contract_id == $source_collection_id) + | select(.contract_id == $destination_collection_id) + | { + contract_id, + token_id, + last_update_block_height + } ] -}' /tmp/testnet-account-nfts.json +}' /tmp/provenance-account-nfts.json ``` -2. Перейдите к RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. +2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. ```bash -NFT_TOKENS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - from_index: "0", - limit: 50 +WIDGET_ARGS_BASE64="$( + jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($author_account_id + "/widget/" + $widget_name)], + options: {with_block_height: true} }' | base64 | tr -d '\n' )" -curl -s "$RPC_URL" \ +curl -s "$MAINNET_RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOURCE_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_tokens_for_owner", - args_base64: $args_base64, - finality: "final" - } - }')" \ + --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ | jq '.result.result | implode | fromjson' \ - | tee /tmp/source-collection-tokens.json >/dev/null - -jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - source_collection_id: $source_collection_id, - source_count: length, - source_token_ids: (map(.token_id) | sort | .[:5]) -}' /tmp/source-collection-tokens.json + | tee /tmp/bos-widget.json >/dev/null + +jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + widget_path: ($author_account_id + "/widget/" + $widget_name), + socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], + source_preview: ( + .[$author_account_id].widget[$widget_name][""] + | split("\n")[0:8] + ) +}' /tmp/bos-widget.json ``` -3. Постройте детерминированные производные метаданные из этого набора токенов. +3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. ```bash -DERIVATIVE_METADATA_JSON="$( - jq -c --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - title: ("Derivative witness for " + $source_collection_id), - description: - ("Minted because the holder currently owns " - + (length | tostring) - + " token(s) from " - + $source_collection_id), - media: ( - map(.metadata.media) - | map(select(. != null)) - | .[0] - ), - copies: 1, - extra: ({ - source_collection_id: $source_collection_id, - source_count: length, - source_token_ids: (map(.token_id) | sort | .[:5]) - } | @json) - }' /tmp/source-collection-tokens.json +jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][""] +' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx + +WIDGET_BLOCK_HEIGHT="$( + jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][":block"] + ' /tmp/bos-widget.json +)" + +SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" +SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" +TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" + +PROVENANCE_METADATA_JSON="$( + jq -nc \ + --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" \ + --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ + --arg block_height "$WIDGET_BLOCK_HEIGHT" \ + --arg source_sha256 "$SOURCE_SHA256" '{ + title: ("BOS widget archive: " + $widget_path), + description: ("Archived from social.near on mainnet at block " + $block_height), + copies: 1, + extra: ({ + author_account_id: $author_account_id, + widget_name: $widget_name, + widget_path: $widget_path, + source_contract_id: "social.near", + source_network: "mainnet", + socialdb_block_height: ($block_height | tonumber), + source_sha256: $source_sha256 + } | @json) + }' )" -printf '%s\n' "$DERIVATIVE_METADATA_JSON" | jq '.' +printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' ``` -4. Выпустите производный токен в целевой коллекции. +4. Выпустите provenance NFT в testnet. ```bash near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$ACCOUNT_ID" \ - --argjson metadata "$DERIVATIVE_METADATA_JSON" '{ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --argjson metadata "$PROVENANCE_METADATA_JSON" '{ token_id: $token_id, receiver_id: $receiver_id, metadata: $metadata @@ -170,123 +274,54 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Подтвердите новый токен тем же NFT view-методом. +5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. -Если сразу после возврата mint-транзакции токен ещё не виден, не считайте это ошибкой сразу же: опросите view-метод несколько раз. +Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. ```bash +NFT_TOKEN_ARGS_BASE64="$( + jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ + | base64 | tr -d '\n' +)" + for attempt in 1 2 3 4 5; do - curl -s "$RPC_URL" \ + curl -s "$TESTNET_RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { request_type: "call_function", account_id: $account_id, - method_name: "nft_tokens_for_owner", + method_name: "nft_token", args_base64: $args_base64, finality: "final" } }')" \ | jq '.result.result | implode | fromjson' \ - | jq --arg token_id "$TOKEN_ID" ' - map(select(.token_id == $token_id)) - ' \ - | tee /tmp/derivative-token-verification.json >/dev/null + | tee /tmp/bos-widget-provenance-token.json >/dev/null - if jq -e 'length > 0' /tmp/derivative-token-verification.json >/dev/null; then + if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then break fi sleep 1 done -jq '.' /tmp/derivative-token-verification.json -``` - -**Зачем нужен следующий шаг?** - -FastNear API — это быстрый ответ на вопрос о допуске. Как только аккаунт проходит условие, правильным следующим шагом становится RPC, потому что именно там видны точные `token_id` и собственные NFT view-методы коллекции. - -### У меня обычный стейкинг или liquid staking? - -Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». - -**Сеть** - -- mainnet - -**Официальные ссылки** - -- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) -- [Liquid staking](https://docs.near.org/primitives/liquid-staking) - -Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. - -**Что вы делаете** - -- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. -- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. -- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. - -```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -``` - -1. Получите представление по прямому стейкингу. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` - -2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null -``` - -3. Классифицируйте аккаунт на основе этих двух индексированных представлений. - -```bash -jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ - --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens - | { - classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) - }' +jq '{ + token_id, + owner_id, + title: .metadata.title, + provenance: (.metadata.extra | fromjson) +}' /tmp/bos-widget-provenance-token.json ``` **Зачем нужен следующий шаг?** -Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. +FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). ## Частые задачи diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index 5b7ed02..f8b5689 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -23,7 +23,7 @@ - как выглядит последнее индексированное значение и какие изменения видны в истории - совпал ли `view_state` с текущим индексированным значением -### Shell-сценарий +### Shell-сценарий истории точного ключа Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index 5b7ed02..f8b5689 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -23,7 +23,7 @@ - как выглядит последнее индексированное значение и какие изменения видны в истории - совпал ли `view_state` с текущим индексированным значением -### Shell-сценарий +### Shell-сценарий истории точного ключа Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 7da65fe..b10fd56 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -996,6 +996,8 @@ https://test.api.fastnear.com ## Готовые сценарии +Эти сценарии выстроены от самого быстрого read-only-запроса к более насыщенному сценарию с изменением состояния. + ### Определить аккаунт по публичному ключу, а затем получить сводку по нему Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» @@ -1036,125 +1038,227 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. -### Проверить владение коллекцией, а затем выпустить производный NFT +### У меня обычный стейкинг или liquid staking? -Используйте этот сценарий, когда история звучит так: «если аккаунт уже владеет хотя бы одним NFT из коллекции X, выпустить ещё один NFT, в чьих метаданных будет зафиксирована эта связь». +Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». **Сеть** -- testnet +- mainnet + +**Официальные ссылки** + +- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) +- [Liquid staking](https://docs.near.org/primitives/liquid-staking) + +Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. + +**Что вы делаете** + +- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. +- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. +- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. +- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +``` + +1. Получите представление по прямому стейкингу. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' +``` + +2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` + +3. Классифицируйте аккаунт на основе этих двух индексированных представлений. + +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' +``` + +**Зачем нужен следующий шаг?** + +Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. + +### Заархивировать версию BOS-виджета как provenance NFT + +Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». + +**Сети** + +- mainnet для чтения виджета из `social.near` +- testnet для безопасного mint provenance NFT в `nft.examples.testnet` **Официальные ссылки** - [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) - [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) - -Перед началом убедитесь, что аккаунт уже владеет хотя бы одним токеном из `nft.examples.testnet`. Если такого токена ещё нет, сначала выпустите его по гайду с предразвёрнутым контрактом, а затем вернитесь к этому сценарию. +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) **Что вы делаете** -- Используете FastNear API, чтобы быстро ответить на вопрос о допуске. -- Расширяетесь до RPC `nft_tokens_for_owner`, чтобы получить точные `token_id` и метаданные из исходной коллекции. -- Строите детерминированные производные метаданные на основе этого набора токенов. -- Выпускаете производный токен и затем подтверждаете его тем же view-методом NFT. +- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. +- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. +- Хешируете исходник виджета и превращаете его в provenance-метаданные. +- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. +- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. + +Зафиксированный исходный виджет: + +- аккаунт автора: `mob.near` +- путь виджета: `mob.near/widget/Profile` +- SocialDB-блок уровня виджета: `86494825` ```bash API_BASE_URL=https://test.api.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SOURCE_COLLECTION_ID=nft.examples.testnet +MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com +TESTNET_RPC_URL=https://rpc.testnet.fastnear.com +AUTHOR_ACCOUNT_ID=mob.near +WIDGET_NAME=Profile DESTINATION_COLLECTION_ID=nft.examples.testnet -SIGNER_ACCOUNT_ID="$ACCOUNT_ID" -TOKEN_ID="derivative-$(date +%s)" +RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Через FastNear API проверьте, есть ли у аккаунта хоть один NFT из исходной коллекции. +1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. ```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ - | tee /tmp/testnet-account-nfts.json >/dev/null +curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ + | tee /tmp/provenance-account-nfts.json >/dev/null -jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - holds_collection: any(.tokens[]?; .contract_id == $source_collection_id), - matching_contracts: [ +jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ + existing_archive_tokens: [ .tokens[]? - | select(.contract_id == $source_collection_id) + | select(.contract_id == $destination_collection_id) + | { + contract_id, + token_id, + last_update_block_height + } ] -}' /tmp/testnet-account-nfts.json +}' /tmp/provenance-account-nfts.json ``` -2. Перейдите к RPC, чтобы получить точные `token_id` и исходные метаданные этой коллекции. +2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. ```bash -NFT_TOKENS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - from_index: "0", - limit: 50 +WIDGET_ARGS_BASE64="$( + jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($author_account_id + "/widget/" + $widget_name)], + options: {with_block_height: true} }' | base64 | tr -d '\n' )" -curl -s "$RPC_URL" \ +curl -s "$MAINNET_RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOURCE_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_tokens_for_owner", - args_base64: $args_base64, - finality: "final" - } - }')" \ + --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ | jq '.result.result | implode | fromjson' \ - | tee /tmp/source-collection-tokens.json >/dev/null - -jq --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - source_collection_id: $source_collection_id, - source_count: length, - source_token_ids: (map(.token_id) | sort | .[:5]) -}' /tmp/source-collection-tokens.json + | tee /tmp/bos-widget.json >/dev/null + +jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + widget_path: ($author_account_id + "/widget/" + $widget_name), + socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], + source_preview: ( + .[$author_account_id].widget[$widget_name][""] + | split("\n")[0:8] + ) +}' /tmp/bos-widget.json ``` -3. Постройте детерминированные производные метаданные из этого набора токенов. +3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. ```bash -DERIVATIVE_METADATA_JSON="$( - jq -c --arg source_collection_id "$SOURCE_COLLECTION_ID" '{ - title: ("Derivative witness for " + $source_collection_id), - description: - ("Minted because the holder currently owns " - + (length | tostring) - + " token(s) from " - + $source_collection_id), - media: ( - map(.metadata.media) - | map(select(. != null)) - | .[0] - ), - copies: 1, - extra: ({ - source_collection_id: $source_collection_id, - source_count: length, - source_token_ids: (map(.token_id) | sort | .[:5]) - } | @json) - }' /tmp/source-collection-tokens.json +jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][""] +' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx + +WIDGET_BLOCK_HEIGHT="$( + jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][":block"] + ' /tmp/bos-widget.json )" -printf '%s\n' "$DERIVATIVE_METADATA_JSON" | jq '.' +SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" +SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" +TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" + +PROVENANCE_METADATA_JSON="$( + jq -nc \ + --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" \ + --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ + --arg block_height "$WIDGET_BLOCK_HEIGHT" \ + --arg source_sha256 "$SOURCE_SHA256" '{ + title: ("BOS widget archive: " + $widget_path), + description: ("Archived from social.near on mainnet at block " + $block_height), + copies: 1, + extra: ({ + author_account_id: $author_account_id, + widget_name: $widget_name, + widget_path: $widget_path, + source_contract_id: "social.near", + source_network: "mainnet", + socialdb_block_height: ($block_height | tonumber), + source_sha256: $source_sha256 + } | @json) + }' +)" + +printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' ``` -4. Выпустите производный токен в целевой коллекции. +4. Выпустите provenance NFT в testnet. ```bash near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$ACCOUNT_ID" \ - --argjson metadata "$DERIVATIVE_METADATA_JSON" '{ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --argjson metadata "$PROVENANCE_METADATA_JSON" '{ token_id: $token_id, receiver_id: $receiver_id, metadata: $metadata @@ -1164,123 +1268,54 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Подтвердите новый токен тем же NFT view-методом. +5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. -Если сразу после возврата mint-транзакции токен ещё не виден, не считайте это ошибкой сразу же: опросите view-метод несколько раз. +Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. ```bash +NFT_TOKEN_ARGS_BASE64="$( + jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ + | base64 | tr -d '\n' +)" + for attempt in 1 2 3 4 5; do - curl -s "$RPC_URL" \ + curl -s "$TESTNET_RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKENS_ARGS_BASE64" '{ + --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { request_type: "call_function", account_id: $account_id, - method_name: "nft_tokens_for_owner", + method_name: "nft_token", args_base64: $args_base64, finality: "final" } }')" \ | jq '.result.result | implode | fromjson' \ - | jq --arg token_id "$TOKEN_ID" ' - map(select(.token_id == $token_id)) - ' \ - | tee /tmp/derivative-token-verification.json >/dev/null + | tee /tmp/bos-widget-provenance-token.json >/dev/null - if jq -e 'length > 0' /tmp/derivative-token-verification.json >/dev/null; then + if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then break fi sleep 1 done -jq '.' /tmp/derivative-token-verification.json -``` - -**Зачем нужен следующий шаг?** - -FastNear API — это быстрый ответ на вопрос о допуске. Как только аккаунт проходит условие, правильным следующим шагом становится RPC, потому что именно там видны точные `token_id` и собственные NFT view-методы коллекции. - -### У меня обычный стейкинг или liquid staking? - -Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». - -**Сеть** - -- mainnet - -**Официальные ссылки** - -- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) -- [Liquid staking](https://docs.near.org/primitives/liquid-staking) - -Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. - -**Что вы делаете** - -- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. -- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. -- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. - -```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -``` - -1. Получите представление по прямому стейкингу. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` - -2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null -``` - -3. Классифицируйте аккаунт на основе этих двух индексированных представлений. - -```bash -jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ - --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens - | { - classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) - }' +jq '{ + token_id, + owner_id, + title: .metadata.title, + provenance: (.metadata.extra | fromjson) +}' /tmp/bos-widget-provenance-token.json ``` **Зачем нужен следующий шаг?** -Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. +FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). ## Частые задачи @@ -1576,7 +1611,7 @@ https://kv.test.fastnear.com - как выглядит последнее индексированное значение и какие изменения видны в истории - совпал ли `view_state` с текущим индексированным значением -### Shell-сценарий +### Shell-сценарий истории точного ключа Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» @@ -2084,7 +2119,7 @@ https://testnet.neardata.xyz - когда то же наблюдение стало финализированным - изменил ли точный разбор через RPC интерпретацию -### Shell-сценарий +### Shell-сценарий проверки финализированного блока Используйте этот сценарий, когда вспомогательный маршрут сам выбирает для вас последний финализированный блок, но следующий шаг всё равно требует точной проверки через RPC. @@ -3115,11 +3150,9 @@ jq -n \ Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. -### Правда ли, что `efiz.near` опубликовал `DonateNEARtoEfiz`, и что этот виджет делает? - -Используйте этот сценарий, когда история более лёгкая и даже немного шуточная: «друг говорит, что `efiz.near` когда-то опубликовал виджет буквально с именем `DonateNEARtoEfiz`. Проверь это и покажи, что именно делает этот виджет, не выходя из RPC». +### Что прямо сейчас содержит `mob.near/widget/Profile`? -Этот пример намеренно сделан для удовольствия. Он не учит ничему особенно глубокому про async-исполнение. Он просто показывает, как точными чтениями SocialDB просматривать каталог BOS-автора и отвечать на один очень конкретный вопрос по живым on-chain-данным. +Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». **Официальные ссылки** @@ -3127,17 +3160,17 @@ jq -n \ **Что вы делаете** -- Спрашиваете у `social.near` каталог виджетов под `efiz.near`. -- Сохраняете высоты блоков, потому что они показывают, когда каждый ключ виджета последний раз переписывался. -- Подтверждаете, что `DonateNEARtoEfiz` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. -- Заканчиваете простым handoff: если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). +- Спрашиваете у `social.near` каталог виджетов под `mob.near`. +- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. +- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. +- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=efiz.near -export WIDGET_NAME=DonateNEARtoEfiz +export ACCOUNT_ID=mob.near +export WIDGET_NAME=Profile ``` 1. Получите каталог виджетов и сохраните высоты блоков последней записи. @@ -3183,9 +3216,7 @@ jq --arg account_id "$ACCOUNT_ID" ' ' /tmp/social-widget-keys.json ``` -Это даёт компактный BOS-каталог. На момент написания у `efiz.near` был удивительно эклектичный набор виджетов, включая `ReversedFeed`, `HelloWorld`, `PotlockDonateAll` и `DonateNEARtoEfiz`, но настоящий источник истины здесь — именно живой запрос. - -2. Подтвердите, что `DonateNEARtoEfiz` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. +2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. ```bash WIDGET_GET_ARGS_BASE64="$( @@ -3226,8 +3257,6 @@ jq -r \ ' /tmp/social-widget-source.json ``` -Так вы печатаете первые 25 строк исходника и быстро понимаете, что это вообще за компонент. В живой версии на момент написания код инициализирует `reciever: "efiz.near"` и строит кнопку, которая вызывает `donate` на выбранную сумму. Название виджета честно предупреждает, что будет дальше. - 3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. ```bash @@ -3242,13 +3271,11 @@ jq -r \ | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" ``` -На момент написания живая высота последней записи для `efiz.near/widget/DonateNEARtoEfiz` была `92543301`. - -Если следующий вопрос уже меняется на «какая транзакция записала именно эту версию виджета?», сохраните эту высоту блока и переходите к сценариям-доказательствам NEAR Social в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). +На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. **Зачем нужен следующий шаг?** -Это хорошее напоминание, что RPC может быть не только судебной экспертизой. `keys` позволяет просматривать каталог BOS-автора как разработчику, а `get` даёт возможность заглянуть в точное тело виджета, которое реально лежит on-chain. Иногда ответ и правда такой: «да, ваш друг действительно опубликовал виджет с именем `DonateNEARtoEfiz`, и вот его код». +Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. ## Частые задачи @@ -3455,7 +3482,7 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash - куда на диске должны попасть данные - должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap -### Shell-сценарий +### Shell-сценарий архивного восстановления mainnet Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. @@ -4106,73 +4133,160 @@ https://tx.test.fastnear.com ## Готовые расследования -### Проследить асинхронную promise-цепочку и доказать порядок callback-ов +Эти расследования намеренно выстроены от самого простого якоря к самой насыщенной форензике: сначала один tx hash, затем один receipt, затем паттерны с ошибками и async, и только потом более глубокие расследования по SocialDB и NEAR Intents. -Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» +### У меня есть один хеш транзакции. Что вообще произошло? + +Используйте это расследование, когда история максимально простая: «мне прислали один хеш транзакции. Я просто хочу понять, сработала ли она, что именно сделала и в какой блок попала». + +Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. **Цель** -- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. +- Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. -Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: +Для этого зафиксированного примера: -- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом -- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке -- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и -- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору +- хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- высота включающего блока: `194263342` +- ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` + +Простой человеческий ответ для этого случая такой: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. ```mermaid flowchart LR - Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] - H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] - R --> C["Async cross-contract callback-и"] - C --> B["Recorder state
beta"] - B --> A["Recorder state
alpha"] - A --> G["Recorder state
gamma"] - Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] - R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] - G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] + H["Один tx hash
AdgNifPY..."] --> T["Получаем транзакцию"] + T --> A["Читаем signer, receiver, actions, block"] + A --> S["Короткая человеческая история"] + T -. "если потом понадобится" .-> R["Первая receipt
5GhZcpfK..."] ``` -Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. - -Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. - | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | -| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | -| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | -| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | -| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | -| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | -| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | -| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | +| Читаемая история транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с хеша транзакции и печатаем signer, receiver, включающий блок, список действий и handoff в первую receipt | Даёт самый быстрый читаемый ответ на вопрос «что вообще сделала эта транзакция?» | +| Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | +| Переход к receipt | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | **Что должен включать полезный ответ** -- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» -- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования -- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался -- в каких блоках стали видны изменения состояния -- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования +- кто подписал транзакцию +- какой аккаунт её получил +- какой тип действия она несла +- в какой блок попала +- одно простое предложение, которое объясняет транзакцию без receipt-жаргона -### Превратить один страшный receipt ID из логов в понятную человеческую историю +### Shell-сценарий: от хеша транзакции к человеческой истории -Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. +Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. -**Цель** +**Что вы делаете** -- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. +- Получаете транзакцию по хешу и печатаете её основные поля. +- Подтверждаете финальный статус только если нужны точные RPC-семантики. +- Сохраняете первую receipt только как необязательный следующий шаг. -Для этого зафиксированного примера «страшный receipt ID из логов» такой: +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp +SIGNER_ACCOUNT_ID=mike.near +``` -- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` -- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- высота блока транзакции: `194263342` -- высота блока исполнения receipt: `194263343` +1. Получите транзакцию и распечатайте базовую историю. + +```bash +FIRST_RECEIPT_ID="$( + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/basic-tx-story.json \ + | jq -r '.transactions[0].transaction_outcome.outcome.status.SuccessReceiptId' +)" + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/basic-tx-story.json + +# Ожидаемый список действий: ["Transfer"] +# Ожидаемая первая receipt: 5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq +``` + +2. Если нужны точные RPC-семантики статуса, подтвердите их через `EXPERIMENTAL_tx_status`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' +``` + +3. Если следующий вопрос уже звучит как «что это была за первая receipt?», один раз перейдите по ней и остановитесь. + +```bash +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$FIRST_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, + transaction_hash: .receipt.transaction_hash + }' +``` + +Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. + +**Зачем нужен следующий шаг?** + +`POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. + +### Превратить один страшный receipt ID из логов в понятную человеческую историю + +Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. + +Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. + +**Цель** + +- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. + +Для этого зафиксированного примера «страшный receipt ID из логов» такой: + +- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` +- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- высота блока транзакции: `194263342` +- высота блока исполнения receipt: `194263343` Человеческая история за этим receipt простая: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. @@ -4198,6 +4312,90 @@ flowchart LR - была ли квитанция главным событием или только шагом в большом каскаде - одно предложение простым языком, которое можно без правок вставить коллеге в чат +### Shell-сценарий: от страшного receipt ID к человеческой истории + +Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. + +**Что вы делаете** + +- Сначала разрешаете receipt. +- Извлекаете `receipt.transaction_hash` через `jq`. +- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. +- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +``` + +1. Разрешите receipt и поймите, что за объект вы смотрите. + +```bash +TX_HASH="$( + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | tee /tmp/receipt-lookup.json \ + | jq -r '.receipt.transaction_hash' +)" + +jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + receipt_type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, + transaction_hash: .receipt.transaction_hash, + tx_block_height: .receipt.tx_block_height + } +}' /tmp/receipt-lookup.json +``` + +2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/receipt-parent-transaction.json >/dev/null + +jq '{ + transaction: { + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transfer_deposit_yocto: ( + .transactions[0].transaction.actions[0].Transfer.deposit // null + ) + }, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/receipt-parent-transaction.json +``` + +3. Сведите это к одному человеческому предложению. + +```bash +jq -r ' + .transactions[0] as $tx + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." +' /tmp/receipt-parent-transaction.json +``` + +Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. + +В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. + +**Зачем нужен следующий шаг?** + +`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. + ### Доказать, что одно неудачное действие сорвало весь пакет Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? @@ -4557,6 +4755,57 @@ jq '{ Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. +### Проследить асинхронную promise-цепочку и доказать порядок callback-ов + +Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» + +**Цель** + +- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. + +Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: + +- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом +- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке +- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и +- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору + +```mermaid +flowchart LR + Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] + H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] + R --> C["Async cross-contract callback-и"] + C --> B["Recorder state
beta"] + B --> A["Recorder state
alpha"] + A --> G["Recorder state
gamma"] + Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] + R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] + G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] +``` + +Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. + +Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | +| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | +| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | +| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | +| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | +| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | +| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | +| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | + +**Что должен включать полезный ответ** + +- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» +- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования +- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался +- в каких блоках стали видны изменения состояния +- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования + ### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». @@ -4939,50 +5188,50 @@ jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" ' NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. -### Какая транзакция записала `DonateNEARtoEfiz`? +### Какая транзакция записала `mob.near/widget/Profile`? -Используйте это расследование, когда история уже более лёгкая и даже немного шуточная: «на странице RPC-примеров я уже увидел, что `efiz.near/widget/DonateNEARtoEfiz` существует и что его last-write block — `92543301`. Какая именно транзакция записала этот виджет?» +Используйте это расследование, когда вопрос звучит так: «я уже знаю, что `mob.near/widget/Profile` существует прямо сейчас. Какая именно транзакция записала ту версию виджета, на которую я смотрю?» -Это весёлый пример, но рецепт доказательства здесь вполне серьёзный и знакомый по другим SocialDB-расследованиям: +Это естественное tx-продолжение к более лёгкому RPC-сценарию про виджет и к provenance-NFT-сценарию. Задача здесь прямолинейная: -- стартуем с одного блока, к которому привязан SocialDB-ключ -- превращаем этот блок в конкретный `efiz.near -> social.near` receipt +- стартуем с собственного SocialDB-блока виджета +- превращаем этот блок в один `mob.near -> social.near` receipt - восстанавливаем исходную транзакцию -- декодируем payload `set` и доказываем, что в нём действительно лежит исходник виджета +- декодируем payload `set` и доказываем, что он действительно нёс исходник виджета **Цель** -- Превратить last-write block виджета в один читаемый ответ: какая транзакция записала `DonateNEARtoEfiz`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. +- Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. **Официальные ссылки** - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -Это расследование специально продолжает более лёгкий RPC-сценарий про виджеты. Для этого живого якоря: +Для этого живого якоря: -- аккаунт: `efiz.near` -- виджет: `DonateNEARtoEfiz` -- блок записи в SocialDB: `92543301` -- receipt ID: `FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E` -- хеш исходной транзакции: `CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf` -- внешний блок транзакции: `92543300` +- аккаунт: `mob.near` +- виджет: `Profile` +- блок записи в SocialDB: `86494825` +- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` +- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- внешний блок транзакции: `86494824` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `92543301` с `with_receipts: true`, а затем фильтруем его обратно до `efiz.near -> social.near` | Превращает last-write block виджета в один конкретный receipt и один конкретный хеш транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `DonateNEARtoEfiz` | +| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `86494825` с `with_receipts: true`, а затем фильтруем его обратно до `mob.near -> social.near` | Превращает блок записи виджета в один конкретный receipt и один конкретный хеш транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `mob.near/widget/Profile` | | Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | **Что должен включать полезный ответ** - высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции - конкретный receipt ID и хеш исходной транзакции за этой записью виджета -- доказательство, что payload записи был `set` с `efiz.near/widget/DonateNEARtoEfiz` -- одно простое предложение вроде «`efiz.near` записал `DonateNEARtoEfiz` в транзакции `CUA61...`, и в payload действительно лежал исходник donation-виджета» +- доказательство, что payload записи был `set` с `mob.near/widget/Profile` +- одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» -### Shell-сценарий доказательства записи DonateNEARtoEfiz +### Shell-сценарий доказательства записи виджета в NEAR Social -Используйте этот сценарий, когда хотите превратить один игривый блоковый якорь виджета в точную транзакцию, которая его записала. +Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. **Что вы делаете** @@ -4994,9 +5243,9 @@ NEAR Social даёт семантическую связь. FastNear block recei ```bash TX_BASE_URL=https://tx.main.fastnear.com RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=efiz.near -WIDGET_NAME=DonateNEARtoEfiz -WIDGET_BLOCK_HEIGHT=92543301 +ACCOUNT_ID=mob.near +WIDGET_NAME=Profile +WIDGET_BLOCK_HEIGHT=86494825 ``` 1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. @@ -5010,7 +5259,7 @@ WIDGET_TX_HASH="$( with_transactions: false, with_receipts: true }')" \ - | tee /tmp/efiz-widget-block.json \ + | tee /tmp/mob-widget-block.json \ | jq -r --arg account_id "$ACCOUNT_ID" ' first( .block_receipts[] @@ -5032,10 +5281,10 @@ jq --arg account_id "$ACCOUNT_ID" '{ } ) ) -}' /tmp/efiz-widget-block.json +}' /tmp/mob-widget-block.json -# Ожидаемый receipt ID: FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E -# Ожидаемый хеш транзакции: CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf +# Ожидаемый receipt ID: CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6 +# Ожидаемый хеш транзакции: 9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia ``` 2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. @@ -5044,7 +5293,7 @@ jq --arg account_id "$ACCOUNT_ID" '{ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/efiz-widget-transaction.json >/dev/null + | tee /tmp/mob-widget-transaction.json >/dev/null jq '{ transaction: { @@ -5061,15 +5310,15 @@ jq '{ .args | @base64d | fromjson - | .data["efiz.near"].widget["DonateNEARtoEfiz"][""] + | .data["mob.near"].widget.Profile[""] | split("\n")[0:12] ) } ) -}' /tmp/efiz-widget-transaction.json +}' /tmp/mob-widget-transaction.json ``` -Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `CUA61...` вызвала `social.near set` и пронесла в `args` настоящий исходник виджета `DonateNEARtoEfiz`. +Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `9QDup...` вызвала `social.near set` и пронесла в `args` настоящий исходник `mob.near/widget/Profile`. 3. Завершите каноническим подтверждением текущего состояния через сырой RPC. @@ -5094,7 +5343,7 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/efiz-widget-rpc.json >/dev/null + | tee /tmp/mob-widget-rpc.json >/dev/null jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ finality: "final", @@ -5105,14 +5354,14 @@ jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ | .[$account_id].widget[$widget_name] | split("\n")[0:5] ) -}' /tmp/efiz-widget-rpc.json +}' /tmp/mob-widget-rpc.json ``` Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. **Зачем нужен следующий шаг?** -Last-write block виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. +Блок записи виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. ### Проследить один расчёт NEAR Intents и показать, что именно произошло @@ -5300,90 +5549,6 @@ jq -r ' `POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. -### Shell-сценарий: от страшного receipt ID к человеческой истории - -Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. - -**Что вы делаете** - -- Сначала разрешаете receipt. -- Извлекаете `receipt.transaction_hash` через `jq`. -- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. -- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' -``` - -1. Разрешите receipt и поймите, что за объект вы смотрите. - -```bash -TX_HASH="$( - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | tee /tmp/receipt-lookup.json \ - | jq -r '.receipt.transaction_hash' -)" - -jq '{ - receipt: { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - receipt_type: .receipt.receipt_type, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash, - tx_block_height: .receipt.tx_block_height - } -}' /tmp/receipt-lookup.json -``` - -2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/receipt-parent-transaction.json >/dev/null - -jq '{ - transaction: { - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transfer_deposit_yocto: ( - .transactions[0].transaction.actions[0].Transfer.deposit // null - ) - }, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/receipt-parent-transaction.json -``` - -3. Сведите это к одному человеческому предложению. - -```bash -jq -r ' - .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." -' /tmp/receipt-parent-transaction.json -``` - -Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. - -В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. - -**Зачем нужен следующий шаг?** - -`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. - ## Частые задачи ### Найти одну транзакцию diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index e6cf70e..c6093a6 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -24,7 +24,7 @@ - когда то же наблюдение стало финализированным - изменил ли точный разбор через RPC интерпретацию -### Shell-сценарий +### Shell-сценарий проверки финализированного блока Используйте этот сценарий, когда вспомогательный маршрут сам выбирает для вас последний финализированный блок, но следующий шаг всё равно требует точной проверки через RPC. diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index e6cf70e..c6093a6 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -24,7 +24,7 @@ - когда то же наблюдение стало финализированным - изменил ли точный разбор через RPC интерпретацию -### Shell-сценарий +### Shell-сценарий проверки финализированного блока Используйте этот сценарий, когда вспомогательный маршрут сам выбирает для вас последний финализированный блок, но следующий шаг всё равно требует точной проверки через RPC. diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index f9251e6..64a7cc1 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -738,11 +738,9 @@ jq -n \ Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. -### Правда ли, что `efiz.near` опубликовал `DonateNEARtoEfiz`, и что этот виджет делает? +### Что прямо сейчас содержит `mob.near/widget/Profile`? -Используйте этот сценарий, когда история более лёгкая и даже немного шуточная: «друг говорит, что `efiz.near` когда-то опубликовал виджет буквально с именем `DonateNEARtoEfiz`. Проверь это и покажи, что именно делает этот виджет, не выходя из RPC». - -Этот пример намеренно сделан для удовольствия. Он не учит ничему особенно глубокому про async-исполнение. Он просто показывает, как точными чтениями SocialDB просматривать каталог BOS-автора и отвечать на один очень конкретный вопрос по живым on-chain-данным. +Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». **Официальные ссылки** @@ -750,17 +748,17 @@ jq -n \ **Что вы делаете** -- Спрашиваете у `social.near` каталог виджетов под `efiz.near`. -- Сохраняете высоты блоков, потому что они показывают, когда каждый ключ виджета последний раз переписывался. -- Подтверждаете, что `DonateNEARtoEfiz` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. -- Заканчиваете простым handoff: если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). +- Спрашиваете у `social.near` каталог виджетов под `mob.near`. +- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. +- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. +- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=efiz.near -export WIDGET_NAME=DonateNEARtoEfiz +export ACCOUNT_ID=mob.near +export WIDGET_NAME=Profile ``` 1. Получите каталог виджетов и сохраните высоты блоков последней записи. @@ -806,9 +804,7 @@ jq --arg account_id "$ACCOUNT_ID" ' ' /tmp/social-widget-keys.json ``` -Это даёт компактный BOS-каталог. На момент написания у `efiz.near` был удивительно эклектичный набор виджетов, включая `ReversedFeed`, `HelloWorld`, `PotlockDonateAll` и `DonateNEARtoEfiz`, но настоящий источник истины здесь — именно живой запрос. - -2. Подтвердите, что `DonateNEARtoEfiz` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. +2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. ```bash WIDGET_GET_ARGS_BASE64="$( @@ -849,8 +845,6 @@ jq -r \ ' /tmp/social-widget-source.json ``` -Так вы печатаете первые 25 строк исходника и быстро понимаете, что это вообще за компонент. В живой версии на момент написания код инициализирует `reciever: "efiz.near"` и строит кнопку, которая вызывает `donate` на выбранную сумму. Название виджета честно предупреждает, что будет дальше. - 3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. ```bash @@ -865,13 +859,11 @@ jq -r \ | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" ``` -На момент написания живая высота последней записи для `efiz.near/widget/DonateNEARtoEfiz` была `92543301`. - -Если следующий вопрос уже меняется на «какая транзакция записала именно эту версию виджета?», сохраните эту высоту блока и переходите к сценариям-доказательствам NEAR Social в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). +На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. **Зачем нужен следующий шаг?** -Это хорошее напоминание, что RPC может быть не только судебной экспертизой. `keys` позволяет просматривать каталог BOS-автора как разработчику, а `get` даёт возможность заглянуть в точное тело виджета, которое реально лежит on-chain. Иногда ответ и правда такой: «да, ваш друг действительно опубликовал виджет с именем `DonateNEARtoEfiz`, и вот его код». +Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. ## Частые задачи diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index f9251e6..64a7cc1 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -738,11 +738,9 @@ jq -n \ Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. -### Правда ли, что `efiz.near` опубликовал `DonateNEARtoEfiz`, и что этот виджет делает? +### Что прямо сейчас содержит `mob.near/widget/Profile`? -Используйте этот сценарий, когда история более лёгкая и даже немного шуточная: «друг говорит, что `efiz.near` когда-то опубликовал виджет буквально с именем `DonateNEARtoEfiz`. Проверь это и покажи, что именно делает этот виджет, не выходя из RPC». - -Этот пример намеренно сделан для удовольствия. Он не учит ничему особенно глубокому про async-исполнение. Он просто показывает, как точными чтениями SocialDB просматривать каталог BOS-автора и отвечать на один очень конкретный вопрос по живым on-chain-данным. +Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». **Официальные ссылки** @@ -750,17 +748,17 @@ jq -n \ **Что вы делаете** -- Спрашиваете у `social.near` каталог виджетов под `efiz.near`. -- Сохраняете высоты блоков, потому что они показывают, когда каждый ключ виджета последний раз переписывался. -- Подтверждаете, что `DonateNEARtoEfiz` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. -- Заканчиваете простым handoff: если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). +- Спрашиваете у `social.near` каталог виджетов под `mob.near`. +- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. +- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. +- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=efiz.near -export WIDGET_NAME=DonateNEARtoEfiz +export ACCOUNT_ID=mob.near +export WIDGET_NAME=Profile ``` 1. Получите каталог виджетов и сохраните высоты блоков последней записи. @@ -806,9 +804,7 @@ jq --arg account_id "$ACCOUNT_ID" ' ' /tmp/social-widget-keys.json ``` -Это даёт компактный BOS-каталог. На момент написания у `efiz.near` был удивительно эклектичный набор виджетов, включая `ReversedFeed`, `HelloWorld`, `PotlockDonateAll` и `DonateNEARtoEfiz`, но настоящий источник истины здесь — именно живой запрос. - -2. Подтвердите, что `DonateNEARtoEfiz` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. +2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. ```bash WIDGET_GET_ARGS_BASE64="$( @@ -849,8 +845,6 @@ jq -r \ ' /tmp/social-widget-source.json ``` -Так вы печатаете первые 25 строк исходника и быстро понимаете, что это вообще за компонент. В живой версии на момент написания код инициализирует `reciever: "efiz.near"` и строит кнопку, которая вызывает `donate` на выбранную сумму. Название виджета честно предупреждает, что будет дальше. - 3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. ```bash @@ -865,13 +859,11 @@ jq -r \ | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" ``` -На момент написания живая высота последней записи для `efiz.near/widget/DonateNEARtoEfiz` была `92543301`. - -Если следующий вопрос уже меняется на «какая транзакция записала именно эту версию виджета?», сохраните эту высоту блока и переходите к сценариям-доказательствам NEAR Social в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). +На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. **Зачем нужен следующий шаг?** -Это хорошее напоминание, что RPC может быть не только судебной экспертизой. `keys` позволяет просматривать каталог BOS-автора как разработчику, а `get` даёт возможность заглянуть в точное тело виджета, которое реально лежит on-chain. Иногда ответ и правда такой: «да, ваш друг действительно опубликовал виджет с именем `DonateNEARtoEfiz`, и вот его код». +Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. ## Частые задачи diff --git a/static/ru/snapshots/examples.md b/static/ru/snapshots/examples.md index 06779c3..907dc34 100644 --- a/static/ru/snapshots/examples.md +++ b/static/ru/snapshots/examples.md @@ -25,7 +25,7 @@ - куда на диске должны попасть данные - должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap -### Shell-сценарий +### Shell-сценарий архивного восстановления mainnet Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. diff --git a/static/ru/snapshots/examples/index.md b/static/ru/snapshots/examples/index.md index 06779c3..907dc34 100644 --- a/static/ru/snapshots/examples/index.md +++ b/static/ru/snapshots/examples/index.md @@ -25,7 +25,7 @@ - куда на диске должны попасть данные - должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap -### Shell-сценарий +### Shell-сценарий архивного восстановления mainnet Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index 8022730..a42bb2d 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -2,61 +2,148 @@ ## Готовые расследования -### Проследить асинхронную promise-цепочку и доказать порядок callback-ов +Эти расследования намеренно выстроены от самого простого якоря к самой насыщенной форензике: сначала один tx hash, затем один receipt, затем паттерны с ошибками и async, и только потом более глубокие расследования по SocialDB и NEAR Intents. -Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» +### У меня есть один хеш транзакции. Что вообще произошло? + +Используйте это расследование, когда история максимально простая: «мне прислали один хеш транзакции. Я просто хочу понять, сработала ли она, что именно сделала и в какой блок попала». + +Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. **Цель** -- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. +- Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. -Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: +Для этого зафиксированного примера: -- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом -- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке -- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и -- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору +- хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- высота включающего блока: `194263342` +- ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` + +Простой человеческий ответ для этого случая такой: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. ```mermaid flowchart LR - Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] - H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] - R --> C["Async cross-contract callback-и"] - C --> B["Recorder state
beta"] - B --> A["Recorder state
alpha"] - A --> G["Recorder state
gamma"] - Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] - R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] - G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] + H["Один tx hash
AdgNifPY..."] --> T["Получаем транзакцию"] + T --> A["Читаем signer, receiver, actions, block"] + A --> S["Короткая человеческая история"] + T -. "если потом понадобится" .-> R["Первая receipt
5GhZcpfK..."] ``` -Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. - -Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. - | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | -| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | -| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | -| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | -| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | -| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | -| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | -| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | +| Читаемая история транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с хеша транзакции и печатаем signer, receiver, включающий блок, список действий и handoff в первую receipt | Даёт самый быстрый читаемый ответ на вопрос «что вообще сделала эта транзакция?» | +| Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | +| Переход к receipt | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | **Что должен включать полезный ответ** -- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» -- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования -- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался -- в каких блоках стали видны изменения состояния -- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования +- кто подписал транзакцию +- какой аккаунт её получил +- какой тип действия она несла +- в какой блок попала +- одно простое предложение, которое объясняет транзакцию без receipt-жаргона + +### Shell-сценарий: от хеша транзакции к человеческой истории + +Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. + +**Что вы делаете** + +- Получаете транзакцию по хешу и печатаете её основные поля. +- Подтверждаете финальный статус только если нужны точные RPC-семантики. +- Сохраняете первую receipt только как необязательный следующий шаг. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp +SIGNER_ACCOUNT_ID=mike.near +``` + +1. Получите транзакцию и распечатайте базовую историю. + +```bash +FIRST_RECEIPT_ID="$( + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/basic-tx-story.json \ + | jq -r '.transactions[0].transaction_outcome.outcome.status.SuccessReceiptId' +)" + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/basic-tx-story.json + +# Ожидаемый список действий: ["Transfer"] +# Ожидаемая первая receipt: 5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq +``` + +2. Если нужны точные RPC-семантики статуса, подтвердите их через `EXPERIMENTAL_tx_status`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' +``` + +3. Если следующий вопрос уже звучит как «что это была за первая receipt?», один раз перейдите по ней и остановитесь. + +```bash +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$FIRST_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, + transaction_hash: .receipt.transaction_hash + }' +``` + +Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. + +**Зачем нужен следующий шаг?** + +`POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. ### Превратить один страшный receipt ID из логов в понятную человеческую историю Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. +Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. + **Цель** - Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. @@ -94,6 +181,90 @@ flowchart LR - была ли квитанция главным событием или только шагом в большом каскаде - одно предложение простым языком, которое можно без правок вставить коллеге в чат +### Shell-сценарий: от страшного receipt ID к человеческой истории + +Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. + +**Что вы делаете** + +- Сначала разрешаете receipt. +- Извлекаете `receipt.transaction_hash` через `jq`. +- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. +- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +``` + +1. Разрешите receipt и поймите, что за объект вы смотрите. + +```bash +TX_HASH="$( + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | tee /tmp/receipt-lookup.json \ + | jq -r '.receipt.transaction_hash' +)" + +jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + receipt_type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, + transaction_hash: .receipt.transaction_hash, + tx_block_height: .receipt.tx_block_height + } +}' /tmp/receipt-lookup.json +``` + +2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/receipt-parent-transaction.json >/dev/null + +jq '{ + transaction: { + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transfer_deposit_yocto: ( + .transactions[0].transaction.actions[0].Transfer.deposit // null + ) + }, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/receipt-parent-transaction.json +``` + +3. Сведите это к одному человеческому предложению. + +```bash +jq -r ' + .transactions[0] as $tx + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." +' /tmp/receipt-parent-transaction.json +``` + +Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. + +В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. + +**Зачем нужен следующий шаг?** + +`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. + ### Доказать, что одно неудачное действие сорвало весь пакет Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? @@ -453,6 +624,57 @@ jq '{ Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. +### Проследить асинхронную promise-цепочку и доказать порядок callback-ов + +Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» + +**Цель** + +- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. + +Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: + +- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом +- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке +- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и +- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору + +```mermaid +flowchart LR + Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] + H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] + R --> C["Async cross-contract callback-и"] + C --> B["Recorder state
beta"] + B --> A["Recorder state
alpha"] + A --> G["Recorder state
gamma"] + Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] + R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] + G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] +``` + +Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. + +Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | +| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | +| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | +| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | +| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | +| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | +| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | +| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | + +**Что должен включать полезный ответ** + +- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» +- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования +- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался +- в каких блоках стали видны изменения состояния +- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования + ### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». @@ -835,50 +1057,50 @@ jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" ' NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. -### Какая транзакция записала `DonateNEARtoEfiz`? +### Какая транзакция записала `mob.near/widget/Profile`? -Используйте это расследование, когда история уже более лёгкая и даже немного шуточная: «на странице RPC-примеров я уже увидел, что `efiz.near/widget/DonateNEARtoEfiz` существует и что его last-write block — `92543301`. Какая именно транзакция записала этот виджет?» +Используйте это расследование, когда вопрос звучит так: «я уже знаю, что `mob.near/widget/Profile` существует прямо сейчас. Какая именно транзакция записала ту версию виджета, на которую я смотрю?» -Это весёлый пример, но рецепт доказательства здесь вполне серьёзный и знакомый по другим SocialDB-расследованиям: +Это естественное tx-продолжение к более лёгкому RPC-сценарию про виджет и к provenance-NFT-сценарию. Задача здесь прямолинейная: -- стартуем с одного блока, к которому привязан SocialDB-ключ -- превращаем этот блок в конкретный `efiz.near -> social.near` receipt +- стартуем с собственного SocialDB-блока виджета +- превращаем этот блок в один `mob.near -> social.near` receipt - восстанавливаем исходную транзакцию -- декодируем payload `set` и доказываем, что в нём действительно лежит исходник виджета +- декодируем payload `set` и доказываем, что он действительно нёс исходник виджета **Цель** -- Превратить last-write block виджета в один читаемый ответ: какая транзакция записала `DonateNEARtoEfiz`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. +- Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. **Официальные ссылки** - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -Это расследование специально продолжает более лёгкий RPC-сценарий про виджеты. Для этого живого якоря: +Для этого живого якоря: -- аккаунт: `efiz.near` -- виджет: `DonateNEARtoEfiz` -- блок записи в SocialDB: `92543301` -- receipt ID: `FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E` -- хеш исходной транзакции: `CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf` -- внешний блок транзакции: `92543300` +- аккаунт: `mob.near` +- виджет: `Profile` +- блок записи в SocialDB: `86494825` +- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` +- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- внешний блок транзакции: `86494824` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `92543301` с `with_receipts: true`, а затем фильтруем его обратно до `efiz.near -> social.near` | Превращает last-write block виджета в один конкретный receipt и один конкретный хеш транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `DonateNEARtoEfiz` | +| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `86494825` с `with_receipts: true`, а затем фильтруем его обратно до `mob.near -> social.near` | Превращает блок записи виджета в один конкретный receipt и один конкретный хеш транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `mob.near/widget/Profile` | | Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | **Что должен включать полезный ответ** - высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции - конкретный receipt ID и хеш исходной транзакции за этой записью виджета -- доказательство, что payload записи был `set` с `efiz.near/widget/DonateNEARtoEfiz` -- одно простое предложение вроде «`efiz.near` записал `DonateNEARtoEfiz` в транзакции `CUA61...`, и в payload действительно лежал исходник donation-виджета» +- доказательство, что payload записи был `set` с `mob.near/widget/Profile` +- одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» -### Shell-сценарий доказательства записи DonateNEARtoEfiz +### Shell-сценарий доказательства записи виджета в NEAR Social -Используйте этот сценарий, когда хотите превратить один игривый блоковый якорь виджета в точную транзакцию, которая его записала. +Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. **Что вы делаете** @@ -890,9 +1112,9 @@ NEAR Social даёт семантическую связь. FastNear block recei ```bash TX_BASE_URL=https://tx.main.fastnear.com RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=efiz.near -WIDGET_NAME=DonateNEARtoEfiz -WIDGET_BLOCK_HEIGHT=92543301 +ACCOUNT_ID=mob.near +WIDGET_NAME=Profile +WIDGET_BLOCK_HEIGHT=86494825 ``` 1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. @@ -906,7 +1128,7 @@ WIDGET_TX_HASH="$( with_transactions: false, with_receipts: true }')" \ - | tee /tmp/efiz-widget-block.json \ + | tee /tmp/mob-widget-block.json \ | jq -r --arg account_id "$ACCOUNT_ID" ' first( .block_receipts[] @@ -928,10 +1150,10 @@ jq --arg account_id "$ACCOUNT_ID" '{ } ) ) -}' /tmp/efiz-widget-block.json +}' /tmp/mob-widget-block.json -# Ожидаемый receipt ID: FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E -# Ожидаемый хеш транзакции: CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf +# Ожидаемый receipt ID: CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6 +# Ожидаемый хеш транзакции: 9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia ``` 2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. @@ -940,7 +1162,7 @@ jq --arg account_id "$ACCOUNT_ID" '{ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/efiz-widget-transaction.json >/dev/null + | tee /tmp/mob-widget-transaction.json >/dev/null jq '{ transaction: { @@ -957,15 +1179,15 @@ jq '{ .args | @base64d | fromjson - | .data["efiz.near"].widget["DonateNEARtoEfiz"][""] + | .data["mob.near"].widget.Profile[""] | split("\n")[0:12] ) } ) -}' /tmp/efiz-widget-transaction.json +}' /tmp/mob-widget-transaction.json ``` -Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `CUA61...` вызвала `social.near set` и пронесла в `args` настоящий исходник виджета `DonateNEARtoEfiz`. +Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `9QDup...` вызвала `social.near set` и пронесла в `args` настоящий исходник `mob.near/widget/Profile`. 3. Завершите каноническим подтверждением текущего состояния через сырой RPC. @@ -990,7 +1212,7 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/efiz-widget-rpc.json >/dev/null + | tee /tmp/mob-widget-rpc.json >/dev/null jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ finality: "final", @@ -1001,14 +1223,14 @@ jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ | .[$account_id].widget[$widget_name] | split("\n")[0:5] ) -}' /tmp/efiz-widget-rpc.json +}' /tmp/mob-widget-rpc.json ``` Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. **Зачем нужен следующий шаг?** -Last-write block виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. +Блок записи виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. ### Проследить один расчёт NEAR Intents и показать, что именно произошло @@ -1196,90 +1418,6 @@ jq -r ' `POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. -### Shell-сценарий: от страшного receipt ID к человеческой истории - -Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. - -**Что вы делаете** - -- Сначала разрешаете receipt. -- Извлекаете `receipt.transaction_hash` через `jq`. -- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. -- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' -``` - -1. Разрешите receipt и поймите, что за объект вы смотрите. - -```bash -TX_HASH="$( - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | tee /tmp/receipt-lookup.json \ - | jq -r '.receipt.transaction_hash' -)" - -jq '{ - receipt: { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - receipt_type: .receipt.receipt_type, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash, - tx_block_height: .receipt.tx_block_height - } -}' /tmp/receipt-lookup.json -``` - -2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/receipt-parent-transaction.json >/dev/null - -jq '{ - transaction: { - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transfer_deposit_yocto: ( - .transactions[0].transaction.actions[0].Transfer.deposit // null - ) - }, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/receipt-parent-transaction.json -``` - -3. Сведите это к одному человеческому предложению. - -```bash -jq -r ' - .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." -' /tmp/receipt-parent-transaction.json -``` - -Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. - -В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. - -**Зачем нужен следующий шаг?** - -`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. - ## Частые задачи ### Найти одну транзакцию diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index 8022730..a42bb2d 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -2,61 +2,148 @@ ## Готовые расследования -### Проследить асинхронную promise-цепочку и доказать порядок callback-ов +Эти расследования намеренно выстроены от самого простого якоря к самой насыщенной форензике: сначала один tx hash, затем один receipt, затем паттерны с ошибками и async, и только потом более глубокие расследования по SocialDB и NEAR Intents. -Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» +### У меня есть один хеш транзакции. Что вообще произошло? + +Используйте это расследование, когда история максимально простая: «мне прислали один хеш транзакции. Я просто хочу понять, сработала ли она, что именно сделала и в какой блок попала». + +Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. **Цель** -- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. +- Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. -Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: +Для этого зафиксированного примера: -- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом -- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке -- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и -- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору +- хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- высота включающего блока: `194263342` +- ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` + +Простой человеческий ответ для этого случая такой: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. ```mermaid flowchart LR - Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] - H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] - R --> C["Async cross-contract callback-и"] - C --> B["Recorder state
beta"] - B --> A["Recorder state
alpha"] - A --> G["Recorder state
gamma"] - Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] - R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] - G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] + H["Один tx hash
AdgNifPY..."] --> T["Получаем транзакцию"] + T --> A["Читаем signer, receiver, actions, block"] + A --> S["Короткая человеческая история"] + T -. "если потом понадобится" .-> R["Первая receipt
5GhZcpfK..."] ``` -Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. - -Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. - | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | -| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | -| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | -| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | -| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | -| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | -| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | -| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | +| Читаемая история транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с хеша транзакции и печатаем signer, receiver, включающий блок, список действий и handoff в первую receipt | Даёт самый быстрый читаемый ответ на вопрос «что вообще сделала эта транзакция?» | +| Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | +| Переход к receipt | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | **Что должен включать полезный ответ** -- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» -- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования -- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался -- в каких блоках стали видны изменения состояния -- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования +- кто подписал транзакцию +- какой аккаунт её получил +- какой тип действия она несла +- в какой блок попала +- одно простое предложение, которое объясняет транзакцию без receipt-жаргона + +### Shell-сценарий: от хеша транзакции к человеческой истории + +Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. + +**Что вы делаете** + +- Получаете транзакцию по хешу и печатаете её основные поля. +- Подтверждаете финальный статус только если нужны точные RPC-семантики. +- Сохраняете первую receipt только как необязательный следующий шаг. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp +SIGNER_ACCOUNT_ID=mike.near +``` + +1. Получите транзакцию и распечатайте базовую историю. + +```bash +FIRST_RECEIPT_ID="$( + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/basic-tx-story.json \ + | jq -r '.transactions[0].transaction_outcome.outcome.status.SuccessReceiptId' +)" + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/basic-tx-story.json + +# Ожидаемый список действий: ["Transfer"] +# Ожидаемая первая receipt: 5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq +``` + +2. Если нужны точные RPC-семантики статуса, подтвердите их через `EXPERIMENTAL_tx_status`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' +``` + +3. Если следующий вопрос уже звучит как «что это была за первая receipt?», один раз перейдите по ней и остановитесь. + +```bash +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$FIRST_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, + transaction_hash: .receipt.transaction_hash + }' +``` + +Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. + +**Зачем нужен следующий шаг?** + +`POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. ### Превратить один страшный receipt ID из логов в понятную человеческую историю Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. +Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. + **Цель** - Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. @@ -94,6 +181,90 @@ flowchart LR - была ли квитанция главным событием или только шагом в большом каскаде - одно предложение простым языком, которое можно без правок вставить коллеге в чат +### Shell-сценарий: от страшного receipt ID к человеческой истории + +Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. + +**Что вы делаете** + +- Сначала разрешаете receipt. +- Извлекаете `receipt.transaction_hash` через `jq`. +- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. +- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +``` + +1. Разрешите receipt и поймите, что за объект вы смотрите. + +```bash +TX_HASH="$( + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | tee /tmp/receipt-lookup.json \ + | jq -r '.receipt.transaction_hash' +)" + +jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + receipt_type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, + transaction_hash: .receipt.transaction_hash, + tx_block_height: .receipt.tx_block_height + } +}' /tmp/receipt-lookup.json +``` + +2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/receipt-parent-transaction.json >/dev/null + +jq '{ + transaction: { + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transfer_deposit_yocto: ( + .transactions[0].transaction.actions[0].Transfer.deposit // null + ) + }, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/receipt-parent-transaction.json +``` + +3. Сведите это к одному человеческому предложению. + +```bash +jq -r ' + .transactions[0] as $tx + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." +' /tmp/receipt-parent-transaction.json +``` + +Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. + +В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. + +**Зачем нужен следующий шаг?** + +`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. + ### Доказать, что одно неудачное действие сорвало весь пакет Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? @@ -453,6 +624,57 @@ jq '{ Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. +### Проследить асинхронную promise-цепочку и доказать порядок callback-ов + +Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» + +**Цель** + +- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. + +Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: + +- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом +- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке +- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и +- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору + +```mermaid +flowchart LR + Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] + H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] + R --> C["Async cross-contract callback-и"] + C --> B["Recorder state
beta"] + B --> A["Recorder state
alpha"] + A --> G["Recorder state
gamma"] + Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] + R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] + G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] +``` + +Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. + +Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | +| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | +| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | +| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | +| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | +| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | +| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | +| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | + +**Что должен включать полезный ответ** + +- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» +- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования +- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался +- в каких блоках стали видны изменения состояния +- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования + ### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». @@ -835,50 +1057,50 @@ jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" ' NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. -### Какая транзакция записала `DonateNEARtoEfiz`? +### Какая транзакция записала `mob.near/widget/Profile`? -Используйте это расследование, когда история уже более лёгкая и даже немного шуточная: «на странице RPC-примеров я уже увидел, что `efiz.near/widget/DonateNEARtoEfiz` существует и что его last-write block — `92543301`. Какая именно транзакция записала этот виджет?» +Используйте это расследование, когда вопрос звучит так: «я уже знаю, что `mob.near/widget/Profile` существует прямо сейчас. Какая именно транзакция записала ту версию виджета, на которую я смотрю?» -Это весёлый пример, но рецепт доказательства здесь вполне серьёзный и знакомый по другим SocialDB-расследованиям: +Это естественное tx-продолжение к более лёгкому RPC-сценарию про виджет и к provenance-NFT-сценарию. Задача здесь прямолинейная: -- стартуем с одного блока, к которому привязан SocialDB-ключ -- превращаем этот блок в конкретный `efiz.near -> social.near` receipt +- стартуем с собственного SocialDB-блока виджета +- превращаем этот блок в один `mob.near -> social.near` receipt - восстанавливаем исходную транзакцию -- декодируем payload `set` и доказываем, что в нём действительно лежит исходник виджета +- декодируем payload `set` и доказываем, что он действительно нёс исходник виджета **Цель** -- Превратить last-write block виджета в один читаемый ответ: какая транзакция записала `DonateNEARtoEfiz`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. +- Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. **Официальные ссылки** - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -Это расследование специально продолжает более лёгкий RPC-сценарий про виджеты. Для этого живого якоря: +Для этого живого якоря: -- аккаунт: `efiz.near` -- виджет: `DonateNEARtoEfiz` -- блок записи в SocialDB: `92543301` -- receipt ID: `FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E` -- хеш исходной транзакции: `CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf` -- внешний блок транзакции: `92543300` +- аккаунт: `mob.near` +- виджет: `Profile` +- блок записи в SocialDB: `86494825` +- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` +- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- внешний блок транзакции: `86494824` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `92543301` с `with_receipts: true`, а затем фильтруем его обратно до `efiz.near -> social.near` | Превращает last-write block виджета в один конкретный receipt и один конкретный хеш транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `DonateNEARtoEfiz` | +| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `86494825` с `with_receipts: true`, а затем фильтруем его обратно до `mob.near -> social.near` | Превращает блок записи виджета в один конкретный receipt и один конкретный хеш транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `mob.near/widget/Profile` | | Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | **Что должен включать полезный ответ** - высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции - конкретный receipt ID и хеш исходной транзакции за этой записью виджета -- доказательство, что payload записи был `set` с `efiz.near/widget/DonateNEARtoEfiz` -- одно простое предложение вроде «`efiz.near` записал `DonateNEARtoEfiz` в транзакции `CUA61...`, и в payload действительно лежал исходник donation-виджета» +- доказательство, что payload записи был `set` с `mob.near/widget/Profile` +- одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» -### Shell-сценарий доказательства записи DonateNEARtoEfiz +### Shell-сценарий доказательства записи виджета в NEAR Social -Используйте этот сценарий, когда хотите превратить один игривый блоковый якорь виджета в точную транзакцию, которая его записала. +Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. **Что вы делаете** @@ -890,9 +1112,9 @@ NEAR Social даёт семантическую связь. FastNear block recei ```bash TX_BASE_URL=https://tx.main.fastnear.com RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=efiz.near -WIDGET_NAME=DonateNEARtoEfiz -WIDGET_BLOCK_HEIGHT=92543301 +ACCOUNT_ID=mob.near +WIDGET_NAME=Profile +WIDGET_BLOCK_HEIGHT=86494825 ``` 1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. @@ -906,7 +1128,7 @@ WIDGET_TX_HASH="$( with_transactions: false, with_receipts: true }')" \ - | tee /tmp/efiz-widget-block.json \ + | tee /tmp/mob-widget-block.json \ | jq -r --arg account_id "$ACCOUNT_ID" ' first( .block_receipts[] @@ -928,10 +1150,10 @@ jq --arg account_id "$ACCOUNT_ID" '{ } ) ) -}' /tmp/efiz-widget-block.json +}' /tmp/mob-widget-block.json -# Ожидаемый receipt ID: FsKL2B2azYBHBT2Ro7XqZtaBHdhHxN4VEUhqm5XZb76E -# Ожидаемый хеш транзакции: CUA61dRkeS9c9hc3MVdURRrb2unef9WXcxFFtWo2dQRf +# Ожидаемый receipt ID: CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6 +# Ожидаемый хеш транзакции: 9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia ``` 2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. @@ -940,7 +1162,7 @@ jq --arg account_id "$ACCOUNT_ID" '{ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/efiz-widget-transaction.json >/dev/null + | tee /tmp/mob-widget-transaction.json >/dev/null jq '{ transaction: { @@ -957,15 +1179,15 @@ jq '{ .args | @base64d | fromjson - | .data["efiz.near"].widget["DonateNEARtoEfiz"][""] + | .data["mob.near"].widget.Profile[""] | split("\n")[0:12] ) } ) -}' /tmp/efiz-widget-transaction.json +}' /tmp/mob-widget-transaction.json ``` -Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `CUA61...` вызвала `social.near set` и пронесла в `args` настоящий исходник виджета `DonateNEARtoEfiz`. +Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `9QDup...` вызвала `social.near set` и пронесла в `args` настоящий исходник `mob.near/widget/Profile`. 3. Завершите каноническим подтверждением текущего состояния через сырой RPC. @@ -990,7 +1212,7 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/efiz-widget-rpc.json >/dev/null + | tee /tmp/mob-widget-rpc.json >/dev/null jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ finality: "final", @@ -1001,14 +1223,14 @@ jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ | .[$account_id].widget[$widget_name] | split("\n")[0:5] ) -}' /tmp/efiz-widget-rpc.json +}' /tmp/mob-widget-rpc.json ``` Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. **Зачем нужен следующий шаг?** -Last-write block виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. +Блок записи виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. ### Проследить один расчёт NEAR Intents и показать, что именно произошло @@ -1196,90 +1418,6 @@ jq -r ' `POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. -### Shell-сценарий: от страшного receipt ID к человеческой истории - -Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. - -**Что вы делаете** - -- Сначала разрешаете receipt. -- Извлекаете `receipt.transaction_hash` через `jq`. -- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. -- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' -``` - -1. Разрешите receipt и поймите, что за объект вы смотрите. - -```bash -TX_HASH="$( - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | tee /tmp/receipt-lookup.json \ - | jq -r '.receipt.transaction_hash' -)" - -jq '{ - receipt: { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - receipt_type: .receipt.receipt_type, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash, - tx_block_height: .receipt.tx_block_height - } -}' /tmp/receipt-lookup.json -``` - -2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/receipt-parent-transaction.json >/dev/null - -jq '{ - transaction: { - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transfer_deposit_yocto: ( - .transactions[0].transaction.actions[0].Transfer.deposit // null - ) - }, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/receipt-parent-transaction.json -``` - -3. Сведите это к одному человеческому предложению. - -```bash -jq -r ' - .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." -' /tmp/receipt-parent-transaction.json -``` - -Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. - -В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. - -**Зачем нужен следующий шаг?** - -`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. - ## Частые задачи ### Найти одну транзакцию From 3043d162e0c22bf8346e235132dea8d675ce5040 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 19:15:09 -0700 Subject: [PATCH 07/35] docs: expand and polish example workflows --- docs/api/examples.md | 38 +- docs/fastdata/kv/examples.md | 12 + docs/neardata/examples.md | 12 + docs/rpc/examples.md | 274 +++++- docs/snapshots/examples.mdx | 12 + docs/transfers/examples.md | 12 + docs/tx/berry-club.mdx | 264 ++++++ docs/tx/examples.md | 144 ++- docs/tx/outlayer.mdx | 274 ++++++ .../current/api/examples.md | 38 +- .../current/fastdata/kv/examples.md | 12 + .../current/neardata/examples.md | 12 + .../current/rpc/examples.md | 274 +++++- .../current/snapshots/examples.mdx | 12 + .../current/transfers/examples.md | 12 + .../current/tx/berry-club.mdx | 264 ++++++ .../current/tx/examples.md | 144 ++- .../current/tx/outlayer.mdx | 274 ++++++ scripts/generate-ai-surfaces.js | 14 + sidebars.js | 31 +- .../BerryClubSnapshotGallery/index.js | 70 ++ .../styles.module.css | 71 ++ src/css/custom.css | 129 +++ src/data/berryClubSnapshots.json | 1 + static/ru/api/examples.md | 23 +- static/ru/api/examples/index.md | 23 +- static/ru/fastdata/kv/examples.md | 7 + static/ru/fastdata/kv/examples/index.md | 7 + static/ru/guides/llms.txt | 4 +- static/ru/llms-full.txt | 889 +++++++++++++++++- static/ru/llms.txt | 4 +- static/ru/neardata/examples.md | 7 + static/ru/neardata/examples/index.md | 7 + static/ru/rpc/examples.md | 249 ++++- static/ru/rpc/examples/index.md | 249 ++++- static/ru/snapshots/examples.md | 7 + static/ru/snapshots/examples/index.md | 7 + static/ru/structured-data/site-graph.json | 26 + static/ru/transfers/examples.md | 7 + static/ru/transfers/examples/index.md | 7 + static/ru/tx/examples.md | 97 +- static/ru/tx/examples/berry-club.md | 226 +++++ static/ru/tx/examples/berry-club/index.md | 226 +++++ static/ru/tx/examples/index.md | 97 +- static/ru/tx/examples/outlayer.md | 250 +++++ static/ru/tx/examples/outlayer/index.md | 250 +++++ 46 files changed, 5002 insertions(+), 67 deletions(-) create mode 100644 docs/tx/berry-club.mdx create mode 100644 docs/tx/outlayer.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx create mode 100644 src/components/BerryClubSnapshotGallery/index.js create mode 100644 src/components/BerryClubSnapshotGallery/styles.module.css create mode 100644 src/data/berryClubSnapshots.json create mode 100644 static/ru/tx/examples/berry-club.md create mode 100644 static/ru/tx/examples/berry-club/index.md create mode 100644 static/ru/tx/examples/outlayer.md create mode 100644 static/ru/tx/examples/outlayer/index.md diff --git a/docs/api/examples.md b/docs/api/examples.md index 073e222..0ac9e2b 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -10,12 +10,24 @@ page_actions: ## Worked walkthroughs -These are ordered from the quickest read-only lookup to the more involved state-changing flow. +Read this page as a short ladder: first resolve who the account is, then classify the wallet shape, then use one richer provenance flow when you want to turn a live BOS artifact into a minted record. ### Resolve a public key, then fetch the account snapshot Use this when you have a public key first and the next user-facing question is “which account is this?” followed immediately by “what does that account look like right now?” +
+
+ Strategy +

Resolve identity first, then reuse the same account ID for one readable wallet snapshot.

+
+
+

01GET /v1/public_key gives the candidate account_id values for the key.

+

02jq lifts the account you actually want to inspect next.

+

03GET /v1/account/.../full answers balances, NFTs, and staking in one response.

+
+
+ **What you're doing** - Resolve the public key to one or more account IDs. @@ -56,6 +68,18 @@ The public-key lookup tells you which account you are dealing with. The full acc Use this when the user story is “show me whether this wallet is exposed through direct staking pools, liquid staking tokens, or both.” +
+
+ Strategy +

Compare staking positions and FT balances before you interpret the wallet.

+
+
+

01GET /v1/account/.../staking finds direct pool exposure.

+

02GET /v1/account/.../ft finds liquid staking tokens that sit beside or instead of direct pools.

+

03jq turns those two indexed reads into direct_only, liquid_only, or mixed.

+
+
+ **Network** - mainnet @@ -132,6 +156,18 @@ If the classification is `direct_only`, the next operational question is usually Use this when the user story is “this BOS widget is a real on-chain artifact. Mint an NFT that records exactly which version I archived.” +
+
+ Strategy +

Read the exact widget first, then mint only after the provenance fields are deterministic.

+
+
+

01GET /v1/account/.../nft checks whether the receiver already holds archive NFTs from this collection.

+

02RPC call_function get on social.near reads the exact widget source and its SocialDB write block.

+

03Hash the source, mint nft_mint on testnet, then verify the provenance fields through nft_tokens_for_owner.

+
+
+ **Networks** - mainnet for reading the widget from `social.near` diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index d861e0b..21ad761 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -14,6 +14,18 @@ page_actions: Use this investigation when one contract storage key looks suspicious and you want the latest indexed value, the write history for that same key, and one final `view_state` check. +
+
+ Strategy +

Start with one exact key, widen only into that key’s history, then finish with one chain-state confirmation.

+
+
+

01get-latest-key gives the newest indexed row for the exact key you care about.

+

02get-history-key or history-by-key shows how that same key changed over time.

+

03RPC view_state is the final exact read when you need to compare index history with the chain right now.

+
+
+ **Goal** - Explain what this storage key looks like in the index, how it changed, and whether `view_state` agrees right now. diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index a0cebd3..05b8b77 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -14,6 +14,18 @@ page_actions: Use this investigation when you want to notice a new block as early as possible, but the final answer still needs a finalized block and sometimes an exact RPC read. +
+
+ Strategy +

Let NEAR Data tell you something changed, then reuse the same block family for the stable confirmation.

+
+
+

01block-optimistic or last-block-optimistic gives the earliest useful signal.

+

02block or last-block-final confirms whether the same observation survived into finalized history.

+

03RPC block is only the last step, once you know the exact height or hash that matters.

+
+
+ **Goal** - Notice a recent block quickly, then check the same thing again once finality catches up. diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index b24334d..c145f3d 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -12,12 +12,26 @@ page_actions: Use this page when you already know the answer lives in RPC and you want the shortest path to it. The goal is not to memorize every method. It is to start with the right RPC read or write, stop as soon as the response answers the question, and only switch to a higher-level API when that would save time. -## Worked walkthroughs +## Account and Key Mechanics + +Start here when the question is about exact permissions, exact key state, or one contract-level write flow. ### Audit and remove old Near Social function-call keys Use this when you know an account has accumulated older `social.near` function-call keys and you want to inspect them, choose one intentionally, and remove it with raw RPC submission. +
+
+ Strategy +

Use exact key reads to narrow the target first, then sign exactly one delete.

+
+
+

01RPC view_access_key_list finds only the function-call keys scoped to social.near.

+

02RPC view_access_key double-checks the one key you plan to remove, and POST /v0/account is only for optional account-level context.

+

03RPC send_tx submits the DeleteKey, then RPC view_access_key_list closes the loop.

+
+
+ **What you're doing** - Use RPC itself to list every access key on the account. @@ -257,10 +271,240 @@ fi Re-running `view_access_key_list` closes the loop on the same RPC method you used for discovery. If the delete succeeded there, you do not need an indexed API to prove the cleanup. +### Which transaction added this `social.near` function-call key, and who authorized it? + +Use this when you can already see a live access key on the account, but you want to trace it back to the `AddKey` transaction that created it and identify which public key actually authorized that change. + +
+
+ Strategy +

Start from the live key, then walk backward only as far as you need.

+
+
+

01RPC view_access_key gives the current stored nonce, which is the best historical clue you have.

+

02POST /v0/account turns that nonce into a tight candidate window instead of a whole-account search.

+

03POST /v0/transactions tells you whether the key was added directly or through delegated authorization, and POST /v0/receipt is only for the exact AddKey execution block.

+
+
+ +**What you're doing** + +- Read the exact key first with RPC and keep its current nonce as the clue. +- Convert that nonce into a tight block-height window for the likely `AddKey` receipt. +- Search account history only inside that window instead of scanning the whole account. +- Hydrate the candidate transaction and distinguish three different keys: + - the key that got added + - the top-level signer public key + - the delegated authorizing public key, if the change was wrapped in a `Delegate` action + +Three nonce details matter up front: + +- New access keys start with a nonce derived from block height at roughly `block_height * 1_000_000`, so dividing the current nonce by `1_000_000` gives a useful search window. +- The `AddKey` action payload often shows `access_key.nonce: 0`. That is not the stored nonce you later see from `view_access_key`. +- If the key has been used heavily since creation, widen the search window a bit more. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' + +# Sample live key observed on April 18, 2026: +# export ACCOUNT_ID=mike.near +# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' +``` + +1. Read the exact key first, then turn its current nonce into a search window. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$TARGET_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" + } + }')" \ + | tee /tmp/key-origin-view.json >/dev/null + +CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" +ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" +SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" +SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" + +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_public_key "$TARGET_PUBLIC_KEY" \ + --argjson current_nonce "$CURRENT_NONCE" \ + --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ + --argjson search_from "$SEARCH_FROM" \ + --argjson search_to "$SEARCH_TO" \ + --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ + account_id: $account_id, + target_public_key: $target_public_key, + current_nonce: $current_nonce, + estimated_receipt_block: $estimated_receipt_block, + search_from_tx_block_height: $search_from, + search_to_tx_block_height: $search_to, + permission: ($permission | fromjson) + }' +``` + +If you use the sample key above, the estimated receipt block should land at `112057392`. + +2. Search account history only inside that block neighborhood. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_tx_block_height "$SEARCH_FROM" \ + --argjson to_tx_block_height "$SEARCH_TO" '{ + account_id: $account_id, + is_real_signer: true, + from_tx_block_height: $from_tx_block_height, + to_tx_block_height: $to_tx_block_height, + desc: false, + limit: 50 + }')" \ + | tee /tmp/key-origin-candidates.json >/dev/null + +jq '{ + txs_count, + candidate_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_signer, + is_real_signer, + is_predecessor, + is_receiver + } + ] +}' /tmp/key-origin-candidates.json +``` + +With the sample `mike.near` key above, this window returns one candidate transaction: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` at outer tx block `112057390`. + +3. Hydrate those candidates and keep only the transaction that actually added your target key. + +```bash +TX_HASHES_JSON="$( + jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json +)" + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ + | tee /tmp/key-origin-transactions.json >/dev/null + +jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "direct", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $tx.transaction.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }), + ($tx.transaction.actions[]? + | .Delegate? + | .delegate_action as $delegate + | $delegate.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "delegated", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $delegate.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }) + ) + | { + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + tx_block_hash: $tx.execution_outcome.block_hash, + receiver_id: $tx.transaction.receiver_id + } + . +' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json +``` + +If `authorization_mode` is `direct`, the top-level signer public key and the authorizing public key are the same. If `authorization_mode` is `delegated`, the key that actually authorized the `AddKey` lives inside `Delegate.delegate_action.public_key`. + +With the sample `mike.near` key above, the match is delegated: + +- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` +- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` +- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` +- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` + +4. Optional: if you need the exact `AddKey` receipt block too, pivot one more time by receipt ID. + +```bash +ADD_KEY_RECEIPT_ID="$( + jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | .receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) + | .receipt.receipt_id + ' /tmp/key-origin-transactions.json | head -n 1 +)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receipt_block_height: .receipt.block_height, + tx_block_height: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }' +``` + +For the sample key above, the exact `AddKey` receipt is `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` in receipt block `112057392`, while the outer transaction landed earlier in block `112057390`. + +**Why this next step?** + +Start with exact current key state because it gives you the nonce clue. A tight `/v0/account` window turns that clue into a small candidate set. `/v0/transactions` tells you whether the key was added directly or through delegated authorization. `/v0/receipt` is the optional last step when you need the exact `AddKey` receipt block, not just the outer transaction. + ### Register FT storage if needed, then transfer tokens Use this when the user story is “send fungible tokens safely, but first prove whether the receiver is already registered for storage on that FT contract.” +
+
+ Strategy +

Read storage first, then spend the minimum write calls needed to make the transfer stick.

+
+
+

01RPC call_function storage_balance_of tells you whether the receiver is already registered.

+

02RPC call_function storage_balance_bounds only matters if you need the exact minimum deposit before writing.

+

03RPC send_tx submits storage_deposit and ft_transfer, then RPC call_function ft_balance_of proves the result.

+
+
+ **Network** - testnet @@ -558,10 +802,26 @@ curl -s "$RPC_URL" \ This is a good RPC example because every step stays close to the contract itself: first check storage state, then send the minimum required change calls, then verify the post-transfer balance directly on the contract. +## NEAR Social and BOS Exact Reads + +These stay on exact SocialDB reads and on-chain readiness checks until the question turns historical. + ### Can this account still publish to NEAR Social right now? Use this when the user story is “I’m about to publish a profile change, widget update, or graph write under `mike.near`, and I want a plain go/no-go answer before I open wallet signing.” +
+
+ Strategy +

Ask social.near for the two things that matter before you sign anything.

+
+
+

01RPC view_account makes sure the signer account exists and can actually submit a transaction.

+

02RPC call_function get_account_storage tells you whether the target account has room left on social.near.

+

03RPC call_function is_write_permission_granted only comes into play when a different signer is trying to write on that account’s behalf.

+
+
+ This is the same question real NEAR Social clients have to answer before they try a write: - does the target account already have storage on `social.near`? @@ -752,6 +1012,18 @@ This keeps the whole question on exact on-chain reads. `social.near` itself answ Use this when the question is simple: “show me the live source for `mob.near/widget/Profile`, tell me when that widget key was last written, and keep me on exact RPC reads.” +
+
+ Strategy +

Stay on exact SocialDB reads, and only widen into history if the question turns forensic.

+
+
+

01RPC call_function keys shows the widget catalog and the last-write blocks under mob.near/widget/*.

+

02RPC call_function get reads the exact source for widget/Profile.

+

03If the next question becomes “which transaction wrote this?”, hand off to the widget proof recipe in /tx/examples.

+
+
+ **Official references** - [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) diff --git a/docs/snapshots/examples.mdx b/docs/snapshots/examples.mdx index cfac0a2..a7261b7 100644 --- a/docs/snapshots/examples.mdx +++ b/docs/snapshots/examples.mdx @@ -14,6 +14,18 @@ page_actions: Use this investigation when an operator says “I need this node back online” and you need to decide whether the right path is optimized `fast-rpc`, standard RPC, or archival hot/cold recovery. +
+
+ Strategy +

Pick the recovery class first, then run the smallest command sequence that matches that class.

+
+
+

01Decide whether the real need is optimized fast-rpc, standard RPC, or archival recovery.

+

02If the answer is archival, capture one exact snapshot block height first and keep reusing it.

+

03Run only the commands for that chosen path, instead of mixing optimized, standard, and archival steps together.

+
+
+ **Goal** - Turn a vague recovery request into the right mainnet snapshot path and the minimum command sequence to get moving safely. diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index 7c0c6a7..068449e 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -14,6 +14,18 @@ page_actions: Use this when the user story is “I know funds moved, but I want the exact execution anchor behind that movement without dragging in the whole account history yet.” +
+
+ Strategy +

Stay narrow on movement first, then pivot once into execution history.

+
+
+

01POST /v0/transfers gives you the tight outgoing window and the specific movement worth chasing.

+

02jq lifts one receipt_id without dragging in the rest of the account history.

+

03POST /v0/receipt turns that movement into one execution anchor you can keep following in /tx.

+
+
+ **What you're doing** - Query a bounded outgoing transfer window for one account on mainnet. diff --git a/docs/tx/berry-club.mdx b/docs/tx/berry-club.mdx new file mode 100644 index 0000000..ea0f9f5 --- /dev/null +++ b/docs/tx/berry-club.mdx @@ -0,0 +1,264 @@ +--- +sidebar_label: Berry Club +slug: /tx/examples/berry-club +title: "Berry Club: Reconstruct historical boards" +description: "Use Transactions API, RPC get_lines, and replayed draw calls to reconstruct Berry Club boards across historical eras." +displayed_sidebar: transactionsApiSidebar +page_actions: + - markdown +keywords: + - Berry Club + - FastNear + - Transactions API + - RPC + - get_lines + - draw + - pixel board + - historical snapshots +--- + +import Link from '@site/src/components/LocalizedLink'; +import BerryClubSnapshotGallery from '@site/src/components/BerryClubSnapshotGallery'; +import berryClubSnapshots from '@site/src/data/berryClubSnapshots.json'; + +{/* FASTNEAR_AI_DISCOVERY: This case study shows how to reconstruct Berry Club boards with FastNear. It separates current-state reads via get_lines from historical archaeology via block ranges, account history, transaction hydration, and replayed draw payloads. */} + +# Berry Club: Reconstruct historical boards + +Use this when the question is: “what did Berry Club look like during one era, and which `draw` calls made the board look that way?” + +This is a read-only Transactions case study. If all you need is the board right now, use `get_lines` and stop. If you want to explain how the board got there, switch to block history, account history, hydrated `draw` calls, and replay. + +
+
+ Strategy +

Read the live board first, bound the era second, and only then replay the draws that explain it.

+
+
+

01RPC call_function get_lines gives the current 50x50 board and tells you what “now” looks like.

+

02POST /v0/blocks plus POST /v0/account bounds one era and yields candidate draw hashes.

+

03POST /v0/transactions hydrates those draws so you can replay them into historical checkpoints.

+
+
+ +Keep these close: + +- [js.fastnear.com](https://js.fastnear.com/) +- [fastnear/js-monorepo](https://github.com/fastnear/js-monorepo) +- Transactions API: Account History +- Transactions API: Transactions by Hash +- Transactions API: Block Range +- RPC: call_function + +Berry Club archaeology is a mainnet-only story in this guide. The rendered checkpoints below come from reproducible mainnet snapshot data checked into this repo. + +## The short version + +Berry Club gives you a clean current-state read through `get_lines`, but it does not give you a ready-made “board at block N” endpoint. + +That splits the job into two parts: + +- use RPC `call_function` when the question is “what does the board look like now?” +- use indexed history when the question is “which writes produced that board?” +- use archival RPC only when you want to materialize a known checkpoint directly + +```mermaid +flowchart TD + A["RPC call_function: get_lines"] --> B["Current 50x50 board"] + C["Transactions API: /v0/blocks"] --> D["Bound the era"] + D --> E["/v0/account for berryclub.ek.near"] + E --> F["Candidate draw transaction hashes"] + F --> G["/v0/transactions hydration"] + G --> H["Replay draw writes into a historical board"] +``` + +## Why Berry Club is a good NEAR history example + +Berry Club gives you both sides of the problem in one contract: + +- a clean current-state read through `get_lines` +- a long-running stream of `draw` transactions with ordinary `FunctionCall` args +- a board format simple enough to decode and replay in normal JavaScript + +That makes it a very NEAR-native history example: one view method for current state, one write method for changes, and indexed history when you want to explain how the current state came to exist. + +## 1. Read the current board first + +The live demo uses `berryclub.ek.near` and calls `get_lines` as a read-only view: + +```javascript +await near.view({ + contractId: 'berryclub.ek.near', + methodName: 'get_lines', + args: { + lines: [...Array(50).keys()], + }, +}); +``` + +That is the “board right now” path. It does not tell you how the board got there. + +| Question | Best surface | Why | +| --- | --- | --- | +| what does the board look like now? | RPC `call_function` | the contract already exposes current state through `get_lines` | +| which draws happened in this era? | `/v0/account` + `/v0/transactions` | indexed history gives you bounded candidate writes and hydrated args | +| what did the board look like at a known checkpoint? | archival RPC or full replay | use archival state for direct materialization, or replay historical writes yourself | + +## 2. Decode `get_lines` into a 50x50 grid + +The useful part of the `js.fastnear.com` Berry Club markup is the line decoder: + +- each returned line is base64 +- decode it to bytes +- skip the first 4 bytes +- then read 32-bit little-endian colors every 8 bytes + +```javascript +function decodeLine(encodedLine) { + const bytes = Buffer.from(encodedLine, 'base64'); + const colors = []; + + for (let offset = 4; offset < bytes.length; offset += 8) { + colors.push(bytes.readUInt32LE(offset) & 0xffffff); + } + + return colors; +} +``` + +Apply that to all 50 returned lines and you have a full 50x50 board ready to render. + +## 3. Bound the era you care about + +Start by bounding the era before you hunt for draws. The checked-in launch checkpoint in this repo sits at block `21898354`, and the midpoint checkpoint sits at block `97601515`. + +Use the block-range surface first: + +```bash +curl -sS https://tx.main.fastnear.com/v0/blocks \ + -H 'content-type: application/json' \ + --data '{ + "from_block_height": 21898350, + "to_block_height": 21898355, + "desc": false, + "limit": 5 + }' +``` + +Then switch to account history and ask for Berry Club activity inside a bounded block window: + +```bash +curl -sS https://tx.main.fastnear.com/v0/account \ + -H 'content-type: application/json' \ + --data '{ + "account_id": "berryclub.ek.near", + "is_function_call": true, + "is_receiver": true, + "is_real_receiver": true, + "from_tx_block_height": 97576515, + "to_tx_block_height": 97601516, + "desc": true, + "limit": 40 + }' +``` + +This is the useful sequence: + +- `/v0/blocks` helps you reason about the block neighborhood +- `/v0/account` gives you candidate Berry Club transaction hashes in that neighborhood + +## 4. Hydrate and keep only `draw` calls + +Once you have candidate hashes, hydrate them and keep only top-level `draw` calls whose receiver is `berryclub.ek.near`. + +The payloads are just normal `FunctionCall` args shaped like `{ pixels: [...] }`: + +```bash +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{ + "tx_hashes": [ + "Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ", + "8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL" + ] + }' | jq '.transactions[] + | select(.transaction.receiver_id == "berryclub.ek.near") + | .transaction.actions[]?.FunctionCall + | select(.method_name == "draw") + | { + method_name, + args: (.args | @base64d | fromjson) + }' +``` + +That gives you exactly what replay needs: + +- which transaction wrote pixels +- which coordinates were touched +- which colors were written + +## 5. Replay historical draws into a board + +For a full replay, keep a 50x50 array in memory and apply hydrated `draw` transactions oldest-first. + +```javascript +const board = Array.from({ length: 50 }, () => Array(50).fill(0)); + +function applyDraw(boardState, drawArgs) { + for (const pixel of drawArgs.pixels) { + if (pixel.x < 0 || pixel.x >= 50 || pixel.y < 0 || pixel.y >= 50) { + continue; + } + + boardState[pixel.y][pixel.x] = pixel.color; + } +} + +for (const drawTx of drawTransactionsOldestFirst) { + applyDraw(board, drawTx.args); +} +``` + +Keep the split straight: + +- `get_lines` is current state +- `tx/account` plus `tx/transactions` is replay material + +## 6. Rendered checkpoints by era + +The gallery below uses checked-in snapshot data generated from Berry Club mainnet history: + +- `launch` is the latest successful `draw` within the first 24 hours after the first successful draw +- `mid` is the latest successful `draw` at or before the midpoint timestamp of Berry Club history +- `recent` is the latest successful `draw` seen during the snapshot rebuild run + + + +These rendered checkpoints currently resolve to: + +- `launch`: `BDNFpCpLXjBrgjR6z6wCZmB9EWdHnVMdqau3iTWTRE5H` at block `21898354` +- `mid`: `Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ` at block `97601515` +- `recent`: `8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL` at block `194588754` + +## Where to go for signed interactions + +Keep this page read-only. + +If you want the live signed interaction path for `draw` and `buy_tokens`, go here instead: + +- [js.fastnear.com](https://js.fastnear.com/) +- [fastnear/js-monorepo Berry Club example](https://github.com/fastnear/js-monorepo/tree/main/examples/static/berryclub) + +That is the right place for wallet-connected flows. This page is for historical reconstruction. diff --git a/docs/tx/examples.md b/docs/tx/examples.md index fd0f43f..a2129e0 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -2,15 +2,17 @@ sidebar_label: Examples slug: /tx/examples title: Transactions Examples -description: Plain-language investigations for following receipts, transactions, NEAR Social writes, promise chains, and NEAR Intents settlements. +description: Plain-language investigations and case studies for following receipts, transactions, NEAR Social writes, promise chains, and NEAR Intents settlements. displayed_sidebar: transactionsApiSidebar page_actions: - markdown --- -## Worked investigations +If you want the longer case-study version of the same surface, jump to [Berry Club](/tx/examples/berry-club) for historical board reconstruction or [OutLayer](/tx/examples/outlayer) for worker and callback tracing. -These are intentionally ordered from the simplest anchor to the richest forensic workflow: start with one tx hash, then one receipt, then failure and async patterns, and only after that move into deeper SocialDB and NEAR Intents investigations. +## Start Here + +These are the smallest useful anchors on the page: start with one tx hash, then one receipt ID, and only go deeper when the simpler story stops being enough. ### I have one transaction hash. What happened? @@ -18,6 +20,18 @@ Use this investigation when the user story is as plain as it gets: “someone pa This is the beginner-to-intermediate on-ramp for the page. Before receipts, promise chains, or forensics, there is one simpler skill every NEAR engineer needs: turn a bare tx hash into one short human story. +
+
+ Strategy +

Start with the readable tx record, then drop into RPC or receipts only if the first answer is not enough.

+
+
+

01POST /v0/transactions gives signer, receiver, action types, block height, and the first receipt handoff.

+

02RPC EXPERIMENTAL_tx_status is only for the exact protocol-side success semantics.

+

03POST /v0/receipt only matters if the first receipt becomes the new anchor.

+
+
+ **Goal** - Start from one transaction hash and recover the shortest useful answer: signer, receiver, action type, included block, and whether the transaction handed off into a successful execution path. @@ -54,7 +68,7 @@ flowchart LR - which block included it - one plain-English sentence that explains the transaction without receipt jargon -### Transaction hash to human story shell walkthrough +#### Transaction hash to human story shell walkthrough Use this when you want the shortest possible path from one tx hash to one readable answer. @@ -152,6 +166,18 @@ Use this investigation when all you have is one ugly `receipt_id` from logs, tra If you already have the transaction hash instead of the receipt ID, start with the simpler investigation just above and only drop down to this one when the receipt itself becomes the best anchor. +
+
+ Strategy +

Resolve the receipt first, then recover the parent transaction and stop once the story is readable.

+
+
+

01POST /v0/receipt tells you which transaction and execution block the receipt belongs to.

+

02POST /v0/transactions turns that raw receipt into signer, receiver, and action context.

+

03RPC tx status is optional follow-up only when “human story” turns into “exact protocol semantics.”

+
+
+ **Goal** - Start from one receipt ID and recover the shortest useful story: who created it, where it executed, which transaction spawned it, and what that transaction was actually trying to do. @@ -189,7 +215,11 @@ flowchart LR - whether the receipt was the main event or just one step in a larger cascade - one plain-English sentence that a teammate could read without decoding receipt jargon -### Ugly receipt ID to human story shell walkthrough +#### Ugly receipt ID to human story shell walkthrough + +## Failure and Async + +This is where the page stops being simple lookup and starts teaching NEAR execution semantics: atomic batches, later async failures, and callback order. Use this when you already have one raw `receipt_id` from logs and want to turn it into a readable explanation fast. @@ -279,6 +309,18 @@ Use this investigation when one transaction tried to create and fund a new accou On NEAR, the actions inside one transaction batch execute in order inside the same first action receipt. If one action in that receipt fails, the earlier actions in that same batch do not stick. That is different from later async receipts or promise chains, where the first receipt can succeed and some later receipt can still fail independently. +
+
+ Strategy +

Prove what the batch tried, which action failed, and whether anything from the earlier actions actually stuck.

+
+
+

01POST /v0/transactions shows the ordered batch exactly as the signer submitted it.

+

02RPC EXPERIMENTAL_tx_status shows the failing FunctionCall and the protocol-side failure reason.

+

03RPC view_account on the intended new account proves whether the earlier create, fund, and key-add actions stuck at all.

+
+
+ **Goal** - Prove, from one pinned testnet transaction, that the final `FunctionCall` failed and the earlier `CreateAccount`, `Transfer`, and `AddKey` actions did not stick. @@ -328,7 +370,7 @@ One detail is worth calling out before the shell walkthrough: the indexed transa - proof that the intended new account still does not exist after finality - a short conclusion that the earlier `CreateAccount`, `Transfer`, and `AddKey` actions did not stick once the final `FunctionCall` failed -### Failed batched transaction shell walkthrough +#### Failed batched transaction shell walkthrough Use this when you want one concrete failed batch that you can inspect step by step with public FastNear testnet endpoints. @@ -450,6 +492,18 @@ Use this investigation when one contract call logged success, changed its own lo This is the opposite of the failed batch example above. There, one action failed inside the first action receipt, so nothing in that batch stuck. Here, the first contract receipt really did succeed and its state change really did stick. The failure happened later, in a separate receipt. +
+
+ Strategy +

First get the human timeline, then prove where the async story split.

+
+
+

01POST /v0/transactions gives the easiest first pass: which receipt ran first, and which receipt failed later.

+

02RPC EXPERIMENTAL_tx_status proves the important NEAR nuance that top-level success and later descendant failure can both be true.

+

03RPC call_function on the router contract tells you whether the first receipt's own local state change stuck.

+
+
+ **Goal** - Prove, from one pinned testnet transaction, that `seq-dr.mike.testnet.kickoff_append(...)` succeeded on its own receipt, then a detached `append(...)` call failed one block later with `CodeDoesNotExist`. @@ -497,7 +551,7 @@ One NEAR detail matters here: receipt success is not transitive. `seq-dr.mike.te - that the router's own state still contains `late-failure`, so the first receipt's local side effect stuck - one sentence explaining why this is different from a failed batched transaction -### Later receipt failure shell walkthrough +#### Later receipt failure shell walkthrough Use this when the user story is “the contract call looked fine, but something failed later, and I need to prove exactly where the story split.” @@ -636,6 +690,18 @@ When a NEAR app “looked successful” and still broke later, the thing to ask Use this investigation when one transaction creates promise work for later, a second transaction resumes it, and the real question is not “did both transactions succeed?” but “did the cross-contract callbacks actually run in the order I intended?” +
+
+ Strategy +

Treat the two tx hashes as one async story: prove the work was live, recover the requested order, then compare it with observed downstream state.

+
+
+

01RPC call_function on the deferred-work view proves the promise work was really live before the resume step.

+

02POST /v0/transactions gives both block anchors and the exact order that the resume transaction requested.

+

03RPC EXPERIMENTAL_tx_status plus the downstream recorder view prove where the callbacks actually ran and in what visible order.

+
+
+ **Goal** - Turn two transaction hashes into one readable proof story: what promise work was created, what order the resume call requested, and what order later showed up in downstream contract state. @@ -683,10 +749,26 @@ For NEAR engineers, the important mental model is: the resume transaction tells - the blocks where the observable state changed - any receipt or account pivots the next investigator should keep +## SocialDB Proofs + +These examples start from readable NEAR Social state and walk back to the exact write that made it true. + ### Prove that `mike.near` set `profile.name` to `Mike Purvis`, then recover the SocialDB profile write transaction Use this investigation when the user story is “I can see `Mike Purvis` on `mike.near`'s NEAR Social profile, but I want to prove exactly when that field was written and which transaction wrote it.” +
+
+ Strategy +

Start from the readable field value, then turn its field-level block into one receipt and one write transaction.

+
+
+

01NEAR Social POST /get gives both the current profile.name value and the field-level :block.

+

02POST /v0/block turns that block into the concrete mike.near -> social.near receipt and transaction hash.

+

03POST /v0/transactions proves the write payload, and RPC call_function get confirms the field still resolves that way now.

+
+
+ **Goal** - Start from one readable SocialDB profile field, then recover the exact receipt and originating transaction that wrote it. @@ -715,7 +797,7 @@ For this live example, the current `profile.name` value is `Mike Purvis`, the fi - proof that the write was a `set` call carrying `profile.name` and other profile fields in the same payload - the distinction between the receipt execution block (`78675795`) and the outer transaction inclusion block (`78675794`) -### NEAR Social profile-proof shell walkthrough +#### NEAR Social profile-proof shell walkthrough Use this when you want a concrete, repeatable proof chain from one readable NEAR Social profile field to the exact SocialDB write transaction behind it. @@ -879,6 +961,18 @@ NEAR Social gives you the semantic field value. FastNear block receipts give you Use this investigation when the user story is “I can see that `mike.near` follows `mob.near`, but I want to prove exactly when that follow edge was written and which transaction wrote it.” +
+
+ Strategy +

Start from the semantic follow edge, then use its write block as the bridge back to one receipt and one transaction.

+
+
+

01NEAR Social POST /get gives the readable follow edge and the SocialDB :block where it was written.

+

02POST /v0/block turns that write block into the specific receipt and transaction hash behind the edge.

+

03POST /v0/transactions proves the graph.follow plus index.graph payload, and RPC call_function get confirms the edge still exists now.

+
+
+ **Goal** - Start from the readable NEAR Social follow edge, then recover the exact receipt and originating transaction that wrote it into SocialDB. @@ -907,7 +1001,7 @@ For this live example, the current edge is `mike.near -> mob.near`, the SocialDB - proof that the write was a `set` call carrying both `graph.follow.mob.near` and the matching `index.graph` entry - the distinction between the receipt execution block (`79574924`) and the outer transaction inclusion block (`79574923`) -### NEAR Social follow-proof shell walkthrough +#### NEAR Social follow-proof shell walkthrough Use this when you want a concrete, repeatable proof chain from one readable NEAR Social follow edge to the exact SocialDB write transaction behind it. @@ -1076,6 +1170,18 @@ This is the natural tx-side companion to the lighter RPC widget inspection and t - recover the originating transaction - decode the `set` payload and prove it really carried the widget source +
+
+ Strategy +

Treat the widget’s write block as the whole bridge: block to receipt, receipt to transaction, transaction to source code.

+
+
+

01POST /v0/block starts from the widget block and narrows it to one mob.near -> social.near receipt.

+

02POST /v0/transactions turns that receipt into one readable set payload carrying the widget source.

+

03RPC call_function get is the final current-state confirmation that the widget still exists now.

+
+
+ **Goal** - Turn one widget-level SocialDB block into one readable answer: which transaction wrote `mob.near/widget/Profile`, which receipt executed the write, and what exact widget source appeared in that payload. @@ -1106,7 +1212,11 @@ For this live anchor: - proof that the write payload was a `set` call carrying `mob.near/widget/Profile` - one plain-English sentence like “`mob.near` wrote `widget/Profile` in tx `9QDup...`, and the payload really did store the current profile widget source” -### NEAR Social widget write-proof shell walkthrough +#### NEAR Social widget write-proof shell walkthrough + +## Settlement Trace + +This is the richest single-trace investigation on the page: one live NEAR Intents settlement, from top-level tx to the receipts and events that explain it. Use this when you want to turn one widget block anchor into the exact transaction that wrote it. @@ -1244,6 +1354,18 @@ The widget's write block gives you the bridge. FastNear block receipts turn that Use this investigation when the user story is “I have one `intents.near` transaction. Show me what actually happened on-chain, which contracts participated, and which events prove it.” +
+
+ Strategy +

Treat one settlement like a readable trace before you treat it like protocol theory.

+
+
+

01POST /v0/transactions gives the settlement skeleton: entrypoint, first downstream contracts, and early logs.

+

02POST /v0/block reuses the same anchor when you want the block-level context around that settlement.

+

03RPC EXPERIMENTAL_tx_status is where you go for the canonical receipt DAG and the event names that prove what actually moved.

+
+
+ **Goal** - Start from one fixed `intents.near` transaction and turn it into a readable settlement story: which method kicked things off, which downstream contracts appeared, and which event families tell you what moved. @@ -1302,7 +1424,7 @@ The public FastNear surfaces are enough to answer the practical question: This example intentionally stays on public FastNear surfaces. NEAR Intents Explorer and the 1Click Explorer are useful too, but their Explorer API is JWT-gated and not the right default for a public docs walkthrough. -### NEAR Intents settlement shell walkthrough +#### NEAR Intents settlement shell walkthrough Use this when you want one concrete `intents.near` settlement that you can inspect immediately with public FastNear endpoints. diff --git a/docs/tx/outlayer.mdx b/docs/tx/outlayer.mdx new file mode 100644 index 0000000..a22022e --- /dev/null +++ b/docs/tx/outlayer.mdx @@ -0,0 +1,274 @@ +--- +sidebar_label: OutLayer +slug: /tx/examples/outlayer +title: "OutLayer: Trace one request from caller to callback" +description: "Use Transactions API and RPC to trace a real OutLayer request from the initial FunctionCall through the worker transaction and into the callback and refund phase." +displayed_sidebar: transactionsApiSidebar +page_actions: + - markdown +keywords: + - OutLayer + - FastNear + - NEAR + - RPC + - Transactions API + - callback + - promise + - receipt +--- + +import Link from '@site/src/components/LocalizedLink'; + +{/* FASTNEAR_AI_DISCOVERY: This case study shows how to use FastNear RPC and Transactions API to trace a live OutLayer execution in NEAR-native terms. It separates the visible request/worker/callback flow that FastNear can trace now from the documented under-the-hood yield/resume and CKD/MPC trust path. */} + +# OutLayer: Trace one request from caller to callback + +Use this when the question is: “I can see OutLayer on-chain. Which transaction opened the work, which later transaction came from the worker, and where did callback, charging, and refund behavior show up?” + +This is the advanced async case study in the Transactions examples family. Keep the NEAR frame first: one caller-side `FunctionCall`, one later worker-side transaction, and receipt-level follow-up only when you need to inspect the finish. + +
+
+ Strategy +

Find the caller tx and the worker tx first, then use receipts only when the finish path becomes the real question.

+
+
+

01POST /v0/account is the fastest way to discover the caller-side and worker-side hashes that belong to the same story.

+

02POST /v0/transactions hydrates both hashes and shows the readable request, worker resolution, and early logs.

+

03Only after that do you inspect receipt-level callback, charging, and refund behavior or fall back to exact RPC identity checks.

+
+
+ +Useful references: + +- Account History +- Transactions by Hash +- View Account +- [OutLayer NEAR Integration](https://outlayer.fastnear.com/docs/near-integration) +- [OutLayer Secrets / CKD](https://outlayer.fastnear.com/docs/secrets) + +## The short version + +If you run into OutLayer on-chain, the practical questions are usually: + +- which transaction created the async work item? +- which later transaction came from the worker? +- where did callback, charging, and refund behavior show up? + +That is not a current-state question. It is an execution-history question. + +The useful FastNear move is to pair one caller-side `request_execution` transaction with one worker-side resolution transaction, and then drop to receipt inspection only for the finish. + +```mermaid +sequenceDiagram + autonumber + participant Caller as "Caller" + participant Outlayer as "outlayer.*" + participant Worker as "worker.outlayer.*" + participant Near as "NEAR runtime" + + Caller->>Outlayer: FunctionCall request_execution + Outlayer-->>Near: establish async work + accounting context + Worker->>Outlayer: resolve_execution or submit_execution_output_and_resolve + Outlayer-->>Near: completion logs, callback path, charging, refunds +``` + +Everything above is visible today with FastNear and RPC. + +## 1. Hydrate one request transaction and one worker resolution + +If you want the whole shape immediately, start with a known pair of hashes and hydrate both. + +This pair worked on April 18, 2026: + +- `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` — caller-side `request_execution` +- `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` — worker-side `submit_execution_output_and_resolve` + +```bash title="Hydrate a request hash and a worker-resolution hash" +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{ + "tx_hashes":[ + "AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4", + "AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs" + ] + }' | jq '.transactions[] | { + hash: .transaction.hash, + signer: .transaction.signer_id, + receiver: .transaction.receiver_id, + actions: [.transaction.actions[] | keys[0]], + logs: (.receipts[0].execution_outcome.outcome.logs[:2]) + }' +``` + +In the sampled output: + +- the request hash came from `solarflux.near` to `outlayer.near` +- the logs showed a resolved project: `zavodil.near/near-email` +- the worker hash came from `worker.outlayer.near` to `outlayer.near` +- the worker logs said `Stored pending output` and `Resolving execution ... (combined flow)` + +That is already the visible loop: the original `FunctionCall` established the async work item, the worker came back later as a separate signer, and the contract resolved the result on-chain. + +If you only copy one command from this page, copy this one. + +## 2. Find the two hashes yourself + +If you do not already have a pair of hashes, switch to Transactions API: Account History. + +```bash title="Recent mainnet activity for outlayer.near" +curl -sS https://tx.main.fastnear.com/v0/account \ + -H 'content-type: application/json' \ + --data '{"account_id":"outlayer.near","desc":true}' \ + | jq '{txs_count, first: .account_txs[0]}' +``` + +On April 18, 2026 this surface reported more than 5,000 traced transactions for `outlayer.near`, and the newest sampled hash was: + +```text +AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs +``` + +That hash was not the original user request. It was already a worker-side follow-up. + +That is why account history is the right first search surface: you are not trying to summarize the contract, you are trying to find two concrete transactions in one execution story. + +```mermaid +flowchart TD + A["Transactions API: /v0/account for outlayer.*"] --> B["Find recent transaction hashes"] + B --> C["Caller-side request_execution hash"] + B --> D["Worker-side resolution hash"] + C --> E["Hydrate both with /v0/transactions"] + D --> E + E --> F["Inspect logs, request_id, project/source, callback receipts, charging, refunds"] +``` + +## 3. Inspect the callback and refund phase + +If you want to go past “a worker called back,” inspect the receipt list on the hydrated worker transaction. + +```bash title="Show receipt-level follow-up for the worker resolution" +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{"tx_hashes":["AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs"]}' \ + | jq '.transactions[0] | { + hash: .transaction.hash, + receipts: [ + .receipts[] | { + predecessor: .receipt.predecessor_id, + receiver: .receiver_id, + actions: [.receipt.receipt.Action.actions[] | keys[0]], + logs: .execution_outcome.outcome.logs + } + ] + }' +``` + +What to look for: + +- `FunctionCall:on_execution_response` +- charging logs such as `[[yNEAR charged: "..."]]` +- completion events such as `execution_completed` +- follow-up `Transfer` receipts + +This is where `receipt` becomes the right abstraction: not at the beginning of the tutorial, but when you are debugging the actual finish path. + +## 4. Confirm the contract identity if you need it + +Use raw RPC when you want exact account identity and code-hash checks. This is the identity step, not the history step. + +```bash title="Mainnet: view_account for outlayer.near" +curl -sS https://rpc.mainnet.fastnear.com \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "id":"1", + "method":"query", + "params":{ + "request_type":"view_account", + "finality":"final", + "account_id":"outlayer.near" + } + }' | jq '.result | {amount, locked, code_hash, storage_usage}' +``` + +```bash title="Testnet: view_account for outlayer.testnet" +curl -sS https://rpc.testnet.fastnear.com \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "id":"1", + "method":"query", + "params":{ + "request_type":"view_account", + "finality":"final", + "account_id":"outlayer.testnet" + } + }' | jq '.result | {amount, locked, code_hash, storage_usage}' +``` + +As of April 18, 2026, both contracts returned the same code hash: + +```text +94uKcoDB3QbEpxDj1xsw9CQwu9bAY1PoVPr2BZYRRv4K +``` + +That is a strong hint that the same contract binary is deployed on both networks. + +## 5. What is happening under the hood? + +The visible story above is what a NEAR builder needs first. The deeper story explains why this flow is interesting. + +### Observable now + +FastNear and RPC can already show you: + +- the caller-side `request_execution` +- the worker-side `resolve_execution` or `submit_execution_output_and_resolve` +- the finish receipts where callback, charging, and refund behavior materialize + +Your own integration is still ordinary NEAR async composition: call `outlayer.*`, then handle your callback. + +### Documented under the hood + +The OutLayer docs describe a deeper internal model: `outlayer.*` uses NEAR yield/resume semantics as its own internal async boundary, off-chain work runs inside TEE workers, and protected secrets use a separate keystore trust path backed by DAO-gated CKD requests to the NEAR MPC signer. + +The important precision for NEAR readers is that yield/resume is not being presented here as something your caller contract directly writes. NEAR's yield/resume primitives are same-account primitives, so if that mechanism is used here, the yielded and resumed actor is `outlayer.*`, not the original caller contract. For the raw runtime model, see Advanced Features. + +The Secrets / CKD docs describe that keystore path as two-level: the keystore gets a derivation key through a DAO-gated MPC path, then uses that cached derivation capability for protected secrets during app executions. That is a trust-path explanation, not a claim that every ordinary OutLayer execution makes a fresh DAO -> MPC round trip. + +The live public gateway account for that keystore / DAO path is still unresolved in our current public chain evidence, so keep that part in the documented-under-the-hood bucket rather than the observable-now bucket. + +```mermaid +flowchart TB + subgraph Observable["Observable now with FastNear / RPC"] + Caller["User or calling contract"] -->|FunctionCall request_execution| Entry["outlayer.*"] + Entry -->|later worker activity| Worker["worker.outlayer.*"] + Worker -->|resolve_execution or submit_execution_output_and_resolve| Entry + Entry -->|on_execution_response, charging, refund transfers| Finish["Callback / finish receipts"] + end + + subgraph Internal["Documented under the hood"] + Yield["outlayer.* internal yield point"] --> TEE["TEE worker executes WASM"] + TEE -->|resume with output| Yield + TEE --> Keystore["TEE keystore for protected secrets"] + Keystore -->|documented DAO-gated request_key| DAO["DAO gateway (documented)"] + DAO -->|documented CKD request| MPC["v1.signer / v1.signer-prod.testnet"] + MPC -->|derivation key for keystore| Keystore + end + + Entry -. same logical execution .-> Yield + + classDef observable fill:#e7f1ff,stroke:#2563eb,color:#0f172a; + classDef internal fill:#fff4e5,stroke:#b45309,color:#0f172a; + class Caller,Entry,Worker,Finish observable; + class Yield,TEE,Keystore,DAO,MPC internal; +``` + +## Read deeper + +- Transactions API for account history, receipts, and transaction hydration +- Advanced Features for NEAR yield/resume semantics +- Async Model for promise and callback vocabulary +- [OutLayer NEAR Integration](https://outlayer.fastnear.com/docs/near-integration) for the documented contract-facing interface +- [OutLayer Secrets / CKD](https://outlayer.fastnear.com/docs/secrets) for the documented keystore, DAO, and MPC trust path diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index 212f237..eb20bc6 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -10,12 +10,24 @@ page_actions: ## Готовые сценарии -Эти сценарии выстроены от самого быстрого read-only-запроса к более насыщенному сценарию с изменением состояния. +Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. ### Определить аккаунт по публичному ключу, а затем получить сводку по нему Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» +
+
+ Стратегия +

Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку.

+
+
+

01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа.

+

02jq поднимает тот аккаунт, который вы хотите смотреть дальше.

+

03GET /v1/account/.../full в одном ответе показывает балансы, NFT и стейкинг.

+
+
+ **Что вы делаете** - Ищете по публичному ключу один или несколько `account_id`. @@ -56,6 +68,18 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». +
+
+ Стратегия +

Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк.

+
+
+

01GET /v1/account/.../staking находит прямую экспозицию через пулы.

+

02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них.

+

03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed.

+
+
+ **Сеть** - mainnet @@ -132,6 +156,18 @@ jq -n \ Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». +
+
+ Стратегия +

Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы.

+
+
+

01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции.

+

02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB.

+

03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner.

+
+
+ **Сети** - mainnet для чтения виджета из `social.near` diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index fcea83f..0efd2cd 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -14,6 +14,18 @@ page_actions: Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и финальную проверку через `view_state`. +
+
+ Стратегия +

Начните с одного точного ключа, расширяйтесь только до его истории и завершайте одной проверкой текущего chain-state.

+
+
+

01get-latest-key даёт самую новую индексированную запись по точному ключу.

+

02get-history-key или history-by-key показывают, как тот же ключ менялся во времени.

+

03RPC view_state — это финальное точное чтение, когда нужно сравнить индексированную историю с тем, что цепочка возвращает прямо сейчас.

+
+
+ **Цель** - Объяснить, как этот ключ выглядит в индексе, как он менялся и совпадает ли с этим `view_state` прямо сейчас. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index c626749..65af47d 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -14,6 +14,18 @@ page_actions: Используйте это расследование, когда нужно заметить новый блок как можно раньше, но финальный ответ всё равно должен опираться на финализированный блок и иногда на точное чтение через RPC. +
+
+ Стратегия +

Пусть NEAR Data сначала скажет, что что-то изменилось, а затем переиспользуйте то же семейство блоков для стабильного подтверждения.

+
+
+

01block-optimistic или last-block-optimistic дают самый ранний полезный сигнал.

+

02block или last-block-final подтверждают, что то же наблюдение дошло до финализированной истории.

+

03RPC block нужен только в самом конце, когда уже известна точная высота или хеш.

+
+
+ **Цель** - Быстро заметить недавний блок, а затем проверить то же самое, когда догонит finality. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index 55c8823..d6745a2 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -12,12 +12,26 @@ page_actions: Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. -## Готовые сценарии +## Механика аккаунтов и ключей + +Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. ### Проверить и удалить старые function-call-ключи Near Social Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. +
+
+ Стратегия +

Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление.

+
+
+

01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near.

+

02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта.

+

03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат.

+
+
+ **Что вы делаете** - Через сам RPC получаете полный список access key аккаунта. @@ -257,10 +271,240 @@ fi Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. +### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? + +Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. + +
+
+ Стратегия +

Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно.

+
+
+

01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории.

+

02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта.

+

03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey.

+
+
+ +**Что вы делаете** + +- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. +- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. +- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. +- Подтягиваете кандидата по транзакциям и различаете три разных ключа: + - ключ, который был добавлен + - public key верхнеуровневого signer + - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` + +Сразу важны три детали про nonce: + +- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. +- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. +- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' + +# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: +# export ACCOUNT_ID=mike.near +# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' +``` + +1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$TARGET_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" + } + }')" \ + | tee /tmp/key-origin-view.json >/dev/null + +CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" +ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" +SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" +SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" + +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_public_key "$TARGET_PUBLIC_KEY" \ + --argjson current_nonce "$CURRENT_NONCE" \ + --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ + --argjson search_from "$SEARCH_FROM" \ + --argjson search_to "$SEARCH_TO" \ + --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ + account_id: $account_id, + target_public_key: $target_public_key, + current_nonce: $current_nonce, + estimated_receipt_block: $estimated_receipt_block, + search_from_tx_block_height: $search_from, + search_to_tx_block_height: $search_to, + permission: ($permission | fromjson) + }' +``` + +Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. + +2. Ищите историю аккаунта только внутри этого диапазона блоков. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_tx_block_height "$SEARCH_FROM" \ + --argjson to_tx_block_height "$SEARCH_TO" '{ + account_id: $account_id, + is_real_signer: true, + from_tx_block_height: $from_tx_block_height, + to_tx_block_height: $to_tx_block_height, + desc: false, + limit: 50 + }')" \ + | tee /tmp/key-origin-candidates.json >/dev/null + +jq '{ + txs_count, + candidate_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_signer, + is_real_signer, + is_predecessor, + is_receiver + } + ] +}' /tmp/key-origin-candidates.json +``` + +Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. + +3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. + +```bash +TX_HASHES_JSON="$( + jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json +)" + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ + | tee /tmp/key-origin-transactions.json >/dev/null + +jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "direct", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $tx.transaction.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }), + ($tx.transaction.actions[]? + | .Delegate? + | .delegate_action as $delegate + | $delegate.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "delegated", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $delegate.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }) + ) + | { + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + tx_block_hash: $tx.execution_outcome.block_hash, + receiver_id: $tx.transaction.receiver_id + } + . +' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json +``` + +Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. + +Для примерного ключа `mike.near` выше совпадение оказывается делегированным: + +- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` +- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` +- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` +- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` + +4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. + +```bash +ADD_KEY_RECEIPT_ID="$( + jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | .receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) + | .receipt.receipt_id + ' /tmp/key-origin-transactions.json | head -n 1 +)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receipt_block_height: .receipt.block_height, + tx_block_height: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }' +``` + +Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. + +**Зачем нужен следующий шаг?** + +Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. + ### Проверить регистрацию FT storage и затем перевести токены Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». +
+
+ Стратегия +

Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу.

+
+
+

01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас.

+

02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит.

+

03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог.

+
+
+ **Сеть** - testnet @@ -558,10 +802,26 @@ curl -s "$RPC_URL" \ Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +## Точные чтения NEAR Social и BOS + +Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. + ### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». +
+
+ Стратегия +

Спросите у social.near ровно о двух вещах, которые важны до подписи.

+
+
+

01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию.

+

02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near.

+

03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer.

+
+
+ Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: - есть ли у целевого аккаунта storage на `social.near`? @@ -752,6 +1012,18 @@ jq -n \ Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». +
+
+ Стратегия +

Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой.

+
+
+

01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*.

+

02RPC call_function get читает точный исходник widget/Profile.

+

03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples.

+
+
+ **Официальные ссылки** - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx index db5e491..5babf4c 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx @@ -14,6 +14,18 @@ page_actions: Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. +
+
+ Стратегия +

Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него.

+
+
+

01Сначала решите, нужен ли вам optimized fast-rpc, обычный RPC или архивный режим.

+

02Если нужен архив, сначала зафиксируйте одну точную высоту snapshot-блока и дальше переиспользуйте только её.

+

03Выполняйте только команды выбранного пути и не смешивайте optimized, standard и archival шаги в одном сценарии.

+
+
+ **Цель** - Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md index f693e59..a72576f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -14,6 +14,18 @@ page_actions: Используйте этот сценарий, когда история звучит так: «я вижу, что средства двигались, но хочу получить точную опорную точку исполнения для этого движения, не затягивая сразу всю историю аккаунта». +
+
+ Стратегия +

Сначала оставайтесь на узкой истории движения, а затем один раз переключайтесь в историю исполнения.

+
+
+

01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять.

+

02jq поднимает один receipt_id, не затягивая остальную историю аккаунта.

+

03POST /v0/receipt превращает это движение в опорную точку исполнения, которую уже можно продолжать в /tx.

+
+
+ **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx new file mode 100644 index 0000000..32e7684 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx @@ -0,0 +1,264 @@ +--- +sidebar_label: Berry Club +slug: /tx/examples/berry-club +title: "Berry Club: как восстанавливать исторические доски" +description: "Используйте Transactions API, RPC get_lines и проигрывание draw-вызовов, чтобы восстанавливать доски Berry Club по историческим эпохам." +displayed_sidebar: transactionsApiSidebar +page_actions: + - markdown +keywords: + - Berry Club + - FastNear + - Transactions API + - RPC + - get_lines + - draw + - pixel board + - historical snapshots +--- + +import Link from '@site/src/components/LocalizedLink'; +import BerryClubSnapshotGallery from '@site/src/components/BerryClubSnapshotGallery'; +import berryClubSnapshots from '@site/src/data/berryClubSnapshots.json'; + +{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как восстанавливать доски Berry Club через FastNear. Он отделяет текущее состояние из get_lines от исторического разбора через диапазоны блоков, историю аккаунта, раскрытие транзакций и проигрывание draw-аргументов. */} + +# Berry Club: как восстанавливать исторические доски + +Используйте этот разбор, когда вопрос звучит так: «как Berry Club выглядел в определённую эпоху и какие `draw`-вызовы сделали доску именно такой?» + +Это read-only разбор из семейства Transactions examples. Если нужна только доска прямо сейчас, используйте `get_lines` и остановитесь. Если нужно объяснить, как доска пришла к такому виду, переключайтесь на историю блоков, историю аккаунта, раскрытые `draw`-вызовы и проигрывание. + +
+
+ Стратегия +

Сначала прочитайте живую доску, затем ограничьте эпоху и только после этого проигрывайте draw-вызовы, которые её объясняют.

+
+
+

01RPC call_function get_lines даёт текущую доску 50x50 и показывает, как выглядит «сейчас».

+

02POST /v0/blocks вместе с POST /v0/account ограничивают одну эпоху и дают кандидатные хеши draw.

+

03POST /v0/transactions раскрывает эти draw-вызовы, чтобы их можно было проиграть в исторические контрольные точки.

+
+
+ +Держите рядом: + +- [js.fastnear.com](https://js.fastnear.com/) +- [fastnear/js-monorepo](https://github.com/fastnear/js-monorepo) +- Transactions API: история аккаунта +- Transactions API: транзакции по хешу +- Transactions API: диапазон блоков +- RPC: call_function + +В этом руководстве история Berry Club разбирается только на mainnet. Снимки ниже собраны из воспроизводимых данных mainnet, которые уже сохранены в этом репозитории. + +## Короткая версия + +Berry Club даёт чистый view текущего состояния через `get_lines`, но не даёт готового эндпоинта вида «доска на блоке N». + +Из-за этого задача делится на две части: + +- используйте RPC `call_function`, когда вопрос звучит как «как доска выглядит сейчас?» +- используйте индексированную историю, когда вопрос звучит как «какие записи привели к этой доске?» +- используйте архивный RPC только тогда, когда нужно напрямую материализовать уже известную контрольную точку + +```mermaid +flowchart TD + A["RPC call_function: get_lines"] --> B["Текущая доска 50x50"] + C["Transactions API: /v0/blocks"] --> D["Ограничить эпоху"] + D --> E["/v0/account для berryclub.ek.near"] + E --> F["Кандидатные хеши draw-транзакций"] + F --> G["Раскрытие через /v0/transactions"] + G --> H["Проигрывание draw-записей в историческую доску"] +``` + +## Почему Berry Club хорошо учит истории в NEAR + +Berry Club удобно показывает обе стороны задачи: + +- чистое чтение текущего состояния через `get_lines` +- длинную историю вызовов `draw` с обычными аргументами `FunctionCall` +- формат доски, который легко декодировать и рендерить обычным JavaScript + +Это очень NEAR-подобная форма: один view-метод для текущего состояния, один write-метод для изменений и индексированная история, когда нужно объяснить, как это состояние вообще появилось. + +## 1. Сначала прочитайте текущую доску + +Живое демо использует `berryclub.ek.near` и читает доску через view-вызов `get_lines`: + +```javascript +await near.view({ + contractId: 'berryclub.ek.near', + methodName: 'get_lines', + args: { + lines: [...Array(50).keys()], + }, +}); +``` + +Это путь текущего состояния. Он не отвечает на вопрос, как доска пришла к такому виду. + +| Вопрос | Лучшая поверхность | Почему | +| --- | --- | --- | +| как доска выглядит сейчас? | RPC `call_function` | контракт уже отдаёт текущее состояние через `get_lines` | +| какие `draw` были в этой эпохе? | `/v0/account` + `/v0/transactions` | индексированная история даёт ограниченный набор записей и раскрытые аргументы | +| как доска выглядела в известной контрольной точке? | архивный RPC или полное проигрывание | можно напрямую материализовать состояние из архива или восстановить его самому по историческим записям | + +## 2. Как декодировать `get_lines` в сетку 50x50 + +Полезная часть Berry Club-разметки из `js.fastnear.com` — это декодер строк: + +- каждая строка приходит в base64 +- её нужно декодировать в байты +- первые 4 байта нужно пропустить +- дальше цвета читаются как 32-битные little-endian значения каждые 8 байт + +```javascript +function decodeLine(encodedLine) { + const bytes = Buffer.from(encodedLine, 'base64'); + const colors = []; + + for (let offset = 4; offset < bytes.length; offset += 8) { + colors.push(bytes.readUInt32LE(offset) & 0xffffff); + } + + return colors; +} +``` + +Примените это ко всем 50 строкам — и получите полную сетку 50x50, готовую к рендерингу. + +## 3. Ограничьте эпоху, которую хотите изучить + +Сначала ограничьте эпоху, прежде чем искать draw-записи. Проверочный снимок запуска в этом репозитории находится на блоке `21898354`, а средний снимок — на блоке `97601515`. + +Сначала зафиксируйте ближайший диапазон блоков: + +```bash +curl -sS https://tx.main.fastnear.com/v0/blocks \ + -H 'content-type: application/json' \ + --data '{ + "from_block_height": 21898350, + "to_block_height": 21898355, + "desc": false, + "limit": 5 + }' +``` + +Затем переключитесь на историю аккаунта и запросите активность Berry Club внутри ограниченного диапазона блоков: + +```bash +curl -sS https://tx.main.fastnear.com/v0/account \ + -H 'content-type: application/json' \ + --data '{ + "account_id": "berryclub.ek.near", + "is_function_call": true, + "is_receiver": true, + "is_real_receiver": true, + "from_tx_block_height": 97576515, + "to_tx_block_height": 97601516, + "desc": true, + "limit": 40 + }' +``` + +Здесь полезна именно такая последовательность: + +- `/v0/blocks` помогает понять соседство по высотам блоков +- `/v0/account` возвращает кандидатные хеши транзакций Berry Club внутри этого диапазона + +## 4. Раскройте транзакции и оставьте только `draw` + +Когда кандидатные хеши уже есть, раскройте их и оставьте только верхнеуровневые вызовы `draw`, где получатель — `berryclub.ek.near`. + +Аргументы вызова — это обычные данные `FunctionCall` вида `{ pixels: [...] }`: + +```bash +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{ + "tx_hashes": [ + "Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ", + "8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL" + ] + }' | jq '.transactions[] + | select(.transaction.receiver_id == "berryclub.ek.near") + | .transaction.actions[]?.FunctionCall + | select(.method_name == "draw") + | { + method_name, + args: (.args | @base64d | fromjson) + }' +``` + +Это даёт всё, что нужно для проигрывания: + +- какая транзакция записывала пиксели +- какие координаты были затронуты +- какие цвета были записаны + +## 5. Проиграйте исторические `draw`-вызовы в доску + +Для полного проигрывания держите в памяти массив 50x50 и применяйте раскрытые транзакции `draw` от старых к новым. + +```javascript +const board = Array.from({ length: 50 }, () => Array(50).fill(0)); + +function applyDraw(boardState, drawArgs) { + for (const pixel of drawArgs.pixels) { + if (pixel.x < 0 || pixel.x >= 50 || pixel.y < 0 || pixel.y >= 50) { + continue; + } + + boardState[pixel.y][pixel.x] = pixel.color; + } +} + +for (const drawTx of drawTransactionsOldestFirst) { + applyDraw(board, drawTx.args); +} +``` + +Важно не путать два разных пути: + +- `get_lines` — это текущее состояние +- `tx/account` плюс `tx/transactions` — это материал для проигрывания + +## 6. Готовые контрольные точки по эпохам + +Галерея ниже использует уже сохранённые данные снимков, собранные из mainnet-истории Berry Club: + +- `launch` — последний успешный `draw` в пределах первых 24 часов после первого успешного draw +- `mid` — последний успешный `draw` не позже средней временной точки всей истории Berry Club +- `recent` — последний успешный `draw`, который увидел скрипт при пересборке снимков + + + +Сейчас эти снимки привязаны к таким транзакциям: + +- `launch`: `BDNFpCpLXjBrgjR6z6wCZmB9EWdHnVMdqau3iTWTRE5H` на блоке `21898354` +- `mid`: `Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ` на блоке `97601515` +- `recent`: `8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL` на блоке `194588754` + +## Куда идти за подписанными взаимодействиями + +Эта страница должна оставаться в режиме чтения. + +Если нужны живые подписанные сценарии для `draw` и `buy_tokens`, переходите сюда: + +- [js.fastnear.com](https://js.fastnear.com/) +- [Berry Club example в fastnear/js-monorepo](https://github.com/fastnear/js-monorepo/tree/main/examples/static/berryclub) + +Именно там уместны кошелёк и подписанные действия. Эта страница посвящена историческому восстановлению. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index 07ab0af..59b4510 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -2,15 +2,17 @@ sidebar_label: Examples slug: /tx/examples title: "Примеры Transactions API" -description: "Пошаговые расследования для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents." +description: "Пошаговые расследования и подробные разборы для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents." displayed_sidebar: transactionsApiSidebar page_actions: - markdown --- -## Готовые расследования +Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](/tx/examples/outlayer) для трассировки воркера и callback-цепочки. -Эти расследования намеренно выстроены от самого простого якоря к самой насыщенной форензике: сначала один tx hash, затем один receipt, затем паттерны с ошибками и async, и только потом более глубокие расследования по SocialDB и NEAR Intents. +## С чего начать + +Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. ### У меня есть один хеш транзакции. Что вообще произошло? @@ -18,6 +20,18 @@ page_actions: Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. +
+
+ Стратегия +

Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно.

+
+
+

01POST /v0/transactions даёт signer, receiver, типы действий, высоту блока и первую receipt-точку передачи.

+

02RPC EXPERIMENTAL_tx_status нужен только для точной протокольной семантики успеха.

+

03POST /v0/receipt имеет смысл только тогда, когда именно первая receipt становится новой опорной точкой.

+
+
+ **Цель** - Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. @@ -54,7 +68,7 @@ flowchart LR - в какой блок попала - одно простое предложение, которое объясняет транзакцию без receipt-жаргона -### Shell-сценарий: от хеша транзакции к человеческой истории +#### Shell-сценарий: от хеша транзакции к человеческой истории Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. @@ -152,6 +166,18 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. +
+
+ Стратегия +

Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой.

+
+
+

01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt.

+

02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий.

+

03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола».

+
+
+ **Цель** - Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. @@ -189,7 +215,11 @@ flowchart LR - была ли квитанция главным событием или только шагом в большом каскаде - одно предложение простым языком, которое можно без правок вставить коллеге в чат -### Shell-сценарий: от страшного receipt ID к человеческой истории +#### Shell-сценарий: от страшного receipt ID к человеческой истории + +## Ошибки и async + +Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и порядок callback-ов. Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. @@ -279,6 +309,18 @@ jq -r ' В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. +
+
+ Стратегия +

Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов.

+
+
+

01POST /v0/transactions показывает упорядоченный пакет ровно в том виде, в каком его подписал signer.

+

02RPC EXPERIMENTAL_tx_status показывает падающий FunctionCall и точную причину отказа на уровне протокола.

+

03RPC view_account по предполагаемому новому аккаунту доказывает, закрепились ли вообще ранние create, fund и add-key действия.

+
+
+ **Цель** - На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. @@ -328,7 +370,7 @@ flowchart LR - доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality - короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` -### Shell-сценарий неудачной транзакции с пакетом действий +#### Shell-сценарий неудачной транзакции с пакетом действий Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. @@ -450,6 +492,18 @@ jq '{ Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. +
+
+ Стратегия +

Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась.

+
+
+

01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже.

+

02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой.

+

03RPC call_function на роутере показывает, закрепилось ли собственное локальное изменение состояния из первой receipt.

+
+
+ **Цель** - Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. @@ -497,7 +551,7 @@ flowchart LR - что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился - одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции -### Shell-сценарий более позднего сбоя receipt +#### Shell-сценарий более позднего сбоя receipt Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». @@ -636,6 +690,18 @@ jq '{ Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» +
+
+ Стратегия +

Смотрите на два хеша как на одну async-историю: докажите, что работа была жива, восстановите запрошенный порядок и сравните его с видимым downstream-state.

+
+
+

01RPC call_function на view отложенной работы доказывает, что promise-работа действительно уже была жива до resume-шага.

+

02POST /v0/transactions даёт оба block-anchor и точный порядок, который запросила resume-транзакция.

+

03RPC EXPERIMENTAL_tx_status вместе с downstream-view доказывают, где callback-и реально выполнились и в каком видимом порядке.

+
+
+ **Цель** - Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. @@ -683,10 +749,26 @@ flowchart LR - в каких блоках стали видны изменения состояния - какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования +## Доказательства по SocialDB + +Эти примеры стартуют с читаемого состояния в NEAR Social и откатываются назад к точной записи, которая это состояние создала. + ### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». +
+
+ Стратегия +

Начните с читаемого значения поля, а затем превратите его field-level block в один receipt и одну транзакцию записи.

+
+
+

01NEAR Social POST /get даёт текущее значение profile.name и field-level :block.

+

02POST /v0/block превращает этот блок в конкретный receipt и хеш транзакции mike.near -> social.near.

+

03POST /v0/transactions доказывает payload записи, а RPC call_function get подтверждает, что поле и сейчас разрешается так же.

+
+
+ **Цель** - Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. @@ -715,7 +797,7 @@ flowchart LR - доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload - различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) -### Shell-сценарий доказательства поля профиля в NEAR Social +#### Shell-сценарий доказательства поля профиля в NEAR Social Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. @@ -879,6 +961,18 @@ NEAR Social даёт семантическое значение поля. FastN Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». +
+
+ Стратегия +

Начните с семантической связи подписки, а затем используйте блок записи как мост назад к одному receipt и одной транзакции.

+
+
+

01NEAR Social POST /get даёт читаемую связь подписки и SocialDB :block, где она была записана.

+

02POST /v0/block превращает этот блок записи в конкретный receipt и хеш транзакции за этой связью.

+

03POST /v0/transactions доказывает payload с graph.follow и index.graph, а RPC call_function get подтверждает, что связь и сейчас существует.

+
+
+ **Цель** - Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. @@ -907,7 +1001,7 @@ NEAR Social даёт семантическое значение поля. FastN - доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` - различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) -### Shell-сценарий доказательства подписки в NEAR Social +#### Shell-сценарий доказательства подписки в NEAR Social Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. @@ -1076,6 +1170,18 @@ NEAR Social даёт семантическую связь. FastNear block recei - восстанавливаем исходную транзакцию - декодируем payload `set` и доказываем, что он действительно нёс исходник виджета +
+
+ Стратегия +

Смотрите на write-block виджета как на весь мост сразу: блок в receipt, receipt в транзакцию, транзакцию в исходник.

+
+
+

01POST /v0/block начинает с блока виджета и сужает его до одного receipt mob.near -> social.near.

+

02POST /v0/transactions превращает этот receipt в один читаемый payload set с исходником виджета.

+

03RPC call_function get — это финальное подтверждение текущего состояния, что виджет и сейчас существует.

+
+
+ **Цель** - Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. @@ -1106,7 +1212,11 @@ NEAR Social даёт семантическую связь. FastNear block recei - доказательство, что payload записи был `set` с `mob.near/widget/Profile` - одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» -### Shell-сценарий доказательства записи виджета в NEAR Social +#### Shell-сценарий доказательства записи виджета в NEAR Social + +## Трассировка расчёта + +Это самое насыщенное расследование на странице: один живой расчёт NEAR Intents от верхнеуровневой транзакции до receipts и событий, которые его объясняют. Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. @@ -1244,6 +1354,18 @@ jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». +
+
+ Стратегия +

Смотрите на один расчёт как на читаемую трассу, а не как на теорию протокола с первой строки.

+
+
+

01POST /v0/transactions даёт каркас расчёта: входную точку, первые downstream-контракты и ранние логи.

+

02POST /v0/block переиспользует тот же якорь, когда нужен контекст включающего блока вокруг этого расчёта.

+

03RPC EXPERIMENTAL_tx_status нужен там, где уже требуется канонический DAG по receipt и имена событий, которые доказывают реальное движение активов.

+
+
+ **Цель** - Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. @@ -1302,7 +1424,7 @@ flowchart LR Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. -### Shell-сценарий расчёта NEAR Intents +#### Shell-сценарий расчёта NEAR Intents Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx new file mode 100644 index 0000000..f623bca --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx @@ -0,0 +1,274 @@ +--- +sidebar_label: OutLayer +slug: /tx/examples/outlayer +title: "OutLayer: как проследить один запрос от вызова до callback" +description: "Используйте Transactions API и RPC, чтобы проследить реальный OutLayer-запрос от исходного FunctionCall через транзакцию воркера до callback- и refund-фазы." +displayed_sidebar: transactionsApiSidebar +page_actions: + - markdown +keywords: + - OutLayer + - FastNear + - NEAR + - RPC + - Transactions API + - callback + - promise + - receipt +--- + +import Link from '@site/src/components/LocalizedLink'; + +{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как использовать FastNear RPC и Transactions API, чтобы разбирать живое исполнение OutLayer в терминах NEAR. Он отделяет видимый request/worker/callback-поток, который уже можно трассировать через FastNear, от задокументированного внутреннего пути yield/resume и CKD/MPC. */} + +# OutLayer: как проследить один запрос от вызова до callback + +Используйте этот разбор, когда вопрос звучит так: «я вижу OutLayer в цепочке. Какая транзакция открыла работу, какая более поздняя транзакция пришла от воркера и где проявились callback, списание и возврат средств?» + +Это продвинутый разбор асинхронного исполнения в семействе Transactions examples. Держите NEAR-рамку первой: один `FunctionCall` со стороны вызывающего, одна более поздняя транзакция со стороны воркера, и квитанции только тогда, когда действительно нужно разбирать фазу завершения. + +
+
+ Стратегия +

Сначала найдите caller-транзакцию и worker-транзакцию, а receipts подключайте только тогда, когда настоящим вопросом становится finish-путь.

+
+
+

01POST /v0/account — самый быстрый способ найти caller-side и worker-side хеши из одной и той же истории.

+

02POST /v0/transactions раскрывает оба хеша и показывает читаемые request, worker-resolution и ранние логи.

+

03Только после этого имеет смысл разбирать callback, списание и refund на уровне receipts или уходить в точные RPC-проверки идентичности.

+
+
+ +Полезные ссылки: + +- История аккаунта +- Транзакции по хешу +- Просмотр аккаунта +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) + +## Короткая версия + +Если вы видите активность OutLayer в цепочке, практические вопросы обычно такие: + +- какая транзакция создала асинхронную единицу работы? +- какая более поздняя транзакция пришла от воркера? +- где именно проявились callback, списание и возврат средств? + +Это не вопрос о текущем состоянии. Это вопрос об истории исполнения. + +Полезный ход через FastNear — связать одну транзакцию `request_execution` со стороны вызывающего с одной транзакцией разрешения со стороны воркера, а к receipt переходить только на этапе завершения. + +```mermaid +sequenceDiagram + autonumber + participant Caller as "Вызывающая сторона" + participant Outlayer as "outlayer.*" + participant Worker as "worker.outlayer.*" + participant Near as "исполняющая среда NEAR" + + Caller->>Outlayer: FunctionCall request_execution + Outlayer-->>Near: создаёт асинхронную работу и контекст учёта + Worker->>Outlayer: resolve_execution или submit_execution_output_and_resolve + Outlayer-->>Near: логи завершения, путь callback, списание, возвраты +``` + +Всё это уже видно сегодня через FastNear и RPC. + +## 1. Раскройте одну транзакцию запроса и одно разрешение воркера + +Если хотите сразу увидеть всю форму потока, начните с уже известной пары хешей и раскройте оба. + +Эта пара работала 18 апреля 2026 года: + +- `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` — `request_execution` со стороны вызывающего +- `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` — `submit_execution_output_and_resolve` со стороны воркера + +```bash title="Раскройте хеш запроса и хеш разрешения воркера" +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{ + "tx_hashes":[ + "AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4", + "AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs" + ] + }' | jq '.transactions[] | { + hash: .transaction.hash, + signer: .transaction.signer_id, + receiver: .transaction.receiver_id, + actions: [.transaction.actions[] | keys[0]], + logs: (.receipts[0].execution_outcome.outcome.logs[:2]) + }' +``` + +В этом выборочном выводе: + +- хеш запроса шёл от `solarflux.near` к `outlayer.near` +- в логах фигурировал разрешённый проект: `zavodil.near/near-email` +- хеш воркера шёл от `worker.outlayer.near` к `outlayer.near` +- в логах воркера было `Stored pending output` и `Resolving execution ... (combined flow)` + +Этого уже достаточно для видимой истории в терминах NEAR: исходный `FunctionCall` создал асинхронную единицу работы, позже воркер вернулся как отдельный подписант, а контракт разрешил результат в цепочке. + +Если копировать с этой страницы только одну команду, то именно эту. + +## 2. Найдите два нужных хеша сами + +Если пары хешей у вас ещё нет, переключитесь на Transactions API: история аккаунта. + +```bash title="Недавняя mainnet-активность для outlayer.near" +curl -sS https://tx.main.fastnear.com/v0/account \ + -H 'content-type: application/json' \ + --data '{"account_id":"outlayer.near","desc":true}' \ + | jq '{txs_count, first: .account_txs[0]}' +``` + +18 апреля 2026 года эта поверхность показывала более 5 000 трассированных транзакций для `outlayer.near`, а самый свежий выборочный хеш был таким: + +```text +AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs +``` + +Этот хеш не был исходным пользовательским запросом. Это уже было последующее действие со стороны воркера. + +Именно поэтому история аккаунта — правильный первый поиск: здесь задача не в том, чтобы описать контракт целиком, а в том, чтобы найти две конкретные транзакции в одной истории исполнения. + +```mermaid +flowchart TD + A["Transactions API: /v0/account для outlayer.*"] --> B["Найдите недавние хеши транзакций"] + B --> C["Хеш request_execution со стороны вызывающего"] + B --> D["Хеш разрешения со стороны воркера"] + C --> E["Раскройте оба через /v0/transactions"] + D --> E + E --> F["Проверьте логи, request_id, project/source, callback-receipt, списание и возвраты"] +``` + +## 3. Разберите фазу callback и возврата средств + +Если нужно пройти дальше, чем просто «воркер вернул результат», посмотрите список receipt у раскрытой воркерской транзакции. + +```bash title="Показать последующие действия на уровне receipt для разрешения воркером" +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{"tx_hashes":["AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs"]}' \ + | jq '.transactions[0] | { + hash: .transaction.hash, + receipts: [ + .receipts[] | { + predecessor: .receipt.predecessor_id, + receiver: .receiver_id, + actions: [.receipt.receipt.Action.actions[] | keys[0]], + logs: .execution_outcome.outcome.logs + } + ] + }' +``` + +На что смотреть: + +- `FunctionCall:on_execution_response` +- логи списания вроде `[[yNEAR charged: "..."]]` +- события завершения вроде `execution_completed` +- последующие receipt `Transfer` + +Здесь понятие receipt как раз становится правильной абстракцией: не в начале урока, а тогда, когда уже отлаживается реальный путь завершения. + +## 4. Подтвердите контракт, если нужна точная проверка + +Если нужна точная проверка аккаунта и `code_hash`, используйте сырой RPC. Это шаг для проверки идентичности, а не для восстановления истории исполнения. + +```bash title="Mainnet: view_account для outlayer.near" +curl -sS https://rpc.mainnet.fastnear.com \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "id":"1", + "method":"query", + "params":{ + "request_type":"view_account", + "finality":"final", + "account_id":"outlayer.near" + } + }' | jq '.result | {amount, locked, code_hash, storage_usage}' +``` + +```bash title="Testnet: view_account для outlayer.testnet" +curl -sS https://rpc.testnet.fastnear.com \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "id":"1", + "method":"query", + "params":{ + "request_type":"view_account", + "finality":"final", + "account_id":"outlayer.testnet" + } + }' | jq '.result | {amount, locked, code_hash, storage_usage}' +``` + +По состоянию на 18 апреля 2026 года оба контракта возвращали один и тот же `code_hash`: + +```text +94uKcoDB3QbEpxDj1xsw9CQwu9bAY1PoVPr2BZYRRv4K +``` + +Это сильный сигнал, что на обеих сетях развёрнут один и тот же бинарник контракта. + +## 5. Что происходит внутри? + +Видимая история выше — это то, что NEAR-разработчику нужно сначала. Более глубокий слой объясняет, почему этот поток вообще интересен. + +### Что можно наблюдать уже сейчас + +Через FastNear и RPC уже видно: + +- вызывающую транзакцию `request_execution` +- воркерскую `resolve_execution` или `submit_execution_output_and_resolve` +- завершающие receipt, где материализуются callback, списание и возврат средств + +Интеграция вашего контракта при этом остаётся обычной асинхронной композицией в NEAR: вы вызываете `outlayer.*`, а потом обрабатываете свой callback. + +### Что задокументировано как внутренний механизм + +Документация OutLayer описывает более глубокий внутренний слой: `outlayer.*` использует семантику NEAR `yield/resume` как свою внутреннюю асинхронную границу, внешняя работа выполняется в TEE-воркерах, а защищённые секреты проходят через отдельный путь доверия, где TEE-keystore получает DAO-gated CKD через MPC signer. + +Для NEAR-разработчика здесь важна точность: мы не говорим, что ваш вызывающий контракт сам пишет `promise_yield_create`. Примитивы `yield/resume` в NEAR работают только в рамках одного и того же аккаунта, поэтому если этот механизм используется здесь, то yielding и resuming делает `outlayer.*`, а не исходный вызывающий контракт. Для сырой модели выполнения смотрите Продвинутые возможности. + +Документация Secrets / CKD описывает этот путь keystore как двухуровневый: сначала keystore получает derivation key через DAO-gated путь к MPC, а затем использует уже полученную derivation capability для защищённых секретов во время исполнений приложения. Это объяснение доверительной модели, а не утверждение, что каждое обычное исполнение OutLayer делает новый DAO -> MPC round trip. + +Публичный gateway-аккаунт для этого пути keystore / DAO в наших текущих публичных chain-данных всё ещё не подтверждён, поэтому эту часть надо держать в корзине «задокументировано внутри», а не в корзине «уже наблюдается сейчас». + +```mermaid +flowchart TB + subgraph Observable["Что уже можно наблюдать через FastNear / RPC"] + Caller["Пользователь или вызывающий контракт"] -->|FunctionCall request_execution| Entry["outlayer.*"] + Entry -->|поздняя активность воркера| Worker["worker.outlayer.*"] + Worker -->|resolve_execution или submit_execution_output_and_resolve| Entry + Entry -->|on_execution_response, списание, refund-переводы| Finish["Callback / finish receipt"] + end + + subgraph Internal["Что задокументировано как внутренний слой"] + Yield["внутренняя yield-точка в outlayer.*"] --> TEE["TEE-воркер исполняет WASM"] + TEE -->|resume с результатом| Yield + TEE --> Keystore["TEE-keystore для защищённых секретов"] + Keystore -->|задокументированный DAO-gated request_key| DAO["DAO gateway (задокументирован)"] + DAO -->|задокументированный CKD request| MPC["v1.signer / v1.signer-prod.testnet"] + MPC -->|derivation key для keystore| Keystore + end + + Entry -. та же логическая история исполнения .-> Yield + + classDef observable fill:#e7f1ff,stroke:#2563eb,color:#0f172a; + classDef internal fill:#fff4e5,stroke:#b45309,color:#0f172a; + class Caller,Entry,Worker,Finish observable; + class Yield,TEE,Keystore,DAO,MPC internal; +``` + +## Куда читать дальше + +- Transactions API для истории аккаунта, receipt и раскрытия транзакций +- Продвинутые возможности для семантики `yield/resume` в NEAR +- Асинхронная модель для лексики promise и callback +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) для задокументированного контрактного интерфейса +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) для задокументированного пути keystore, DAO и MPC diff --git a/scripts/generate-ai-surfaces.js b/scripts/generate-ai-surfaces.js index 27e4464..a89f67a 100644 --- a/scripts/generate-ai-surfaces.js +++ b/scripts/generate-ai-surfaces.js @@ -106,6 +106,8 @@ const AUTHORED_MARKDOWN_LABELS = { en: { aiAndAgents: "AI & Agents", bestFor: "Best for:", + berryClubGalleryNote: + "Rendered snapshot gallery: launch, mid, and recent checkpoints from checked-in `src/data/berryClubSnapshots.json`.", guidesArchiveTitle: "FastNear Builder Docs Full Documentation Archive", guidesArchiveIntro: "AI-readable Markdown mirrors for authored docs plus canonical `/rpcs/**` and `/apis/**` routes.", @@ -137,6 +139,8 @@ const AUTHORED_MARKDOWN_LABELS = { ru: { aiAndAgents: "AI и агенты", bestFor: "Лучше всего подходит для:", + berryClubGalleryNote: + "Галерея снимков: контрольные точки launch, mid и recent из сохранённого `src/data/berryClubSnapshots.json`.", guidesArchiveTitle: "Полный архив документации FastNear Builder Docs", guidesArchiveIntro: "AI-читабельные Markdown-копии авторских гайдов и канонических маршрутов `/rpcs/**` и `/apis/**`.", @@ -654,6 +658,15 @@ function transformSimpleJsx(markdown, locale = DEFAULT_LOCALE) { return transformed; } +function transformSpecialComponents(markdown, locale = DEFAULT_LOCALE) { + const labels = getAuthoredMarkdownLabels(locale); + + return markdown.replace( + //g, + `\n\n${labels.berryClubGalleryNote}\n\n` + ); +} + function removeImports(markdown) { return markdown.replace(/^import\s+.+?;?\n/gm, ""); } @@ -679,6 +692,7 @@ function renderAuthoredMarkdown(content, route, filePath, locale = DEFAULT_LOCAL let markdown = removeImports(content); markdown = transformCardGrid(markdown, locale); markdown = transformInlineLinks(markdown, locale); + markdown = transformSpecialComponents(markdown, locale); markdown = transformSimpleJsx(markdown, locale); markdown = rewriteRootRelativeMarkdownLinks(markdown, locale); assertNoUnsupportedJsx(markdown, filePath); diff --git a/sidebars.js b/sidebars.js index 357b957..3669c7e 100644 --- a/sidebars.js +++ b/sidebars.js @@ -58,8 +58,30 @@ function makeExamplesDoc(id) { }; } -function withExamplesFooter(items, examplesId) { - return [...items, makeExamplesDivider(), makeExamplesDoc(examplesId)]; +function makeExamplesCategory(id, items) { + return { + type: 'category', + label: 'Examples', + link: { + type: 'doc', + id, + }, + items, + collapsed: false, + className: 'fastnear-sidebar-examples-category', + }; +} + +function makeExamplesFooterItem(config) { + if (typeof config === 'string') { + return makeExamplesDoc(config); + } + + return makeExamplesCategory(config.id, config.items); +} + +function withExamplesFooter(items, examplesConfig) { + return [...items, makeExamplesDivider(), makeExamplesFooterItem(examplesConfig)]; } const rpcSidebar = withExamplesFooter( @@ -193,7 +215,10 @@ const transactionsApiSidebar = withExamplesFooter( 'tx/blocks', 'tx/receipt', ], - 'tx/examples' + { + id: 'tx/examples', + items: ['tx/berry-club', 'tx/outlayer'], + } ); const transfersApiSidebar = hideEarlyApiFamilies diff --git a/src/components/BerryClubSnapshotGallery/index.js b/src/components/BerryClubSnapshotGallery/index.js new file mode 100644 index 0000000..a3957ef --- /dev/null +++ b/src/components/BerryClubSnapshotGallery/index.js @@ -0,0 +1,70 @@ +import React from 'react'; + +import styles from './styles.module.css'; + +function colorToHex(color) { + return `#${Number(color).toString(16).padStart(6, '0').slice(-6)}`; +} + +function SnapshotCard({ snapshot, label, uiText }) { + const hashPreview = `${snapshot.txHash.slice(0, 8)}...${snapshot.txHash.slice(-8)}`; + + return ( +
+
+

{label}

+
+
+
{uiText.blockHeightLabel}
+
{snapshot.blockHeight}
+
+
+
{uiText.timestampLabel}
+
{snapshot.timestamp}
+
+
+
{uiText.txHashLabel}
+
+ {hashPreview} +
+
+
+
+ +
+ {snapshot.board.flat().map((color, index) => ( +
+ ))} +
+
+ ); +} + +export default function BerryClubSnapshotGallery({ + snapshots, + labels = {}, + uiText = {}, +}) { + const resolvedUiText = { + blockHeightLabel: uiText.blockHeightLabel || 'Block', + timestampLabel: uiText.timestampLabel || 'Time', + txHashLabel: uiText.txHashLabel || 'Transaction', + }; + + return ( +
+ {snapshots.map((snapshot) => ( + + ))} +
+ ); +} diff --git a/src/components/BerryClubSnapshotGallery/styles.module.css b/src/components/BerryClubSnapshotGallery/styles.module.css new file mode 100644 index 0000000..0688604 --- /dev/null +++ b/src/components/BerryClubSnapshotGallery/styles.module.css @@ -0,0 +1,71 @@ +.gallery { + display: grid; + gap: 1.25rem; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + margin: 1.5rem 0; +} + +.card { + background: + linear-gradient(180deg, rgba(255, 248, 240, 0.96), rgba(255, 255, 255, 0.98)); + border: 1px solid rgba(170, 140, 116, 0.35); + border-radius: 16px; + box-shadow: 0 18px 48px rgba(83, 52, 28, 0.12); + padding: 1rem; +} + +.cardHeader { + margin-bottom: 0.85rem; +} + +.cardTitle { + font-size: 1rem; + line-height: 1.2; + margin: 0 0 0.75rem; +} + +.metaList { + display: grid; + gap: 0.35rem; + margin: 0; +} + +.metaItem { + align-items: baseline; + display: grid; + gap: 0.35rem; + grid-template-columns: minmax(72px, auto) 1fr; +} + +.metaItem dt { + color: rgba(92, 67, 48, 0.78); + font-size: 0.78rem; + font-weight: 600; + margin: 0; +} + +.metaItem dd { + color: rgba(33, 23, 15, 0.96); + font-family: var(--ifm-font-family-monospace); + font-size: 0.78rem; + margin: 0; +} + +.hashValue { + overflow-wrap: anywhere; +} + +.board { + background: #0f0d08; + border: 1px solid rgba(22, 16, 10, 0.65); + border-radius: 12px; + display: grid; + grid-template-columns: repeat(50, minmax(0, 1fr)); + overflow: hidden; + width: 100%; +} + +.pixel { + aspect-ratio: 1 / 1; + min-width: 0; +} diff --git a/src/css/custom.css b/src/css/custom.css index c86b5b2..b0e2ee0 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -496,6 +496,11 @@ pre code { margin-top: 0.35rem; } +.theme-doc-sidebar-item-category.fastnear-sidebar-examples-category, +.navbar-sidebar .fastnear-sidebar-examples-category { + margin-top: 0.35rem; +} + [data-theme='dark'] .theme-doc-sidebar-item-link.fastnear-sidebar-examples-divider > div, [data-theme='dark'] .navbar-sidebar .fastnear-sidebar-examples-divider > div { border-top-color: rgba(243, 245, 255, 0.12); @@ -1689,6 +1694,95 @@ html[data-theme='light'] .fastnear-home-button--primary:focus-visible { text-decoration: none; } +.fastnear-example-strategy { + margin: 1rem 0 1.35rem; + padding: 16px 18px; + border: 1px solid rgb(15 23 42 / 8%); + border-radius: 18px; + background: + linear-gradient(180deg, rgb(255 255 255 / 94%), rgb(244 247 255 / 98%)); + box-shadow: 0 10px 24px rgb(15 23 42 / 6%); +} + +.fastnear-example-strategy__header { + display: grid; + gap: 6px; + margin-bottom: 10px; +} + +.fastnear-example-strategy__eyebrow { + display: inline-flex; + align-items: center; + width: fit-content; + padding: 3px 8px; + border: 1px solid rgb(15 86 179 / 14%); + border-radius: 999px; + background: rgb(255 255 255 / 72%); + color: #3654a8; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.fastnear-example-strategy__title { + margin: 0; + color: #14213d; + font-size: 1rem; + font-weight: 700; + line-height: 1.45; +} + +.fastnear-example-strategy__items { + display: grid; + gap: 8px; +} + +.fastnear-example-strategy__item { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + align-items: start; + gap: 10px; + margin: 0; + padding: 10px 12px; + border: 1px solid rgb(54 84 168 / 10%); + border-radius: 12px; + background: rgb(255 255 255 / 58%); + color: #42526f; + line-height: 1.6; +} + +.fastnear-example-strategy__step { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 2rem; + height: 2rem; + border: 1px solid rgb(15 86 179 / 14%); + border-radius: 10px; + background: linear-gradient(180deg, rgb(255 255 255 / 86%), rgb(224 231 255 / 86%)); + color: #3654a8; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.06em; +} + +.fastnear-example-strategy__item span:last-child { + min-width: 0; +} + +.fastnear-example-strategy__code { + white-space: nowrap; +} + +.fastnear-example-strategy__code { + padding: 0.12rem 0.38rem; + border: 1px solid rgb(54 84 168 / 10%); + border-radius: 8px; + background: rgb(255 255 255 / 72%); + color: #233252; +} + .fastnear-home-hero__panel { display: grid; align-content: start; @@ -2021,6 +2115,41 @@ html[data-theme='light'] .fastnear-home-button--primary:focus-visible { color: #ffffff; } +[data-theme='dark'] .fastnear-example-strategy { + border-color: rgb(148 163 184 / 20%); + background: + linear-gradient(180deg, rgb(30 41 59 / 96%), rgb(26 35 50 / 98%)); + box-shadow: 0 18px 34px rgb(0 0 0 / 22%); +} + +[data-theme='dark'] .fastnear-example-strategy__eyebrow { + border-color: rgb(148 163 184 / 26%); + background: rgb(15 23 42 / 56%); + color: #c4d3ff; +} + +[data-theme='dark'] .fastnear-example-strategy__title { + color: #f3f5ff; +} + +[data-theme='dark'] .fastnear-example-strategy__item { + border-color: rgb(148 163 184 / 18%); + background: rgb(15 23 42 / 34%); + color: rgb(226 232 240 / 86%); +} + +[data-theme='dark'] .fastnear-example-strategy__step { + border-color: rgb(148 163 184 / 20%); + background: linear-gradient(180deg, rgb(15 23 42 / 82%), rgb(30 41 59 / 88%)); + color: #c4d3ff; +} + +[data-theme='dark'] .fastnear-example-strategy__code { + border-color: rgb(148 163 184 / 18%); + background: rgb(15 23 42 / 62%); + color: #e8eefc; +} + [data-theme='dark'] .fastnear-home-route-card__title { color: #98c7fd; } diff --git a/src/data/berryClubSnapshots.json b/src/data/berryClubSnapshots.json new file mode 100644 index 0000000..84c9ff5 --- /dev/null +++ b/src/data/berryClubSnapshots.json @@ -0,0 +1 @@ +{"generatedAt":"2026-04-18T23:00:41.902Z","network":"mainnet","contractId":"berryclub.ek.near","boardSize":50,"snapshots":[{"id":"launch","label":"Launch era","txHash":"BDNFpCpLXjBrgjR6z6wCZmB9EWdHnVMdqau3iTWTRE5H","blockHeight":21898354,"timestamp":"2020-11-08T01:18:58.488Z","timestampNs":"1604798338488044956","signerId":"kmpec.near","pixelCount":1,"board":[[11184810,6749952,6749952,6749952,6749952,16622079,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6710886,16622079,16683520,16683520,16683520,16683520,16683520,6710886,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,982784,6749952,6749952,6749952],[11184810,16622079,16622079,16622079,10421504,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,10421504,7592191,7592191,11184810,7592191,7592191,10421504,10421504,10421504,16683520,16683520,16683520,16683520,16683520,16683520,16683520,16683520,16683520,6749952,16683520,6749952,16683520,6749952,6749952,6749952,6749952,16394495,16394495,11184810,11184810,11184810,11184810,11184810,11184810,25265,6749952],[11184810,11184810,11184810,11184810,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,7592191,16622079,16622079,7592191,16622079,16622079,7592191,11184810,11184810,16622079,16622079,11184810,16622079,16622079,11184810,16622079,16622079,16622079,11184810,16622079,16622079,6749952,11184810,16622079,16683520,11184810,16683520,16394495,11184810,16622079,16622079,16622079,11184810,16622079,25265,6749952],[11184810,11184810,11184810,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,7592191,16622079,16622079,16622079,16622079,16622079,7592191,16622079,6710886,0,11184810,16622079,6710886,16622079,6710886,16622079,16622079,0,6710886,16622079,6710886,0,16394495,16622079,0,0,16622079,16394495,11184810,16683520,11184810,16622079,11184810,11184810,25265,6749952],[11184810,11184810,6749952,6749952,6863872,6863872,6863872,6863872,6863872,6749952,6863872,6863872,6863872,6863872,6749952,7592191,16622079,16622079,16622079,7592191,40160,0,0,0,0,0,0,16622079,11184810,6710886,16622079,11184810,11184810,16683520,11184810,11184810,11184810,6710886,6710886,16711740,16394495,16622079,11184810,6710886,16683520,16683520,11184810,11184810,25265,6749952],[11184810,11184810,11184810,6749952,16777215,16777215,16777215,0,16777215,6749952,16777215,16777215,13840661,0,6749952,11184810,7592191,16622079,7592191,40160,40160,0,0,0,0,0,6749952,0,16622079,11184810,0,16683520,16394495,11184810,16394495,11184810,12865792,12865792,12865792,16394495,16394495,16683520,11184810,16622079,11184810,16622079,11184810,16711740,25265,6749952],[11184810,11184810,11184810,6749952,6749952,16777215,16777215,0,16711740,6749952,2507776,16777215,16777215,0,6749952,11184810,11184810,7592191,6710886,6710886,6710886,6749952,0,6749952,6710886,6749952,6749952,11184810,16622079,10421504,16394495,16394495,11184810,11184810,16394495,12865792,11184810,11184810,11184810,6863872,6863872,11184810,11184810,11184810,11184810,11184810,16683520,11184810,25265,6749952],[11184810,11184810,6749952,6749952,6863872,6863872,6863872,6749952,6863872,6749952,6863872,6749952,6749952,6749952,6749952,11184810,11184810,11184810,6710886,16622079,16622079,6710886,16571392,16622079,11184810,6710886,11184810,16565248,6710886,11184810,6710886,11184810,10804480,11184810,11184810,11184810,11184810,6863872,6863872,11660800,11660800,6863872,11184810,11184810,11184810,11184810,16711740,16683520,25265,6749952],[11184810,11184810,11184810,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,6749952,11184810,11184810,11184810,6710886,16622079,6710886,6710886,16711740,6710886,6710886,6710886,11184810,16683520,16683520,11184810,11184810,11184810,11184810,11184810,11184810,6863872,6863872,11660800,11660800,11660800,11660800,11660800,6863872,11184810,11184810,11184810,11184810,16683520,11184810,6749952],[11184810,11184810,11184810,16683520,0,0,0,0,6749952,6749952,6749952,0,0,0,0,11184810,11184810,11184810,11184810,6710886,6749952,6710886,6749952,1900799,6710886,0,11184810,0,16394495,11184810,11184810,11184810,11184810,11184810,11184810,6863872,11660800,11660800,11660800,11660800,11660800,11660800,11660800,6863872,11184810,11184810,11184810,11184810,11184810,6749952],[11184810,11184810,11184810,11184810,16683520,16010811,16683520,16711740,16683520,6749952,16683520,16683520,16683520,16683520,16683520,11184810,11184810,11184810,11184810,6749952,6749952,6749952,0,13840661,6868170,11184810,6710886,11184810,0,11184810,11184810,11184810,11184810,11184810,6863872,6863872,11660800,11660800,11660800,11660800,11660800,11660800,11660800,6863872,11184810,11184810,16683520,11184810,11184810,6749952],[11184810,11184810,11184810,11184810,11184810,11184810,11184810,6863872,6863872,6749952,6863872,6863872,6863872,11184810,11184810,11184810,11184810,11184810,6868170,6749952,6749952,6749952,6749952,0,6710886,6710886,6710886,0,6710886,11184810,11184810,11184810,11184810,6863872,11660800,11660800,11660800,11660800,11660800,11660800,11660800,11660800,11660800,6863872,11184810,11184810,16683520,11184810,11184810,6749952],[11184810,11184810,11184810,11184810,11184810,11184810,6749952,6749952,6749952,6749952,6749952,6749952,6749952,11184810,11184810,11184810,11184810,11184810,6749952,6749952,6749952,6749952,0,6868170,11184810,0,0,0,11184810,11184810,11184810,11184810,6863872,11660800,11660800,11660800,11660800,3342336,16565248,16565248,11660800,11660800,11660800,6863872,11184810,11184810,11184810,11184810,0,6749952],[11184810,11184810,11184810,11184810,11184810,11184810,6749952,4481535,6749952,6749952,6749952,4481535,6749952,6749952,11184810,11184810,11184810,6749952,6749952,11184810,6749952,0,6749952,16683520,0,0,6868170,11184810,10804480,11184810,11184810,6863872,6863872,11660800,11660800,12865792,16683520,16683520,10421504,10421504,11660800,11660800,11660800,6863872,11184810,11184810,0,16711740,0,6749952],[11184810,11184810,11184810,11184810,6749952,6749952,6749952,4481535,4481535,6749952,6749952,4481535,6749952,6749952,6749952,11184810,6749952,6749952,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,6863872,11660800,11660800,11660800,10421504,12865792,16683520,16683520,12865792,11660800,11660800,6863872,8423680,11184810,11184810,11184810,0,7592191,6749952],[11184810,11184810,6749952,6749952,6749952,6749952,6749952,4481535,6749952,4481535,6749952,4481535,6749952,6749952,11184810,6749952,6749952,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,6863872,11660800,11660800,0,16683520,12865792,12865792,12865792,0,11660800,11660800,6863872,8423680,11184810,11184810,12865792,0,0,6749952],[11184810,11184810,11184810,11184810,6749952,6749952,6749952,4481535,6749952,6749952,4481535,4481535,6749952,6749952,11184810,6749952,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,6863872,11660800,11660800,0,12865792,3342336,3342336,0,12865792,11660800,11660800,6863872,8423680,11184810,11184810,8086783,7592191,0,6749952],[11184810,11184810,11184810,11184810,0,6749952,6749952,4481535,6749952,6749952,6749952,4481535,6749952,6749952,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,8423680,6863872,11660800,0,16565248,12865792,12865792,0,16565248,11660800,6863872,8423680,8423680,11184810,11184810,8086783,8086783,0,6749952],[11184810,11184810,6749952,6749952,6749952,6710886,6749952,6749952,6749952,16711680,6749952,6749952,6749952,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,8423680,6863872,11660800,12865792,0,12865792,0,16683520,11660800,11660800,6863872,8423680,11184810,11184810,11184810,12865792,11184810,6868170,6749952],[11184810,11184810,11184810,11184810,6749952,6749952,11184810,6749952,6749952,16711680,6749952,6749952,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,8423680,6863872,11660800,0,12865792,0,16683520,11660800,6863872,8423680,8423680,11184810,11184810,11184810,11184810,11184810,0,6749952],[11184810,11184810,11184810,6749952,6749952,6749952,11184810,6749952,0,11184810,0,6749952,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,8423680,6863872,11660800,11660800,11660800,11660800,11660800,11660800,6863872,8423680,8423680,11184810,11184810,11184810,8086783,6868170,0,6749952],[11184810,11184810,11184810,11184810,6749952,11184810,11184810,6749952,16565248,16711680,0,6749952,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,8423680,6863872,6863872,6863872,6863872,6863872,6863872,8423680,8423680,11184810,11184810,11184810,11184810,8086783,0,0,6749952],[11184810,11184810,11184810,11184810,11184810,11184810,11184810,6749952,6710886,16711680,0,6749952,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,8423680,8423680,8423680,8423680,8423680,8423680,8423680,8423680,8423680,11184810,11184810,11184810,11184810,8086783,11184810,6868170,6749952],[11184810,11184810,11184810,11184810,11184810,11184810,11184810,6749952,0,0,6868170,6749952,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,11184810,8423680,8423680,8423680,8423680,8423680,8423680,12865792,12865792,11184810,11184810,11184810,11184810,0,6868170,6868170,6749952],[11184810,11184810,11184810,11184810,6749952,6749952,6749952,6749952,0,0,0,6749952,6749952,6749952,6749952,0,6868170,6868170,0,0,16565248,8423680,16565248,0,0,0,16711684,16565248,0,0,0,6710886,16565248,11184810,6868170,16565248,6868170,6868170,16565248,0,6710886,0,6868170,6868170,6868170,0,0,6868170,0,6749952],[16622079,6710886,6868170,0,6749952,6749952,6749952,6749952,16777215,0,6868170,6749952,6749952,6749952,6749952,0,0,0,0,0,0,16565248,6868170,16565248,0,6868170,0,6868170,0,11582464,0,6868170,16565248,16565248,6868170,12865792,11582464,0,11582464,11184810,6868170,11184810,0,0,0,0,0,0,11184810,6749952],[16683520,6868170,6868170,16777215,6868170,16565248,6868170,6868170,0,0,0,0,0,7592191,0,0,0,0,0,0,0,6868170,16565248,16565248,16711684,6868170,6868170,0,16565248,11184810,6868170,0,16565248,6710886,11582464,12865792,16711684,1484197,0,11582464,1484197,11582464,6868170,0,0,1484197,16565248,1484197,16565248,6749952],[8423680,12865792,0,0,6868170,16565248,1228,8423680,0,6868170,7592191,0,0,0,0,0,0,0,6868170,0,16565248,6868170,16777215,16565248,16711684,0,16565248,6868170,11444735,6868170,16394495,0,11582464,6868170,16394495,16394495,16394495,0,16394495,11582464,1484197,1484197,1484197,1484197,6868170,0,1484197,16565248,0,1484197],[16565248,8423680,0,0,1228,0,0,7592191,6868170,0,0,0,8423680,0,8423680,0,8423680,0,6868170,16565248,14409472,16565248,6868170,16565248,16777215,16711684,6868170,6868170,0,16777215,6868170,0,6710886,6710886,6868170,1484197,16565248,6868170,6868170,1484197,6868170,11582464,6868170,6868170,11444735,6868170,16565248,16565248,16565248,6749952],[16565248,16565248,0,0,1228,16565248,0,16777215,0,6868170,0,0,0,0,0,16010811,6868170,0,0,16010811,16565248,0,16777215,0,16565248,0,16565248,16565248,16565248,0,0,10421504,10421504,6868170,10421504,6868170,6868170,16565248,11444735,11444735,11444735,11582464,11444735,6868170,6868170,6868170,0,16565248,16565248,6749952],[16683520,16565248,0,8423680,16565248,16565248,0,6868170,7592191,7592191,0,6868170,16489984,6868170,6868170,6868170,0,16565248,16565248,6868170,16565248,0,0,0,0,16565248,0,6868170,16777215,16565248,11444735,16565248,6868170,11444735,11444735,0,6868170,16565248,6710886,11582464,11444735,6868170,6868170,16777215,16565248,16565248,16565248,0,16565248,6749952],[6632084,16565248,16777215,16565248,0,6868170,0,0,0,0,0,16489984,6632084,0,0,6868170,6868170,6868170,6868170,6868170,6868170,16565248,0,6868170,6868170,6632084,10421504,6868170,11582464,6868170,6868170,16565248,16565248,16565248,16565248,6868170,11444735,0,16565248,0,0,0,11444735,0,16565248,16565248,16565248,16565248,0,16683520],[16683520,6868170,6868170,16565248,16565248,6868170,0,0,6868170,0,0,16565248,16489984,0,16777215,6868170,6868170,6868170,16565248,6868170,16565248,16489984,16565248,16565248,16711684,16711684,16711684,6868170,6868170,6868170,11582464,16565248,16565248,6868170,6868170,6868170,6868170,11444735,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,0,6710886],[6868170,8423680,16777215,6868170,0,6868170,6868170,6868170,16565248,0,0,0,0,16489984,6868170,6868170,16565248,6868170,16565248,0,6868170,6868170,0,0,10421504,11444735,0,0,0,6868170,11582464,6868170,6868170,16565248,11444735,6868170,6710886,11444735,16777215,16565248,16565248,16565248,16565248,0,0,0,16565248,16565248,16777215,1658163],[16622079,6868170,6868170,6868170,0,6868170,0,6868170,6868170,16565248,0,0,0,16565248,6868170,16565248,16571392,16565248,16565248,14409472,6868170,6868170,16565248,11444735,0,16565248,0,16565248,11582464,16565248,0,0,11444735,16565248,16565248,16565248,16565248,11444735,10421504,16565248,16565248,16565248,0,16571392,16571392,0,16565248,16565248,16565248,16683520],[16010811,16565248,16565248,16565248,0,0,16565248,0,0,0,0,6868170,16489984,6868170,16489984,6868170,16565248,6868170,6868170,16777215,0,0,11444735,0,7592191,7592191,0,0,6868170,7592191,6710886,11582464,11444735,0,12865792,11444735,11444735,16565248,0,16565248,16565248,16565248,0,16571392,16571392,0,16565248,16565248,16565248,6710886],[16010811,6868170,6868170,6868170,0,0,16565248,6868170,16565248,16489984,16489984,16489984,11211934,16489984,16489984,16489984,0,6868170,16489984,16489984,6868170,16565248,16489984,11444735,6710886,0,7592191,16565248,11582464,16565248,0,0,11444735,11444735,0,0,11582464,11582464,0,16565248,16565248,16565248,0,16571392,16571392,16571392,0,16565248,16565248,16683520],[16010811,25265,25265,25265,25265,0,16565248,25265,16565248,16565248,16565248,16565248,16565248,16565248,0,0,16565248,16565248,11211934,16565248,10421504,0,0,16777215,11211934,16565248,16565248,0,11582464,0,16565248,0,0,11582464,11582464,0,11444735,16565248,10421504,16565248,16565248,16565248,0,16571392,16571392,16571392,0,16565248,16565248,6868170],[16010811,25265,25265,25265,16489984,16489984,16489984,25265,16489984,16565248,16565248,16565248,16489984,0,0,0,0,0,16565248,0,6632084,6632084,6632084,6632084,6632084,6632084,11444735,0,11582464,0,0,11444735,10421504,11444735,16565248,10421504,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16711933,16571392,16565248,0],[25265,25265,25265,25265,16489984,16565248,25265,16565248,25265,25265,16622079,16622079,16622079,16622079,16622079,16622079,16622079,16565248,0,6632084,6632084,6868170,16571392,6632084,6632084,6632084,16565248,6710886,16565248,0,0,0,0,0,16565248,10421504,16565248,16565248,16565248,16565248,16565248,0,16571392,16571392,16571392,16571392,0,16565248,16565248,16622079],[6868170,16489984,25265,25265,16489984,25265,25265,6868170,6868170,6868170,16489984,16622079,6868170,16565248,25265,0,16622079,0,16565248,6868170,6632084,0,0,0,6632084,0,16565248,0,0,16777215,10421504,6868170,0,0,10421504,16565248,16565248,16565248,16565248,16565248,0,0,16571392,16571392,16571392,16571392,0,16565248,16565248,7592191],[0,0,0,6868170,0,25265,25265,16622079,0,16622079,25265,0,6868170,6868170,0,6868170,16622079,6868170,16777215,6632084,0,16565248,6868170,6632084,0,6868170,2277887,0,6868170,6868170,2277887,0,0,7592191,16565248,16565248,16565248,16565248,16565248,0,16571392,16571392,16571392,16571392,16571392,16571392,0,16565248,16565248,16777215],[13377024,13377024,13377024,0,13377024,16622079,13377024,0,0,13377024,0,6868170,13377024,6868170,6868170,6868170,16622079,6868170,6632084,6632084,6632084,16571392,6868170,16489984,6632084,16565248,16565248,0,0,6868170,16565248,6868170,7592191,7592191,16565248,16565248,16565248,0,0,16571392,16571392,16571392,16571392,16571392,16571392,0,16565248,16565248,16565248,16622079],[6868170,13377024,13377024,6868170,13377024,16622079,16622079,6868170,6868170,13377024,6868170,0,0,6868170,0,6868170,0,0,6632084,16565248,6632084,6632084,6632084,6632084,6632084,2277887,2277887,0,7592191,2277887,7592191,6868170,7592191,10421504,16565248,16565248,0,16571392,16571392,16571392,16571392,16571392,16571392,16571392,0,16565248,16565248,16565248,16565248,7592191],[13377024,6868170,13377024,0,0,6868170,13377024,6868170,0,6868170,13377024,6868170,6868170,0,16622079,16565248,16622079,0,6632084,6632084,6632084,6632084,2277887,16565248,16565248,0,10421504,7592191,0,7592191,0,0,16565248,16565248,16565248,16565248,0,16571392,16571392,16571392,16571392,16571392,0,0,16565248,16565248,16565248,16565248,16565248,16622079],[13377024,0,0,16622079,13377024,6868170,13377024,6868170,13377024,13377024,6868170,6868170,6868170,6868170,13377024,16565248,16565248,16565248,16565248,16565248,6632084,16571392,0,10421504,2277887,10421504,0,10421504,0,10421504,0,10421504,7592191,16565248,16565248,16565248,16565248,0,0,0,0,0,16565248,16565248,16565248,16565248,16565248,16565248,16777215,0],[13377024,13377024,13377024,16622079,13377024,13377024,13377024,13377024,13377024,13377024,13377024,13377024,16622079,6868170,13377024,16622079,11184810,11184810,11184810,16565248,16565248,8912896,16565248,16565248,2277887,10421504,2277887,2277887,2277887,2277887,8912896,2277887,8912896,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16711933,16571392,16565248,0],[0,0,2277887,16622079,7935,2277887,2277887,2277887,2277887,2277887,16622079,16622079,4508671,40160,7592191,0,0,0,11184810,8912896,8912896,16565248,16565248,16565248,2277887,2277887,2277887,2277887,2277887,8912896,2277887,2277887,16571392,16571392,16777215,16571392,16571392,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,16565248,0,0],[0,0,2277887,16010811,2277887,2277887,2277887,2277887,2277887,2277887,16622079,16622079,2277887,2277887,2277887,16711686,16711686,16711686,11184810,16565248,16565248,16565248,16565248,16565248,11211934,10421504,10421504,2277887,10421504,8912896,8912896,8912896,16571392,16777215,16571392,16565248,16565248,16565248,16565248,16565248,16565248,16571392,16565248,16565248,7935,16571392,16565248,16622079,0,0],[0,0,2277887,16622079,16622079,2277887,2277887,16772846,2277887,2277887,16622079,2277887,2277887,2277887,2277887,16571392,16571392,16571392,8912896,8912896,16565248,16565248,16622079,16565248,11211934,16565248,16565248,16565248,16565248,16565248,16622079,2277887,16571392,16565248,16565248,16565248,16565248,16565248,16571392,16777215,16565248,16565248,16565248,16565248,16571392,16571392,0,0,0,0]]},{"id":"mid","label":"Mid-era","txHash":"Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ","blockHeight":97601515,"timestamp":"2023-07-29T10:41:41.009Z","timestampNs":"1690627301009685032","signerId":"1241q.near","pixelCount":72,"board":[[11789332,16645629,16777215,16711422,16710394,16710394,16775159,16776187,16776187,2827300,16710394,16643573,7104616,11328277,6908265,7895146,7697512,16776187,16776187,16777215,16776187,16776187,16777215,16776187,16775159,7104616,8946049,7630959,7894888,1966301,7960681,7565166,16710394,1966301,1966301,1966301,1966301,1966301,1966301,1966301,8288890,16710394,16776187,16710394,1966301,16776187,8287862,16571392,1446418,1314832],[7895144,7631988,16776187,16775159,8353655,7499374,8745584,16643573,16774388,16774388,16776187,16775159,16776187,16774388,16774388,16776187,16775159,16775159,16776187,16709366,16776187,16711422,7763559,8223097,16776187,16578808,16579836,16776187,16775159,1966301,1966301,16710394,1966301,1966301,1966301,16710394,1966301,7895146,1966301,1966301,1966301,6250335,1966301,16577780,1966301,1966301,16775159,16571392,6381921,1643797],[7083444,16776187,16775159,7566195,16775159,16775159,16774388,16775159,16577009,7499373,16513015,6842472,7696753,16711422,16774388,16775159,8946049,16577780,16512244,16512244,8287862,16775159,8551793,16709366,7565167,16512244,16710394,7762546,16776187,1966301,8551538,1966301,1966301,1966301,1966301,1966301,1966301,1966301,2294015,1966301,1966301,1966301,1966301,1966301,1966301,1966301,16571392,16571392,6839645,7104616],[7215538,16775159,16776187,16775159,16775159,16776187,7433580,16579836,9208441,16708595,16711422,8811376,16776187,16708595,16776187,16711422,16579836,16775159,16710394,16512244,7762546,16774388,16578808,16513015,6908265,6842216,16643573,1504118,6842472,16777215,1966301,1966301,1966301,1966301,1966301,1966301,7696753,1966301,1966301,1966301,1966301,1966301,1966301,1966301,16577780,1966301,16775159,16571392,16514043,16644601],[7808943,16775159,16776187,16776187,7499374,16513015,16577780,7630960,16776187,9602951,11784753,16774388,16577009,16708595,16774388,16775159,16775159,16776187,16772846,16577009,9076607,16577009,16775159,16708595,16774388,8221814,16775159,1966301,16774388,1966301,1966301,1966301,1966301,1966301,1966301,2294015,1966301,1966301,1966301,1966301,1966301,1966301,1966301,1966301,16710394,16571392,16571392,16571392,16571392,16578808],[16775159,16776187,16775159,16776187,16511473,8222326,16578808,7630959,16511473,16709366,16773617,16707824,8943491,8946049,16775159,16708595,16709366,16774388,16775159,16775159,16710394,16774388,7628912,16775159,16775159,16578808,1966301,7565167,1966301,1966301,1966301,1966301,1966301,2294015,2294015,1966301,1966301,2294015,1966301,1966301,1966301,1966301,1966301,16315636,16571392,16571392,16571392,16571392,16446451,16381429],[16776187,16775159,16578808,16577780,8287862,16380658,16776187,16709366,16577780,16577009,9077378,16709366,8683378,16775159,16775159,8877698,7434094,16777215,16775159,16775159,16776187,16776187,8812416,9602951,16774388,16775159,1966301,16577780,1966301,1966301,1966301,1966301,1966301,16710394,1966301,1966301,1966301,16709366,2294015,2294015,1966301,1966301,16380401,16571392,16571392,16571392,16571392,16571392,16571392,16644601],[16776187,16775159,16513015,16775159,16512244,16379887,16709366,8288890,16774388,16511473,16776187,16710394,8002248,7500402,16775159,16776187,9271681,7565167,16774388,9010814,16577009,16577009,16578808,7433581,1966301,8810111,1966301,1966301,1966301,1966301,1966301,2294015,2294015,1966301,2294015,2294015,1966301,1966301,1966301,1966301,16381429,16512244,16571392,16571392,16571392,16571392,16571392,16571392,16571392],[16776187,16775159,16512244,16775159,16709366,16579836,7894889,9602180,9340026,7499374,16710394,16513015,7762546,16775159,16776187,8353399,16776187,8036868,16577780,16575467,16775159,7631719,16577780,1966301,1966301,7565167,1966301,1966301,2294015,1966301,16577009,1966301,2294015,16578808,1966301,1966301,1966301,1966301,1966301,16571392,16578808,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16643573,16571392],[16776187,16775159,9077112,16776187,16775159,7565167,8880256,16577780,16513015,16710394,16776187,16710394,16776187,6710630,16775159,8353655,16776187,16710394,16577780,16577009,16577009,16578808,7433581,1966301,8613756,1966301,1966301,1966301,1966301,1966301,2294015,2294015,1966301,2294015,2294015,1966301,1966301,1966301,1966301,16381429,16512244,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16644601],[16774388,16777215,1966301,2294015,2294015,1966301,1966301,1966301,8945021,7630960,2294015,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16777215,16571392,16571392,16571392,16571392,16578808,16571392,16571392,16579836,16571392,16578808,6513258,16579836,16578808,4861858,16776187,5658198,6776679,7499629,8223097,16578808,16578808,7565167,16709366,16579836,16514043,16382457,16711422,16710394,6710886,16709366,0],[16775159,16776187,1966301,1966301,1966301,1966301,2294015,2294015,2294015,8946049,2294015,2294015,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16514043,16776187,16571392,16571392,16776187,16578808,16776187,8419192,7565167,16777215,7565167,16776187,16775159,12566463,16776187,16776187,6710885,16775159,16709366,7827311,8680060,16579836,16382457,7433581,7630704,7168363,16709366,6184542],[16776187,16777215,2294015,16777215,1966301,1966301,1966301,2294015,2294015,16514043,2294015,16571392,2294015,16578808,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16578808,16776187,8880256,16776187,16776187,16578808,16776187,9076607,16775159,7697513,8287862,9601923,16775159,8879485,16773617,16708595,16577780,16775159,16577780,16775159,16578808,16645629,16448250,7565167,7038823],[1446418,1966301,16710394,1966301,16709366,16512244,16578808,2294015,7500402,2294015,7561331,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,7565422,16571392,16571392,16571392,16776187,16776187,16579836,16777215,16777215,16777215,16776187,16579836,8287862,16776187,16774388,16777215,7565422,16710394,7564911,8025461,16775159,16776187,16578808,7564911,8153464,16380658,16776187,16776187,16645629,7564911,7433581],[328965,16776187,7958897,1966301,1966301,16512244,7956598,16774388,16513015,2294015,7566195,16571392,16571392,2294015,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16775159,16777215,16710394,16777215,16776187,16514043,16514043,16513015,7565422,16711422,8223097,12566463,8223097,16645629,16579836,6317414,16710394,16709366,6250335,7500402,7564911,7499374,16513015,16578808,16644601,7566195,11806883,6184542],[328965,16577780,1966301,16709366,1966301,8594127,16710394,16710394,16776187,16579836,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16776187,16571392,16571392,16776187,16578808,16776187,8419192,7565167,16777215,7565167,16776187,16775159,12566463,16776187,16776187,6710885,16775159,16709366,7827311,8680060,16579836,16382457,7433581,7630704,7168363,16709366,6184542],[16777215,16776187,16512244,7826041,7366512,16709366,16577780,16709366,16571392,16571392,16571392,16571392,16571392,16571392,16571392,6184542,16571392,16710394,16578808,8354683,6776934,16777215,6842472,6842472,7500402,16777215,16579836,7170923,16711422,6776679,16776187,16776187,6710886,16777215,16775159,16776187,16513015,16776187,8353655,7565168,16776187,7630704,8223097,16709366,8353655,16776187,16710394,7891571,11082395,7038823],[16776187,16776187,8550006,16709366,16513015,7827311,16381172,16571392,16513015,16710394,16571392,16571392,16571392,16513015,16711422,16571392,6842472,16711422,16571392,16571392,6710885,6711141,16711422,16777215,16777215,16711422,16711422,16711422,16777215,16777215,16777215,16776187,16777215,16645629,16711422,16578808,16776187,16711422,7499375,6842472,16776187,8353655,8222326,8223097,16381429,16776187,7170409,7893104,1906712,7104616],[16777215,16777215,591880,16577780,16577009,9537158,16571392,16571392,16571392,16571392,16571392,16571392,16571392,8354683,16776187,16777215,7631988,16571392,16571392,16776187,16776187,16776187,16776187,16513015,16447222,16578808,16578808,7565167,7499374,16776187,7565167,16644601,6776679,16777215,16777215,16777215,16777215,7565167,16709366,8287862,16776187,16776187,16578808,16578808,16380658,16577780,16776187,9011585,7566195,6184542],[16777215,16777215,131586,1446418,2695457,16571392,16571392,16571392,16571392,16571392,16571392,16571392,16571392,6710886,16571392,7499374,16571392,7630448,7104616,8288890,16776187,16777215,7960681,8222326,8288890,8745584,8483698,16513015,16643573,11211796,16645629,16645629,7168096,6842473,7630960,8809978,16776187,1446418,7630714,6842472,7958897,7958897,2761507,7565167,7565167,16776187,16776187,6250335,6184542,0]]},{"id":"recent","label":"Recent era","txHash":"8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL","blockHeight":194588754,"timestamp":"2026-04-18T21:04:42.229Z","timestampNs":"1776546282229772869","signerId":"sleet.near","pixelCount":2500,"board":[[0,0,0,0,0,0,0,0,0,0,0,65280,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,0,0,0,0,0,0,0],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65280,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65280,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65280,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,65280,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215],[16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,16777215,65280,16777215,16777215,16777215,16777215,16777215,16777215]]}]} diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index a78c846..1946346 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -2,12 +2,19 @@ ## Готовые сценарии -Эти сценарии выстроены от самого быстрого read-only-запроса к более насыщенному сценарию с изменением состояния. +Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. ### Определить аккаунт по публичному ключу, а затем получить сводку по нему Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» + Стратегия + Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. + + 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. + 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. + 03GET /v1/account/.../full в одном ответе показывает балансы, NFT и стейкинг. + **Что вы делаете** - Ищете по публичному ключу один или несколько `account_id`. @@ -48,6 +55,13 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». + Стратегия + Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. + + 01GET /v1/account/.../staking находит прямую экспозицию через пулы. + 02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них. + 03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed. + **Сеть** - mainnet @@ -124,6 +138,13 @@ jq -n \ Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». + Стратегия + Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. + + 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. + 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. + 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner. + **Сети** - mainnet для чтения виджета из `social.near` diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index a78c846..1946346 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -2,12 +2,19 @@ ## Готовые сценарии -Эти сценарии выстроены от самого быстрого read-only-запроса к более насыщенному сценарию с изменением состояния. +Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. ### Определить аккаунт по публичному ключу, а затем получить сводку по нему Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» + Стратегия + Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. + + 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. + 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. + 03GET /v1/account/.../full в одном ответе показывает балансы, NFT и стейкинг. + **Что вы делаете** - Ищете по публичному ключу один или несколько `account_id`. @@ -48,6 +55,13 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». + Стратегия + Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. + + 01GET /v1/account/.../staking находит прямую экспозицию через пулы. + 02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них. + 03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed. + **Сеть** - mainnet @@ -124,6 +138,13 @@ jq -n \ Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». + Стратегия + Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. + + 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. + 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. + 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner. + **Сети** - mainnet для чтения виджета из `social.near` diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index f8b5689..77e9db8 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -6,6 +6,13 @@ Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и финальную проверку через `view_state`. + Стратегия + Начните с одного точного ключа, расширяйтесь только до его истории и завершайте одной проверкой текущего chain-state. + + 01get-latest-key даёт самую новую индексированную запись по точному ключу. + 02get-history-key или history-by-key показывают, как тот же ключ менялся во времени. + 03RPC view_state — это финальное точное чтение, когда нужно сравнить индексированную историю с тем, что цепочка возвращает прямо сейчас. + **Цель** - Объяснить, как этот ключ выглядит в индексе, как он менялся и совпадает ли с этим `view_state` прямо сейчас. diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index f8b5689..77e9db8 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -6,6 +6,13 @@ Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и финальную проверку через `view_state`. + Стратегия + Начните с одного точного ключа, расширяйтесь только до его истории и завершайте одной проверкой текущего chain-state. + + 01get-latest-key даёт самую новую индексированную запись по точному ключу. + 02get-history-key или history-by-key показывают, как тот же ключ менялся во времени. + 03RPC view_state — это финальное точное чтение, когда нужно сравнить индексированную историю с тем, что цепочка возвращает прямо сейчас. + **Цель** - Объяснить, как этот ключ выглядит в индексе, как он менялся и совпадает ли с этим `view_state` прямо сейчас. diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index a42d3e4..731499f 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -34,7 +34,9 @@ - [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для polling по оптимистичным и финализированным блокам и перехода к RPC, когда это нужно. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. - [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. -- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents. +- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования и подробные разборы для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents. +- [Berry Club: как восстанавливать исторические доски](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Используйте Transactions API, RPC get_lines и проигрывание draw-вызовов, чтобы восстанавливать доски Berry Club по историческим эпохам. +- [OutLayer: как проследить один запрос от вызова до callback](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API и RPC, чтобы проследить реальный OutLayer-запрос от исходного FunctionCall через транзакцию воркера до callback- и refund-фазы. ## Снапшоты diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index b10fd56..de81222 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -996,12 +996,19 @@ https://test.api.fastnear.com ## Готовые сценарии -Эти сценарии выстроены от самого быстрого read-only-запроса к более насыщенному сценарию с изменением состояния. +Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. ### Определить аккаунт по публичному ключу, а затем получить сводку по нему Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» + Стратегия + Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. + + 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. + 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. + 03GET /v1/account/.../full в одном ответе показывает балансы, NFT и стейкинг. + **Что вы делаете** - Ищете по публичному ключу один или несколько `account_id`. @@ -1042,6 +1049,13 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». + Стратегия + Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. + + 01GET /v1/account/.../staking находит прямую экспозицию через пулы. + 02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них. + 03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed. + **Сеть** - mainnet @@ -1118,6 +1132,13 @@ jq -n \ Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». + Стратегия + Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. + + 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. + 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. + 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner. + **Сети** - mainnet для чтения виджета из `social.near` @@ -1594,6 +1615,13 @@ https://kv.test.fastnear.com Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и финальную проверку через `view_state`. + Стратегия + Начните с одного точного ключа, расширяйтесь только до его истории и завершайте одной проверкой текущего chain-state. + + 01get-latest-key даёт самую новую индексированную запись по точному ключу. + 02get-history-key или history-by-key показывают, как тот же ключ менялся во времени. + 03RPC view_state — это финальное точное чтение, когда нужно сравнить индексированную историю с тем, что цепочка возвращает прямо сейчас. + **Цель** - Объяснить, как этот ключ выглядит в индексе, как он менялся и совпадает ли с этим `view_state` прямо сейчас. @@ -2101,6 +2129,13 @@ https://testnet.neardata.xyz Используйте это расследование, когда нужно заметить новый блок как можно раньше, но финальный ответ всё равно должен опираться на финализированный блок и иногда на точное чтение через RPC. + Стратегия + Пусть NEAR Data сначала скажет, что что-то изменилось, а затем переиспользуйте то же семейство блоков для стабильного подтверждения. + + 01block-optimistic или last-block-optimistic дают самый ранний полезный сигнал. + 02block или last-block-final подтверждают, что то же наблюдение дошло до финализированной истории. + 03RPC block нужен только в самом конце, когда уже известна точная высота или хеш. + **Цель** - Быстро заметить недавний блок, а затем проверить то же самое, когда догонит finality. @@ -2416,12 +2451,21 @@ https://archival-rpc.testnet.fastnear.com Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. -## Готовые сценарии +## Механика аккаунтов и ключей + +Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. ### Проверить и удалить старые function-call-ключи Near Social Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. + Стратегия + Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. + + 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. + 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. + 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. + **Что вы делаете** - Через сам RPC получаете полный список access key аккаунта. @@ -2660,10 +2704,230 @@ fi Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. +### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? + +Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. + + Стратегия + Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. + + 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. + 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. + 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. + +**Что вы делаете** + +- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. +- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. +- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. +- Подтягиваете кандидата по транзакциям и различаете три разных ключа: + - ключ, который был добавлен + - public key верхнеуровневого signer + - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` + +Сразу важны три детали про nonce: + +- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. +- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. +- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' + +# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: +# export ACCOUNT_ID=mike.near +# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' +``` + +1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$TARGET_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" + } + }')" \ + | tee /tmp/key-origin-view.json >/dev/null + +CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" +ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" +SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" +SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" + +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_public_key "$TARGET_PUBLIC_KEY" \ + --argjson current_nonce "$CURRENT_NONCE" \ + --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ + --argjson search_from "$SEARCH_FROM" \ + --argjson search_to "$SEARCH_TO" \ + --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ + account_id: $account_id, + target_public_key: $target_public_key, + current_nonce: $current_nonce, + estimated_receipt_block: $estimated_receipt_block, + search_from_tx_block_height: $search_from, + search_to_tx_block_height: $search_to, + permission: ($permission | fromjson) + }' +``` + +Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. + +2. Ищите историю аккаунта только внутри этого диапазона блоков. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_tx_block_height "$SEARCH_FROM" \ + --argjson to_tx_block_height "$SEARCH_TO" '{ + account_id: $account_id, + is_real_signer: true, + from_tx_block_height: $from_tx_block_height, + to_tx_block_height: $to_tx_block_height, + desc: false, + limit: 50 + }')" \ + | tee /tmp/key-origin-candidates.json >/dev/null + +jq '{ + txs_count, + candidate_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_signer, + is_real_signer, + is_predecessor, + is_receiver + } + ] +}' /tmp/key-origin-candidates.json +``` + +Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. + +3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. + +```bash +TX_HASHES_JSON="$( + jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json +)" + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ + | tee /tmp/key-origin-transactions.json >/dev/null + +jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "direct", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $tx.transaction.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }), + ($tx.transaction.actions[]? + | .Delegate? + | .delegate_action as $delegate + | $delegate.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "delegated", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $delegate.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }) + ) + | { + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + tx_block_hash: $tx.execution_outcome.block_hash, + receiver_id: $tx.transaction.receiver_id + } + . +' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json +``` + +Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. + +Для примерного ключа `mike.near` выше совпадение оказывается делегированным: + +- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` +- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` +- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` +- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` + +4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. + +```bash +ADD_KEY_RECEIPT_ID="$( + jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | .receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) + | .receipt.receipt_id + ' /tmp/key-origin-transactions.json | head -n 1 +)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receipt_block_height: .receipt.block_height, + tx_block_height: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }' +``` + +Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. + +**Зачем нужен следующий шаг?** + +Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. + ### Проверить регистрацию FT storage и затем перевести токены Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». + Стратегия + Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. + + 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. + 02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит. + 03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог. + **Сеть** - testnet @@ -2960,10 +3224,21 @@ curl -s "$RPC_URL" \ Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +## Точные чтения NEAR Social и BOS + +Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. + ### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». + Стратегия + Спросите у social.near ровно о двух вещах, которые важны до подписи. + + 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. + 02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near. + 03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer. + Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: - есть ли у целевого аккаунта storage на `social.near`? @@ -3154,6 +3429,13 @@ jq -n \ Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». + Стратегия + Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. + + 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. + 02RPC call_function get читает точный исходник widget/Profile. + 03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples. + **Официальные ссылки** - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) @@ -3463,6 +3745,13 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. + Стратегия + Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него. + + 01Сначала решите, нужен ли вам optimized fast-rpc, обычный RPC или архивный режим. + 02Если нужен архив, сначала зафиксируйте одну точную высоту snapshot-блока и дальше переиспользуйте только её. + 03Выполняйте только команды выбранного пути и не смешивайте optimized, standard и archival шаги в одном сценарии. + **Цель** - Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. @@ -3920,6 +4209,13 @@ https://transfers.main.fastnear.com Используйте этот сценарий, когда история звучит так: «я вижу, что средства двигались, но хочу получить точную опорную точку исполнения для этого движения, не затягивая сразу всю историю аккаунта». + Стратегия + Сначала оставайтесь на узкой истории движения, а затем один раз переключайтесь в историю исполнения. + + 01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять. + 02jq поднимает один receipt_id, не затягивая остальную историю аккаунта. + 03POST /v0/receipt превращает это движение в опорную точку исполнения, которую уже можно продолжать в /tx. + **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. @@ -4131,9 +4427,11 @@ https://tx.test.fastnear.com **Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) -## Готовые расследования +Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. + +## С чего начать -Эти расследования намеренно выстроены от самого простого якоря к самой насыщенной форензике: сначала один tx hash, затем один receipt, затем паттерны с ошибками и async, и только потом более глубокие расследования по SocialDB и NEAR Intents. +Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. ### У меня есть один хеш транзакции. Что вообще произошло? @@ -4141,6 +4439,13 @@ https://tx.test.fastnear.com Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. + Стратегия + Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно. + + 01POST /v0/transactions даёт signer, receiver, типы действий, высоту блока и первую receipt-точку передачи. + 02RPC EXPERIMENTAL_tx_status нужен только для точной протокольной семантики успеха. + 03POST /v0/receipt имеет смысл только тогда, когда именно первая receipt становится новой опорной точкой. + **Цель** - Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. @@ -4177,7 +4482,7 @@ flowchart LR - в какой блок попала - одно простое предложение, которое объясняет транзакцию без receipt-жаргона -### Shell-сценарий: от хеша транзакции к человеческой истории +#### Shell-сценарий: от хеша транзакции к человеческой истории Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. @@ -4275,6 +4580,13 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. + Стратегия + Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой. + + 01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt. + 02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий. + 03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола». + **Цель** - Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. @@ -4312,7 +4624,11 @@ flowchart LR - была ли квитанция главным событием или только шагом в большом каскаде - одно предложение простым языком, которое можно без правок вставить коллеге в чат -### Shell-сценарий: от страшного receipt ID к человеческой истории +#### Shell-сценарий: от страшного receipt ID к человеческой истории + +## Ошибки и async + +Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и порядок callback-ов. Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. @@ -4402,6 +4718,13 @@ jq -r ' В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. + Стратегия + Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов. + + 01POST /v0/transactions показывает упорядоченный пакет ровно в том виде, в каком его подписал signer. + 02RPC EXPERIMENTAL_tx_status показывает падающий FunctionCall и точную причину отказа на уровне протокола. + 03RPC view_account по предполагаемому новому аккаунту доказывает, закрепились ли вообще ранние create, fund и add-key действия. + **Цель** - На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. @@ -4451,7 +4774,7 @@ flowchart LR - доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality - короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` -### Shell-сценарий неудачной транзакции с пакетом действий +#### Shell-сценарий неудачной транзакции с пакетом действий Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. @@ -4573,6 +4896,13 @@ jq '{ Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. + Стратегия + Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась. + + 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. + 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. + 03RPC call_function на роутере показывает, закрепилось ли собственное локальное изменение состояния из первой receipt. + **Цель** - Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. @@ -4620,7 +4950,7 @@ flowchart LR - что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился - одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции -### Shell-сценарий более позднего сбоя receipt +#### Shell-сценарий более позднего сбоя receipt Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». @@ -4759,6 +5089,13 @@ jq '{ Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» + Стратегия + Смотрите на два хеша как на одну async-историю: докажите, что работа была жива, восстановите запрошенный порядок и сравните его с видимым downstream-state. + + 01RPC call_function на view отложенной работы доказывает, что promise-работа действительно уже была жива до resume-шага. + 02POST /v0/transactions даёт оба block-anchor и точный порядок, который запросила resume-транзакция. + 03RPC EXPERIMENTAL_tx_status вместе с downstream-view доказывают, где callback-и реально выполнились и в каком видимом порядке. + **Цель** - Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. @@ -4806,10 +5143,21 @@ flowchart LR - в каких блоках стали видны изменения состояния - какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования +## Доказательства по SocialDB + +Эти примеры стартуют с читаемого состояния в NEAR Social и откатываются назад к точной записи, которая это состояние создала. + ### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». + Стратегия + Начните с читаемого значения поля, а затем превратите его field-level block в один receipt и одну транзакцию записи. + + 01NEAR Social POST /get даёт текущее значение profile.name и field-level :block. + 02POST /v0/block превращает этот блок в конкретный receipt и хеш транзакции mike.near -> social.near. + 03POST /v0/transactions доказывает payload записи, а RPC call_function get подтверждает, что поле и сейчас разрешается так же. + **Цель** - Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. @@ -4838,7 +5186,7 @@ flowchart LR - доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload - различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) -### Shell-сценарий доказательства поля профиля в NEAR Social +#### Shell-сценарий доказательства поля профиля в NEAR Social Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. @@ -5002,6 +5350,13 @@ NEAR Social даёт семантическое значение поля. FastN Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». + Стратегия + Начните с семантической связи подписки, а затем используйте блок записи как мост назад к одному receipt и одной транзакции. + + 01NEAR Social POST /get даёт читаемую связь подписки и SocialDB :block, где она была записана. + 02POST /v0/block превращает этот блок записи в конкретный receipt и хеш транзакции за этой связью. + 03POST /v0/transactions доказывает payload с graph.follow и index.graph, а RPC call_function get подтверждает, что связь и сейчас существует. + **Цель** - Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. @@ -5030,7 +5385,7 @@ NEAR Social даёт семантическое значение поля. FastN - доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` - различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) -### Shell-сценарий доказательства подписки в NEAR Social +#### Shell-сценарий доказательства подписки в NEAR Social Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. @@ -5199,6 +5554,13 @@ NEAR Social даёт семантическую связь. FastNear block recei - восстанавливаем исходную транзакцию - декодируем payload `set` и доказываем, что он действительно нёс исходник виджета + Стратегия + Смотрите на write-block виджета как на весь мост сразу: блок в receipt, receipt в транзакцию, транзакцию в исходник. + + 01POST /v0/block начинает с блока виджета и сужает его до одного receipt mob.near -> social.near. + 02POST /v0/transactions превращает этот receipt в один читаемый payload set с исходником виджета. + 03RPC call_function get — это финальное подтверждение текущего состояния, что виджет и сейчас существует. + **Цель** - Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. @@ -5229,7 +5591,11 @@ NEAR Social даёт семантическую связь. FastNear block recei - доказательство, что payload записи был `set` с `mob.near/widget/Profile` - одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» -### Shell-сценарий доказательства записи виджета в NEAR Social +#### Shell-сценарий доказательства записи виджета в NEAR Social + +## Трассировка расчёта + +Это самое насыщенное расследование на странице: один живой расчёт NEAR Intents от верхнеуровневой транзакции до receipts и событий, которые его объясняют. Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. @@ -5367,6 +5733,13 @@ jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». + Стратегия + Смотрите на один расчёт как на читаемую трассу, а не как на теорию протокола с первой строки. + + 01POST /v0/transactions даёт каркас расчёта: входную точку, первые downstream-контракты и ранние логи. + 02POST /v0/block переиспользует тот же якорь, когда нужен контекст включающего блока вокруг этого расчёта. + 03RPC EXPERIMENTAL_tx_status нужен там, где уже требуется канонический DAG по receipt и имена событий, которые доказывают реальное движение активов. + **Цель** - Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. @@ -5425,7 +5798,7 @@ flowchart LR Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. -### Shell-сценарий расчёта NEAR Intents +#### Shell-сценарий расчёта NEAR Intents Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. @@ -5651,6 +6024,498 @@ jq -r ' --- +## Berry Club: как восстанавливать исторические доски + +- HTML-маршрут: https://docs.fastnear.com/ru/tx/examples/berry-club +- Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples/berry-club.md + +**Источник:** [https://docs.fastnear.com/ru/tx/examples/berry-club](https://docs.fastnear.com/ru/tx/examples/berry-club) + +{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как восстанавливать доски Berry Club через FastNear. Он отделяет текущее состояние из get_lines от исторического разбора через диапазоны блоков, историю аккаунта, раскрытие транзакций и проигрывание draw-аргументов. */} + +# Berry Club: как восстанавливать исторические доски + +Используйте этот разбор, когда вопрос звучит так: «как Berry Club выглядел в определённую эпоху и какие `draw`-вызовы сделали доску именно такой?» + +Это read-only разбор из семейства Transactions examples. Если нужна только доска прямо сейчас, используйте `get_lines` и остановитесь. Если нужно объяснить, как доска пришла к такому виду, переключайтесь на историю блоков, историю аккаунта, раскрытые `draw`-вызовы и проигрывание. + + Стратегия + Сначала прочитайте живую доску, затем ограничьте эпоху и только после этого проигрывайте draw-вызовы, которые её объясняют. + + 01RPC call_function get_lines даёт текущую доску 50x50 и показывает, как выглядит «сейчас». + 02POST /v0/blocks вместе с POST /v0/account ограничивают одну эпоху и дают кандидатные хеши draw. + 03POST /v0/transactions раскрывает эти draw-вызовы, чтобы их можно было проиграть в исторические контрольные точки. + +Держите рядом: + +- [js.fastnear.com](https://js.fastnear.com/) +- [fastnear/js-monorepo](https://github.com/fastnear/js-monorepo) +- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +- [Transactions API: диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) +- [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) + +В этом руководстве история Berry Club разбирается только на mainnet. Снимки ниже собраны из воспроизводимых данных mainnet, которые уже сохранены в этом репозитории. + +## Короткая версия + +Berry Club даёт чистый view текущего состояния через `get_lines`, но не даёт готового эндпоинта вида «доска на блоке N». + +Из-за этого задача делится на две части: + +- используйте RPC `call_function`, когда вопрос звучит как «как доска выглядит сейчас?» +- используйте индексированную историю, когда вопрос звучит как «какие записи привели к этой доске?» +- используйте архивный RPC только тогда, когда нужно напрямую материализовать уже известную контрольную точку + +```mermaid +flowchart TD + A["RPC call_function: get_lines"] --> B["Текущая доска 50x50"] + C["Transactions API: /v0/blocks"] --> D["Ограничить эпоху"] + D --> E["/v0/account для berryclub.ek.near"] + E --> F["Кандидатные хеши draw-транзакций"] + F --> G["Раскрытие через /v0/transactions"] + G --> H["Проигрывание draw-записей в историческую доску"] +``` + +## Почему Berry Club хорошо учит истории в NEAR + +Berry Club удобно показывает обе стороны задачи: + +- чистое чтение текущего состояния через `get_lines` +- длинную историю вызовов `draw` с обычными аргументами `FunctionCall` +- формат доски, который легко декодировать и рендерить обычным JavaScript + +Это очень NEAR-подобная форма: один view-метод для текущего состояния, один write-метод для изменений и индексированная история, когда нужно объяснить, как это состояние вообще появилось. + +## 1. Сначала прочитайте текущую доску + +Живое демо использует `berryclub.ek.near` и читает доску через view-вызов `get_lines`: + +```javascript +await near.view({ + contractId: 'berryclub.ek.near', + methodName: 'get_lines', + args: { + lines: [...Array(50).keys()], + }, +}); +``` + +Это путь текущего состояния. Он не отвечает на вопрос, как доска пришла к такому виду. + +| Вопрос | Лучшая поверхность | Почему | +| --- | --- | --- | +| как доска выглядит сейчас? | [RPC `call_function`](https://docs.fastnear.com/ru/rpc/contract/call-function) | контракт уже отдаёт текущее состояние через `get_lines` | +| какие `draw` были в этой эпохе? | [`/v0/account`](https://docs.fastnear.com/ru/tx/account) + [`/v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | индексированная история даёт ограниченный набор записей и раскрытые аргументы | +| как доска выглядела в известной контрольной точке? | архивный RPC или полное проигрывание | можно напрямую материализовать состояние из архива или восстановить его самому по историческим записям | + +## 2. Как декодировать `get_lines` в сетку 50x50 + +Полезная часть Berry Club-разметки из `js.fastnear.com` — это декодер строк: + +- каждая строка приходит в base64 +- её нужно декодировать в байты +- первые 4 байта нужно пропустить +- дальше цвета читаются как 32-битные little-endian значения каждые 8 байт + +```javascript +function decodeLine(encodedLine) { + const bytes = Buffer.from(encodedLine, 'base64'); + const colors = []; + + for (let offset = 4; offset < bytes.length; offset += 8) { + colors.push(bytes.readUInt32LE(offset) & 0xffffff); + } + + return colors; +} +``` + +Примените это ко всем 50 строкам — и получите полную сетку 50x50, готовую к рендерингу. + +## 3. Ограничьте эпоху, которую хотите изучить + +Сначала ограничьте эпоху, прежде чем искать draw-записи. Проверочный снимок запуска в этом репозитории находится на блоке `21898354`, а средний снимок — на блоке `97601515`. + +Сначала зафиксируйте ближайший диапазон блоков: + +```bash +curl -sS https://tx.main.fastnear.com/v0/blocks \ + -H 'content-type: application/json' \ + --data '{ + "from_block_height": 21898350, + "to_block_height": 21898355, + "desc": false, + "limit": 5 + }' +``` + +Затем переключитесь на историю аккаунта и запросите активность Berry Club внутри ограниченного диапазона блоков: + +```bash +curl -sS https://tx.main.fastnear.com/v0/account \ + -H 'content-type: application/json' \ + --data '{ + "account_id": "berryclub.ek.near", + "is_function_call": true, + "is_receiver": true, + "is_real_receiver": true, + "from_tx_block_height": 97576515, + "to_tx_block_height": 97601516, + "desc": true, + "limit": 40 + }' +``` + +Здесь полезна именно такая последовательность: + +- `/v0/blocks` помогает понять соседство по высотам блоков +- `/v0/account` возвращает кандидатные хеши транзакций Berry Club внутри этого диапазона + +## 4. Раскройте транзакции и оставьте только `draw` + +Когда кандидатные хеши уже есть, раскройте их и оставьте только верхнеуровневые вызовы `draw`, где получатель — `berryclub.ek.near`. + +Аргументы вызова — это обычные данные `FunctionCall` вида `{ pixels: [...] }`: + +```bash +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{ + "tx_hashes": [ + "Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ", + "8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL" + ] + }' | jq '.transactions[] + | select(.transaction.receiver_id == "berryclub.ek.near") + | .transaction.actions[]?.FunctionCall + | select(.method_name == "draw") + | { + method_name, + args: (.args | @base64d | fromjson) + }' +``` + +Это даёт всё, что нужно для проигрывания: + +- какая транзакция записывала пиксели +- какие координаты были затронуты +- какие цвета были записаны + +## 5. Проиграйте исторические `draw`-вызовы в доску + +Для полного проигрывания держите в памяти массив 50x50 и применяйте раскрытые транзакции `draw` от старых к новым. + +```javascript +const board = Array.from({ length: 50 }, () => Array(50).fill(0)); + +function applyDraw(boardState, drawArgs) { + for (const pixel of drawArgs.pixels) { + if (pixel.x < 0 || pixel.x >= 50 || pixel.y < 0 || pixel.y >= 50) { + continue; + } + + boardState[pixel.y][pixel.x] = pixel.color; + } +} + +for (const drawTx of drawTransactionsOldestFirst) { + applyDraw(board, drawTx.args); +} +``` + +Важно не путать два разных пути: + +- `get_lines` — это текущее состояние +- `tx/account` плюс `tx/transactions` — это материал для проигрывания + +## 6. Готовые контрольные точки по эпохам + +Галерея ниже использует уже сохранённые данные снимков, собранные из mainnet-истории Berry Club: + +- `launch` — последний успешный `draw` в пределах первых 24 часов после первого успешного draw +- `mid` — последний успешный `draw` не позже средней временной точки всей истории Berry Club +- `recent` — последний успешный `draw`, который увидел скрипт при пересборке снимков + +Галерея снимков: контрольные точки launch, mid и recent из сохранённого `src/data/berryClubSnapshots.json`. + +Сейчас эти снимки привязаны к таким транзакциям: + +- `launch`: `BDNFpCpLXjBrgjR6z6wCZmB9EWdHnVMdqau3iTWTRE5H` на блоке `21898354` +- `mid`: `Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ` на блоке `97601515` +- `recent`: `8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL` на блоке `194588754` + +## Куда идти за подписанными взаимодействиями + +Эта страница должна оставаться в режиме чтения. + +Если нужны живые подписанные сценарии для `draw` и `buy_tokens`, переходите сюда: + +- [js.fastnear.com](https://js.fastnear.com/) +- [Berry Club example в fastnear/js-monorepo](https://github.com/fastnear/js-monorepo/tree/main/examples/static/berryclub) + +Именно там уместны кошелёк и подписанные действия. Эта страница посвящена историческому восстановлению. + +--- + +## OutLayer: как проследить один запрос от вызова до callback + +- HTML-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer +- Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer.md + +**Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) + +{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как использовать FastNear RPC и Transactions API, чтобы разбирать живое исполнение OutLayer в терминах NEAR. Он отделяет видимый request/worker/callback-поток, который уже можно трассировать через FastNear, от задокументированного внутреннего пути yield/resume и CKD/MPC. */} + +# OutLayer: как проследить один запрос от вызова до callback + +Используйте этот разбор, когда вопрос звучит так: «я вижу OutLayer в цепочке. Какая транзакция открыла работу, какая более поздняя транзакция пришла от воркера и где проявились callback, списание и возврат средств?» + +Это продвинутый разбор асинхронного исполнения в семействе Transactions examples. Держите NEAR-рамку первой: один `FunctionCall` со стороны вызывающего, одна более поздняя транзакция со стороны воркера, и квитанции только тогда, когда действительно нужно разбирать фазу завершения. + + Стратегия + Сначала найдите caller-транзакцию и worker-транзакцию, а receipts подключайте только тогда, когда настоящим вопросом становится finish-путь. + + 01POST /v0/account — самый быстрый способ найти caller-side и worker-side хеши из одной и той же истории. + 02POST /v0/transactions раскрывает оба хеша и показывает читаемые request, worker-resolution и ранние логи. + 03Только после этого имеет смысл разбирать callback, списание и refund на уровне receipts или уходить в точные RPC-проверки идентичности. + +Полезные ссылки: + +- [История аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +- [Просмотр аккаунта](https://docs.fastnear.com/ru/rpc/account/view-account) +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) + +## Короткая версия + +Если вы видите активность OutLayer в цепочке, практические вопросы обычно такие: + +- какая транзакция создала асинхронную единицу работы? +- какая более поздняя транзакция пришла от воркера? +- где именно проявились callback, списание и возврат средств? + +Это не вопрос о текущем состоянии. Это вопрос об истории исполнения. + +Полезный ход через FastNear — связать одну транзакцию `request_execution` со стороны вызывающего с одной транзакцией разрешения со стороны воркера, а к receipt переходить только на этапе завершения. + +```mermaid +sequenceDiagram + autonumber + participant Caller as "Вызывающая сторона" + participant Outlayer as "outlayer.*" + participant Worker as "worker.outlayer.*" + participant Near as "исполняющая среда NEAR" + + Caller->>Outlayer: FunctionCall request_execution + Outlayer-->>Near: создаёт асинхронную работу и контекст учёта + Worker->>Outlayer: resolve_execution или submit_execution_output_and_resolve + Outlayer-->>Near: логи завершения, путь callback, списание, возвраты +``` + +Всё это уже видно сегодня через FastNear и RPC. + +## 1. Раскройте одну транзакцию запроса и одно разрешение воркера + +Если хотите сразу увидеть всю форму потока, начните с уже известной пары хешей и раскройте оба. + +Эта пара работала 18 апреля 2026 года: + +- `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` — `request_execution` со стороны вызывающего +- `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` — `submit_execution_output_and_resolve` со стороны воркера + +```bash title="Раскройте хеш запроса и хеш разрешения воркера" +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{ + "tx_hashes":[ + "AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4", + "AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs" + ] + }' | jq '.transactions[] | { + hash: .transaction.hash, + signer: .transaction.signer_id, + receiver: .transaction.receiver_id, + actions: [.transaction.actions[] | keys[0]], + logs: (.receipts[0].execution_outcome.outcome.logs[:2]) + }' +``` + +В этом выборочном выводе: + +- хеш запроса шёл от `solarflux.near` к `outlayer.near` +- в логах фигурировал разрешённый проект: `zavodil.near/near-email` +- хеш воркера шёл от `worker.outlayer.near` к `outlayer.near` +- в логах воркера было `Stored pending output` и `Resolving execution ... (combined flow)` + +Этого уже достаточно для видимой истории в терминах NEAR: исходный `FunctionCall` создал асинхронную единицу работы, позже воркер вернулся как отдельный подписант, а контракт разрешил результат в цепочке. + +Если копировать с этой страницы только одну команду, то именно эту. + +## 2. Найдите два нужных хеша сами + +Если пары хешей у вас ещё нет, переключитесь на [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account). + +```bash title="Недавняя mainnet-активность для outlayer.near" +curl -sS https://tx.main.fastnear.com/v0/account \ + -H 'content-type: application/json' \ + --data '{"account_id":"outlayer.near","desc":true}' \ + | jq '{txs_count, first: .account_txs[0]}' +``` + +18 апреля 2026 года эта поверхность показывала более 5 000 трассированных транзакций для `outlayer.near`, а самый свежий выборочный хеш был таким: + +```text +AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs +``` + +Этот хеш не был исходным пользовательским запросом. Это уже было последующее действие со стороны воркера. + +Именно поэтому история аккаунта — правильный первый поиск: здесь задача не в том, чтобы описать контракт целиком, а в том, чтобы найти две конкретные транзакции в одной истории исполнения. + +```mermaid +flowchart TD + A["Transactions API: /v0/account для outlayer.*"] --> B["Найдите недавние хеши транзакций"] + B --> C["Хеш request_execution со стороны вызывающего"] + B --> D["Хеш разрешения со стороны воркера"] + C --> E["Раскройте оба через /v0/transactions"] + D --> E + E --> F["Проверьте логи, request_id, project/source, callback-receipt, списание и возвраты"] +``` + +## 3. Разберите фазу callback и возврата средств + +Если нужно пройти дальше, чем просто «воркер вернул результат», посмотрите список receipt у раскрытой воркерской транзакции. + +```bash title="Показать последующие действия на уровне receipt для разрешения воркером" +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{"tx_hashes":["AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs"]}' \ + | jq '.transactions[0] | { + hash: .transaction.hash, + receipts: [ + .receipts[] | { + predecessor: .receipt.predecessor_id, + receiver: .receiver_id, + actions: [.receipt.receipt.Action.actions[] | keys[0]], + logs: .execution_outcome.outcome.logs + } + ] + }' +``` + +На что смотреть: + +- `FunctionCall:on_execution_response` +- логи списания вроде `[[yNEAR charged: "..."]]` +- события завершения вроде `execution_completed` +- последующие receipt `Transfer` + +Здесь понятие receipt как раз становится правильной абстракцией: не в начале урока, а тогда, когда уже отлаживается реальный путь завершения. + +## 4. Подтвердите контракт, если нужна точная проверка + +Если нужна точная проверка аккаунта и `code_hash`, используйте сырой RPC. Это шаг для проверки идентичности, а не для восстановления истории исполнения. + +```bash title="Mainnet: view_account для outlayer.near" +curl -sS https://rpc.mainnet.fastnear.com \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "id":"1", + "method":"query", + "params":{ + "request_type":"view_account", + "finality":"final", + "account_id":"outlayer.near" + } + }' | jq '.result | {amount, locked, code_hash, storage_usage}' +``` + +```bash title="Testnet: view_account для outlayer.testnet" +curl -sS https://rpc.testnet.fastnear.com \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "id":"1", + "method":"query", + "params":{ + "request_type":"view_account", + "finality":"final", + "account_id":"outlayer.testnet" + } + }' | jq '.result | {amount, locked, code_hash, storage_usage}' +``` + +По состоянию на 18 апреля 2026 года оба контракта возвращали один и тот же `code_hash`: + +```text +94uKcoDB3QbEpxDj1xsw9CQwu9bAY1PoVPr2BZYRRv4K +``` + +Это сильный сигнал, что на обеих сетях развёрнут один и тот же бинарник контракта. + +## 5. Что происходит внутри? + +Видимая история выше — это то, что NEAR-разработчику нужно сначала. Более глубокий слой объясняет, почему этот поток вообще интересен. + +### Что можно наблюдать уже сейчас + +Через FastNear и RPC уже видно: + +- вызывающую транзакцию `request_execution` +- воркерскую `resolve_execution` или `submit_execution_output_and_resolve` +- завершающие receipt, где материализуются callback, списание и возврат средств + +Интеграция вашего контракта при этом остаётся обычной асинхронной композицией в NEAR: вы вызываете `outlayer.*`, а потом обрабатываете свой callback. + +### Что задокументировано как внутренний механизм + +Документация OutLayer описывает более глубокий внутренний слой: `outlayer.*` использует семантику NEAR `yield/resume` как свою внутреннюю асинхронную границу, внешняя работа выполняется в TEE-воркерах, а защищённые секреты проходят через отдельный путь доверия, где TEE-keystore получает DAO-gated CKD через MPC signer. + +Для NEAR-разработчика здесь важна точность: мы не говорим, что ваш вызывающий контракт сам пишет `promise_yield_create`. Примитивы `yield/resume` в NEAR работают только в рамках одного и того же аккаунта, поэтому если этот механизм используется здесь, то yielding и resuming делает `outlayer.*`, а не исходный вызывающий контракт. Для сырой модели выполнения смотрите [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features). + +Документация Secrets / CKD описывает этот путь keystore как двухуровневый: сначала keystore получает derivation key через DAO-gated путь к MPC, а затем использует уже полученную derivation capability для защищённых секретов во время исполнений приложения. Это объяснение доверительной модели, а не утверждение, что каждое обычное исполнение OutLayer делает новый DAO -> MPC round trip. + +Публичный gateway-аккаунт для этого пути keystore / DAO в наших текущих публичных chain-данных всё ещё не подтверждён, поэтому эту часть надо держать в корзине «задокументировано внутри», а не в корзине «уже наблюдается сейчас». + +```mermaid +flowchart TB + subgraph Observable["Что уже можно наблюдать через FastNear / RPC"] + Caller["Пользователь или вызывающий контракт"] -->|FunctionCall request_execution| Entry["outlayer.*"] + Entry -->|поздняя активность воркера| Worker["worker.outlayer.*"] + Worker -->|resolve_execution или submit_execution_output_and_resolve| Entry + Entry -->|on_execution_response, списание, refund-переводы| Finish["Callback / finish receipt"] + end + + subgraph Internal["Что задокументировано как внутренний слой"] + Yield["внутренняя yield-точка в outlayer.*"] --> TEE["TEE-воркер исполняет WASM"] + TEE -->|resume с результатом| Yield + TEE --> Keystore["TEE-keystore для защищённых секретов"] + Keystore -->|задокументированный DAO-gated request_key| DAO["DAO gateway (задокументирован)"] + DAO -->|задокументированный CKD request| MPC["v1.signer / v1.signer-prod.testnet"] + MPC -->|derivation key для keystore| Keystore + end + + Entry -. та же логическая история исполнения .-> Yield + + classDef observable fill:#e7f1ff,stroke:#2563eb,color:#0f172a; + classDef internal fill:#fff4e5,stroke:#b45309,color:#0f172a; + class Caller,Entry,Worker,Finish observable; + class Yield,TEE,Keystore,DAO,MPC internal; +``` + +## Куда читать дальше + +- [Transactions API](https://docs.fastnear.com/ru/tx) для истории аккаунта, receipt и раскрытия транзакций +- [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features) для семантики `yield/resume` в NEAR +- [Асинхронная модель](https://docs.fastnear.com/ru/transaction-flow/async-model) для лексики promise и callback +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) для задокументированного контрактного интерфейса +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) для задокументированного пути keystore, DAO и MPC + +--- + ## RPC протокола NEAR: Просмотр ключа доступа - HTML-маршрут: https://docs.fastnear.com/ru/rpcs/account/view_access_key diff --git a/static/ru/llms.txt b/static/ru/llms.txt index e69a2a9..ed0765d 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -37,7 +37,9 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для polling по оптимистичным и финализированным блокам и перехода к RPC, когда это нужно. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. - [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. -- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents. +- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования и подробные разборы для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents. +- [Berry Club: как восстанавливать исторические доски](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Используйте Transactions API, RPC get_lines и проигрывание draw-вызовов, чтобы восстанавливать доски Berry Club по историческим эпохам. +- [OutLayer: как проследить один запрос от вызова до callback](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API и RPC, чтобы проследить реальный OutLayer-запрос от исходного FunctionCall через транзакцию воркера до callback- и refund-фазы. ## Снапшоты diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index c6093a6..9e91c08 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -6,6 +6,13 @@ Используйте это расследование, когда нужно заметить новый блок как можно раньше, но финальный ответ всё равно должен опираться на финализированный блок и иногда на точное чтение через RPC. + Стратегия + Пусть NEAR Data сначала скажет, что что-то изменилось, а затем переиспользуйте то же семейство блоков для стабильного подтверждения. + + 01block-optimistic или last-block-optimistic дают самый ранний полезный сигнал. + 02block или last-block-final подтверждают, что то же наблюдение дошло до финализированной истории. + 03RPC block нужен только в самом конце, когда уже известна точная высота или хеш. + **Цель** - Быстро заметить недавний блок, а затем проверить то же самое, когда догонит finality. diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index c6093a6..9e91c08 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -6,6 +6,13 @@ Используйте это расследование, когда нужно заметить новый блок как можно раньше, но финальный ответ всё равно должен опираться на финализированный блок и иногда на точное чтение через RPC. + Стратегия + Пусть NEAR Data сначала скажет, что что-то изменилось, а затем переиспользуйте то же семейство блоков для стабильного подтверждения. + + 01block-optimistic или last-block-optimistic дают самый ранний полезный сигнал. + 02block или last-block-final подтверждают, что то же наблюдение дошло до финализированной истории. + 03RPC block нужен только в самом конце, когда уже известна точная высота или хеш. + **Цель** - Быстро заметить недавний блок, а затем проверить то же самое, когда догонит finality. diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index 64a7cc1..4f9eb62 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -4,12 +4,21 @@ Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. -## Готовые сценарии +## Механика аккаунтов и ключей + +Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. ### Проверить и удалить старые function-call-ключи Near Social Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. + Стратегия + Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. + + 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. + 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. + 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. + **Что вы делаете** - Через сам RPC получаете полный список access key аккаунта. @@ -248,10 +257,230 @@ fi Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. +### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? + +Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. + + Стратегия + Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. + + 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. + 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. + 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. + +**Что вы делаете** + +- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. +- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. +- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. +- Подтягиваете кандидата по транзакциям и различаете три разных ключа: + - ключ, который был добавлен + - public key верхнеуровневого signer + - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` + +Сразу важны три детали про nonce: + +- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. +- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. +- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' + +# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: +# export ACCOUNT_ID=mike.near +# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' +``` + +1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$TARGET_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" + } + }')" \ + | tee /tmp/key-origin-view.json >/dev/null + +CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" +ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" +SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" +SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" + +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_public_key "$TARGET_PUBLIC_KEY" \ + --argjson current_nonce "$CURRENT_NONCE" \ + --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ + --argjson search_from "$SEARCH_FROM" \ + --argjson search_to "$SEARCH_TO" \ + --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ + account_id: $account_id, + target_public_key: $target_public_key, + current_nonce: $current_nonce, + estimated_receipt_block: $estimated_receipt_block, + search_from_tx_block_height: $search_from, + search_to_tx_block_height: $search_to, + permission: ($permission | fromjson) + }' +``` + +Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. + +2. Ищите историю аккаунта только внутри этого диапазона блоков. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_tx_block_height "$SEARCH_FROM" \ + --argjson to_tx_block_height "$SEARCH_TO" '{ + account_id: $account_id, + is_real_signer: true, + from_tx_block_height: $from_tx_block_height, + to_tx_block_height: $to_tx_block_height, + desc: false, + limit: 50 + }')" \ + | tee /tmp/key-origin-candidates.json >/dev/null + +jq '{ + txs_count, + candidate_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_signer, + is_real_signer, + is_predecessor, + is_receiver + } + ] +}' /tmp/key-origin-candidates.json +``` + +Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. + +3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. + +```bash +TX_HASHES_JSON="$( + jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json +)" + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ + | tee /tmp/key-origin-transactions.json >/dev/null + +jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "direct", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $tx.transaction.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }), + ($tx.transaction.actions[]? + | .Delegate? + | .delegate_action as $delegate + | $delegate.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "delegated", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $delegate.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }) + ) + | { + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + tx_block_hash: $tx.execution_outcome.block_hash, + receiver_id: $tx.transaction.receiver_id + } + . +' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json +``` + +Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. + +Для примерного ключа `mike.near` выше совпадение оказывается делегированным: + +- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` +- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` +- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` +- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` + +4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. + +```bash +ADD_KEY_RECEIPT_ID="$( + jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | .receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) + | .receipt.receipt_id + ' /tmp/key-origin-transactions.json | head -n 1 +)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receipt_block_height: .receipt.block_height, + tx_block_height: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }' +``` + +Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. + +**Зачем нужен следующий шаг?** + +Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. + ### Проверить регистрацию FT storage и затем перевести токены Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». + Стратегия + Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. + + 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. + 02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит. + 03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог. + **Сеть** - testnet @@ -548,10 +777,21 @@ curl -s "$RPC_URL" \ Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +## Точные чтения NEAR Social и BOS + +Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. + ### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». + Стратегия + Спросите у social.near ровно о двух вещах, которые важны до подписи. + + 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. + 02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near. + 03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer. + Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: - есть ли у целевого аккаунта storage на `social.near`? @@ -742,6 +982,13 @@ jq -n \ Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». + Стратегия + Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. + + 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. + 02RPC call_function get читает точный исходник widget/Profile. + 03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples. + **Официальные ссылки** - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index 64a7cc1..4f9eb62 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -4,12 +4,21 @@ Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. -## Готовые сценарии +## Механика аккаунтов и ключей + +Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. ### Проверить и удалить старые function-call-ключи Near Social Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. + Стратегия + Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. + + 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. + 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. + 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. + **Что вы делаете** - Через сам RPC получаете полный список access key аккаунта. @@ -248,10 +257,230 @@ fi Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. +### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? + +Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. + + Стратегия + Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. + + 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. + 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. + 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. + +**Что вы делаете** + +- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. +- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. +- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. +- Подтягиваете кандидата по транзакциям и различаете три разных ключа: + - ключ, который был добавлен + - public key верхнеуровневого signer + - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` + +Сразу важны три детали про nonce: + +- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. +- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. +- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' + +# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: +# export ACCOUNT_ID=mike.near +# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' +``` + +1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$TARGET_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" + } + }')" \ + | tee /tmp/key-origin-view.json >/dev/null + +CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" +ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" +SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" +SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" + +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_public_key "$TARGET_PUBLIC_KEY" \ + --argjson current_nonce "$CURRENT_NONCE" \ + --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ + --argjson search_from "$SEARCH_FROM" \ + --argjson search_to "$SEARCH_TO" \ + --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ + account_id: $account_id, + target_public_key: $target_public_key, + current_nonce: $current_nonce, + estimated_receipt_block: $estimated_receipt_block, + search_from_tx_block_height: $search_from, + search_to_tx_block_height: $search_to, + permission: ($permission | fromjson) + }' +``` + +Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. + +2. Ищите историю аккаунта только внутри этого диапазона блоков. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_tx_block_height "$SEARCH_FROM" \ + --argjson to_tx_block_height "$SEARCH_TO" '{ + account_id: $account_id, + is_real_signer: true, + from_tx_block_height: $from_tx_block_height, + to_tx_block_height: $to_tx_block_height, + desc: false, + limit: 50 + }')" \ + | tee /tmp/key-origin-candidates.json >/dev/null + +jq '{ + txs_count, + candidate_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_signer, + is_real_signer, + is_predecessor, + is_receiver + } + ] +}' /tmp/key-origin-candidates.json +``` + +Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. + +3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. + +```bash +TX_HASHES_JSON="$( + jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json +)" + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ + | tee /tmp/key-origin-transactions.json >/dev/null + +jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "direct", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $tx.transaction.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }), + ($tx.transaction.actions[]? + | .Delegate? + | .delegate_action as $delegate + | $delegate.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "delegated", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $delegate.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }) + ) + | { + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + tx_block_hash: $tx.execution_outcome.block_hash, + receiver_id: $tx.transaction.receiver_id + } + . +' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json +``` + +Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. + +Для примерного ключа `mike.near` выше совпадение оказывается делегированным: + +- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` +- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` +- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` +- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` + +4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. + +```bash +ADD_KEY_RECEIPT_ID="$( + jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | .receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) + | .receipt.receipt_id + ' /tmp/key-origin-transactions.json | head -n 1 +)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receipt_block_height: .receipt.block_height, + tx_block_height: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }' +``` + +Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. + +**Зачем нужен следующий шаг?** + +Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. + ### Проверить регистрацию FT storage и затем перевести токены Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». + Стратегия + Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. + + 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. + 02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит. + 03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог. + **Сеть** - testnet @@ -548,10 +777,21 @@ curl -s "$RPC_URL" \ Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +## Точные чтения NEAR Social и BOS + +Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. + ### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». + Стратегия + Спросите у social.near ровно о двух вещах, которые важны до подписи. + + 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. + 02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near. + 03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer. + Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: - есть ли у целевого аккаунта storage на `social.near`? @@ -742,6 +982,13 @@ jq -n \ Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». + Стратегия + Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. + + 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. + 02RPC call_function get читает точный исходник widget/Profile. + 03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples. + **Официальные ссылки** - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) diff --git a/static/ru/snapshots/examples.md b/static/ru/snapshots/examples.md index 907dc34..83106a7 100644 --- a/static/ru/snapshots/examples.md +++ b/static/ru/snapshots/examples.md @@ -6,6 +6,13 @@ Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. + Стратегия + Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него. + + 01Сначала решите, нужен ли вам optimized fast-rpc, обычный RPC или архивный режим. + 02Если нужен архив, сначала зафиксируйте одну точную высоту snapshot-блока и дальше переиспользуйте только её. + 03Выполняйте только команды выбранного пути и не смешивайте optimized, standard и archival шаги в одном сценарии. + **Цель** - Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. diff --git a/static/ru/snapshots/examples/index.md b/static/ru/snapshots/examples/index.md index 907dc34..83106a7 100644 --- a/static/ru/snapshots/examples/index.md +++ b/static/ru/snapshots/examples/index.md @@ -6,6 +6,13 @@ Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. + Стратегия + Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него. + + 01Сначала решите, нужен ли вам optimized fast-rpc, обычный RPC или архивный режим. + 02Если нужен архив, сначала зафиксируйте одну точную высоту snapshot-блока и дальше переиспользуйте только её. + 03Выполняйте только команды выбранного пути и не смешивайте optimized, standard и archival шаги в одном сценарии. + **Цель** - Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. diff --git a/static/ru/structured-data/site-graph.json b/static/ru/structured-data/site-graph.json index ff8dc10..5694603 100644 --- a/static/ru/structured-data/site-graph.json +++ b/static/ru/structured-data/site-graph.json @@ -6454,6 +6454,32 @@ "routeType": "docs", "url": "https://docs.fastnear.com/ru/tx/examples" }, + { + "entityIds": { + "familyIds": [], + "mainEntityId": null, + "pageId": "https://docs.fastnear.com/ru/tx/examples/berry-club#page" + }, + "indexable": true, + "markdownMirrorUrl": "https://docs.fastnear.com/ru/tx/examples/berry-club.md", + "pageSchemaType": "TechArticle", + "route": "/ru/tx/examples/berry-club", + "routeType": "docs", + "url": "https://docs.fastnear.com/ru/tx/examples/berry-club" + }, + { + "entityIds": { + "familyIds": [], + "mainEntityId": null, + "pageId": "https://docs.fastnear.com/ru/tx/examples/outlayer#page" + }, + "indexable": true, + "markdownMirrorUrl": "https://docs.fastnear.com/ru/tx/examples/outlayer.md", + "pageSchemaType": "TechArticle", + "route": "/ru/tx/examples/outlayer", + "routeType": "docs", + "url": "https://docs.fastnear.com/ru/tx/examples/outlayer" + }, { "entityIds": { "familyIds": [ diff --git a/static/ru/transfers/examples.md b/static/ru/transfers/examples.md index 9012c41..b898351 100644 --- a/static/ru/transfers/examples.md +++ b/static/ru/transfers/examples.md @@ -6,6 +6,13 @@ Используйте этот сценарий, когда история звучит так: «я вижу, что средства двигались, но хочу получить точную опорную точку исполнения для этого движения, не затягивая сразу всю историю аккаунта». + Стратегия + Сначала оставайтесь на узкой истории движения, а затем один раз переключайтесь в историю исполнения. + + 01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять. + 02jq поднимает один receipt_id, не затягивая остальную историю аккаунта. + 03POST /v0/receipt превращает это движение в опорную точку исполнения, которую уже можно продолжать в /tx. + **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. diff --git a/static/ru/transfers/examples/index.md b/static/ru/transfers/examples/index.md index 9012c41..b898351 100644 --- a/static/ru/transfers/examples/index.md +++ b/static/ru/transfers/examples/index.md @@ -6,6 +6,13 @@ Используйте этот сценарий, когда история звучит так: «я вижу, что средства двигались, но хочу получить точную опорную точку исполнения для этого движения, не затягивая сразу всю историю аккаунта». + Стратегия + Сначала оставайтесь на узкой истории движения, а затем один раз переключайтесь в историю исполнения. + + 01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять. + 02jq поднимает один receipt_id, не затягивая остальную историю аккаунта. + 03POST /v0/receipt превращает это движение в опорную точку исполнения, которую уже можно продолжать в /tx. + **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index a42bb2d..0c24ba4 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -1,8 +1,10 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) -## Готовые расследования +Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. -Эти расследования намеренно выстроены от самого простого якоря к самой насыщенной форензике: сначала один tx hash, затем один receipt, затем паттерны с ошибками и async, и только потом более глубокие расследования по SocialDB и NEAR Intents. +## С чего начать + +Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. ### У меня есть один хеш транзакции. Что вообще произошло? @@ -10,6 +12,13 @@ Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. + Стратегия + Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно. + + 01POST /v0/transactions даёт signer, receiver, типы действий, высоту блока и первую receipt-точку передачи. + 02RPC EXPERIMENTAL_tx_status нужен только для точной протокольной семантики успеха. + 03POST /v0/receipt имеет смысл только тогда, когда именно первая receipt становится новой опорной точкой. + **Цель** - Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. @@ -46,7 +55,7 @@ flowchart LR - в какой блок попала - одно простое предложение, которое объясняет транзакцию без receipt-жаргона -### Shell-сценарий: от хеша транзакции к человеческой истории +#### Shell-сценарий: от хеша транзакции к человеческой истории Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. @@ -144,6 +153,13 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. + Стратегия + Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой. + + 01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt. + 02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий. + 03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола». + **Цель** - Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. @@ -181,7 +197,11 @@ flowchart LR - была ли квитанция главным событием или только шагом в большом каскаде - одно предложение простым языком, которое можно без правок вставить коллеге в чат -### Shell-сценарий: от страшного receipt ID к человеческой истории +#### Shell-сценарий: от страшного receipt ID к человеческой истории + +## Ошибки и async + +Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и порядок callback-ов. Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. @@ -271,6 +291,13 @@ jq -r ' В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. + Стратегия + Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов. + + 01POST /v0/transactions показывает упорядоченный пакет ровно в том виде, в каком его подписал signer. + 02RPC EXPERIMENTAL_tx_status показывает падающий FunctionCall и точную причину отказа на уровне протокола. + 03RPC view_account по предполагаемому новому аккаунту доказывает, закрепились ли вообще ранние create, fund и add-key действия. + **Цель** - На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. @@ -320,7 +347,7 @@ flowchart LR - доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality - короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` -### Shell-сценарий неудачной транзакции с пакетом действий +#### Shell-сценарий неудачной транзакции с пакетом действий Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. @@ -442,6 +469,13 @@ jq '{ Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. + Стратегия + Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась. + + 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. + 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. + 03RPC call_function на роутере показывает, закрепилось ли собственное локальное изменение состояния из первой receipt. + **Цель** - Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. @@ -489,7 +523,7 @@ flowchart LR - что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился - одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции -### Shell-сценарий более позднего сбоя receipt +#### Shell-сценарий более позднего сбоя receipt Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». @@ -628,6 +662,13 @@ jq '{ Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» + Стратегия + Смотрите на два хеша как на одну async-историю: докажите, что работа была жива, восстановите запрошенный порядок и сравните его с видимым downstream-state. + + 01RPC call_function на view отложенной работы доказывает, что promise-работа действительно уже была жива до resume-шага. + 02POST /v0/transactions даёт оба block-anchor и точный порядок, который запросила resume-транзакция. + 03RPC EXPERIMENTAL_tx_status вместе с downstream-view доказывают, где callback-и реально выполнились и в каком видимом порядке. + **Цель** - Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. @@ -675,10 +716,21 @@ flowchart LR - в каких блоках стали видны изменения состояния - какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования +## Доказательства по SocialDB + +Эти примеры стартуют с читаемого состояния в NEAR Social и откатываются назад к точной записи, которая это состояние создала. + ### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». + Стратегия + Начните с читаемого значения поля, а затем превратите его field-level block в один receipt и одну транзакцию записи. + + 01NEAR Social POST /get даёт текущее значение profile.name и field-level :block. + 02POST /v0/block превращает этот блок в конкретный receipt и хеш транзакции mike.near -> social.near. + 03POST /v0/transactions доказывает payload записи, а RPC call_function get подтверждает, что поле и сейчас разрешается так же. + **Цель** - Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. @@ -707,7 +759,7 @@ flowchart LR - доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload - различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) -### Shell-сценарий доказательства поля профиля в NEAR Social +#### Shell-сценарий доказательства поля профиля в NEAR Social Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. @@ -871,6 +923,13 @@ NEAR Social даёт семантическое значение поля. FastN Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». + Стратегия + Начните с семантической связи подписки, а затем используйте блок записи как мост назад к одному receipt и одной транзакции. + + 01NEAR Social POST /get даёт читаемую связь подписки и SocialDB :block, где она была записана. + 02POST /v0/block превращает этот блок записи в конкретный receipt и хеш транзакции за этой связью. + 03POST /v0/transactions доказывает payload с graph.follow и index.graph, а RPC call_function get подтверждает, что связь и сейчас существует. + **Цель** - Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. @@ -899,7 +958,7 @@ NEAR Social даёт семантическое значение поля. FastN - доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` - различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) -### Shell-сценарий доказательства подписки в NEAR Social +#### Shell-сценарий доказательства подписки в NEAR Social Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. @@ -1068,6 +1127,13 @@ NEAR Social даёт семантическую связь. FastNear block recei - восстанавливаем исходную транзакцию - декодируем payload `set` и доказываем, что он действительно нёс исходник виджета + Стратегия + Смотрите на write-block виджета как на весь мост сразу: блок в receipt, receipt в транзакцию, транзакцию в исходник. + + 01POST /v0/block начинает с блока виджета и сужает его до одного receipt mob.near -> social.near. + 02POST /v0/transactions превращает этот receipt в один читаемый payload set с исходником виджета. + 03RPC call_function get — это финальное подтверждение текущего состояния, что виджет и сейчас существует. + **Цель** - Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. @@ -1098,7 +1164,11 @@ NEAR Social даёт семантическую связь. FastNear block recei - доказательство, что payload записи был `set` с `mob.near/widget/Profile` - одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» -### Shell-сценарий доказательства записи виджета в NEAR Social +#### Shell-сценарий доказательства записи виджета в NEAR Social + +## Трассировка расчёта + +Это самое насыщенное расследование на странице: один живой расчёт NEAR Intents от верхнеуровневой транзакции до receipts и событий, которые его объясняют. Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. @@ -1236,6 +1306,13 @@ jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». + Стратегия + Смотрите на один расчёт как на читаемую трассу, а не как на теорию протокола с первой строки. + + 01POST /v0/transactions даёт каркас расчёта: входную точку, первые downstream-контракты и ранние логи. + 02POST /v0/block переиспользует тот же якорь, когда нужен контекст включающего блока вокруг этого расчёта. + 03RPC EXPERIMENTAL_tx_status нужен там, где уже требуется канонический DAG по receipt и имена событий, которые доказывают реальное движение активов. + **Цель** - Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. @@ -1294,7 +1371,7 @@ flowchart LR Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. -### Shell-сценарий расчёта NEAR Intents +#### Shell-сценарий расчёта NEAR Intents Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. diff --git a/static/ru/tx/examples/berry-club.md b/static/ru/tx/examples/berry-club.md new file mode 100644 index 0000000..5606276 --- /dev/null +++ b/static/ru/tx/examples/berry-club.md @@ -0,0 +1,226 @@ +**Источник:** [https://docs.fastnear.com/ru/tx/examples/berry-club](https://docs.fastnear.com/ru/tx/examples/berry-club) + +{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как восстанавливать доски Berry Club через FastNear. Он отделяет текущее состояние из get_lines от исторического разбора через диапазоны блоков, историю аккаунта, раскрытие транзакций и проигрывание draw-аргументов. */} + +# Berry Club: как восстанавливать исторические доски + +Используйте этот разбор, когда вопрос звучит так: «как Berry Club выглядел в определённую эпоху и какие `draw`-вызовы сделали доску именно такой?» + +Это read-only разбор из семейства Transactions examples. Если нужна только доска прямо сейчас, используйте `get_lines` и остановитесь. Если нужно объяснить, как доска пришла к такому виду, переключайтесь на историю блоков, историю аккаунта, раскрытые `draw`-вызовы и проигрывание. + + Стратегия + Сначала прочитайте живую доску, затем ограничьте эпоху и только после этого проигрывайте draw-вызовы, которые её объясняют. + + 01RPC call_function get_lines даёт текущую доску 50x50 и показывает, как выглядит «сейчас». + 02POST /v0/blocks вместе с POST /v0/account ограничивают одну эпоху и дают кандидатные хеши draw. + 03POST /v0/transactions раскрывает эти draw-вызовы, чтобы их можно было проиграть в исторические контрольные точки. + +Держите рядом: + +- [js.fastnear.com](https://js.fastnear.com/) +- [fastnear/js-monorepo](https://github.com/fastnear/js-monorepo) +- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +- [Transactions API: диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) +- [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) + +В этом руководстве история Berry Club разбирается только на mainnet. Снимки ниже собраны из воспроизводимых данных mainnet, которые уже сохранены в этом репозитории. + +## Короткая версия + +Berry Club даёт чистый view текущего состояния через `get_lines`, но не даёт готового эндпоинта вида «доска на блоке N». + +Из-за этого задача делится на две части: + +- используйте RPC `call_function`, когда вопрос звучит как «как доска выглядит сейчас?» +- используйте индексированную историю, когда вопрос звучит как «какие записи привели к этой доске?» +- используйте архивный RPC только тогда, когда нужно напрямую материализовать уже известную контрольную точку + +```mermaid +flowchart TD + A["RPC call_function: get_lines"] --> B["Текущая доска 50x50"] + C["Transactions API: /v0/blocks"] --> D["Ограничить эпоху"] + D --> E["/v0/account для berryclub.ek.near"] + E --> F["Кандидатные хеши draw-транзакций"] + F --> G["Раскрытие через /v0/transactions"] + G --> H["Проигрывание draw-записей в историческую доску"] +``` + +## Почему Berry Club хорошо учит истории в NEAR + +Berry Club удобно показывает обе стороны задачи: + +- чистое чтение текущего состояния через `get_lines` +- длинную историю вызовов `draw` с обычными аргументами `FunctionCall` +- формат доски, который легко декодировать и рендерить обычным JavaScript + +Это очень NEAR-подобная форма: один view-метод для текущего состояния, один write-метод для изменений и индексированная история, когда нужно объяснить, как это состояние вообще появилось. + +## 1. Сначала прочитайте текущую доску + +Живое демо использует `berryclub.ek.near` и читает доску через view-вызов `get_lines`: + +```javascript +await near.view({ + contractId: 'berryclub.ek.near', + methodName: 'get_lines', + args: { + lines: [...Array(50).keys()], + }, +}); +``` + +Это путь текущего состояния. Он не отвечает на вопрос, как доска пришла к такому виду. + +| Вопрос | Лучшая поверхность | Почему | +| --- | --- | --- | +| как доска выглядит сейчас? | [RPC `call_function`](https://docs.fastnear.com/ru/rpc/contract/call-function) | контракт уже отдаёт текущее состояние через `get_lines` | +| какие `draw` были в этой эпохе? | [`/v0/account`](https://docs.fastnear.com/ru/tx/account) + [`/v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | индексированная история даёт ограниченный набор записей и раскрытые аргументы | +| как доска выглядела в известной контрольной точке? | архивный RPC или полное проигрывание | можно напрямую материализовать состояние из архива или восстановить его самому по историческим записям | + +## 2. Как декодировать `get_lines` в сетку 50x50 + +Полезная часть Berry Club-разметки из `js.fastnear.com` — это декодер строк: + +- каждая строка приходит в base64 +- её нужно декодировать в байты +- первые 4 байта нужно пропустить +- дальше цвета читаются как 32-битные little-endian значения каждые 8 байт + +```javascript +function decodeLine(encodedLine) { + const bytes = Buffer.from(encodedLine, 'base64'); + const colors = []; + + for (let offset = 4; offset < bytes.length; offset += 8) { + colors.push(bytes.readUInt32LE(offset) & 0xffffff); + } + + return colors; +} +``` + +Примените это ко всем 50 строкам — и получите полную сетку 50x50, готовую к рендерингу. + +## 3. Ограничьте эпоху, которую хотите изучить + +Сначала ограничьте эпоху, прежде чем искать draw-записи. Проверочный снимок запуска в этом репозитории находится на блоке `21898354`, а средний снимок — на блоке `97601515`. + +Сначала зафиксируйте ближайший диапазон блоков: + +```bash +curl -sS https://tx.main.fastnear.com/v0/blocks \ + -H 'content-type: application/json' \ + --data '{ + "from_block_height": 21898350, + "to_block_height": 21898355, + "desc": false, + "limit": 5 + }' +``` + +Затем переключитесь на историю аккаунта и запросите активность Berry Club внутри ограниченного диапазона блоков: + +```bash +curl -sS https://tx.main.fastnear.com/v0/account \ + -H 'content-type: application/json' \ + --data '{ + "account_id": "berryclub.ek.near", + "is_function_call": true, + "is_receiver": true, + "is_real_receiver": true, + "from_tx_block_height": 97576515, + "to_tx_block_height": 97601516, + "desc": true, + "limit": 40 + }' +``` + +Здесь полезна именно такая последовательность: + +- `/v0/blocks` помогает понять соседство по высотам блоков +- `/v0/account` возвращает кандидатные хеши транзакций Berry Club внутри этого диапазона + +## 4. Раскройте транзакции и оставьте только `draw` + +Когда кандидатные хеши уже есть, раскройте их и оставьте только верхнеуровневые вызовы `draw`, где получатель — `berryclub.ek.near`. + +Аргументы вызова — это обычные данные `FunctionCall` вида `{ pixels: [...] }`: + +```bash +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{ + "tx_hashes": [ + "Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ", + "8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL" + ] + }' | jq '.transactions[] + | select(.transaction.receiver_id == "berryclub.ek.near") + | .transaction.actions[]?.FunctionCall + | select(.method_name == "draw") + | { + method_name, + args: (.args | @base64d | fromjson) + }' +``` + +Это даёт всё, что нужно для проигрывания: + +- какая транзакция записывала пиксели +- какие координаты были затронуты +- какие цвета были записаны + +## 5. Проиграйте исторические `draw`-вызовы в доску + +Для полного проигрывания держите в памяти массив 50x50 и применяйте раскрытые транзакции `draw` от старых к новым. + +```javascript +const board = Array.from({ length: 50 }, () => Array(50).fill(0)); + +function applyDraw(boardState, drawArgs) { + for (const pixel of drawArgs.pixels) { + if (pixel.x < 0 || pixel.x >= 50 || pixel.y < 0 || pixel.y >= 50) { + continue; + } + + boardState[pixel.y][pixel.x] = pixel.color; + } +} + +for (const drawTx of drawTransactionsOldestFirst) { + applyDraw(board, drawTx.args); +} +``` + +Важно не путать два разных пути: + +- `get_lines` — это текущее состояние +- `tx/account` плюс `tx/transactions` — это материал для проигрывания + +## 6. Готовые контрольные точки по эпохам + +Галерея ниже использует уже сохранённые данные снимков, собранные из mainnet-истории Berry Club: + +- `launch` — последний успешный `draw` в пределах первых 24 часов после первого успешного draw +- `mid` — последний успешный `draw` не позже средней временной точки всей истории Berry Club +- `recent` — последний успешный `draw`, который увидел скрипт при пересборке снимков + +Галерея снимков: контрольные точки launch, mid и recent из сохранённого `src/data/berryClubSnapshots.json`. + +Сейчас эти снимки привязаны к таким транзакциям: + +- `launch`: `BDNFpCpLXjBrgjR6z6wCZmB9EWdHnVMdqau3iTWTRE5H` на блоке `21898354` +- `mid`: `Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ` на блоке `97601515` +- `recent`: `8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL` на блоке `194588754` + +## Куда идти за подписанными взаимодействиями + +Эта страница должна оставаться в режиме чтения. + +Если нужны живые подписанные сценарии для `draw` и `buy_tokens`, переходите сюда: + +- [js.fastnear.com](https://js.fastnear.com/) +- [Berry Club example в fastnear/js-monorepo](https://github.com/fastnear/js-monorepo/tree/main/examples/static/berryclub) + +Именно там уместны кошелёк и подписанные действия. Эта страница посвящена историческому восстановлению. diff --git a/static/ru/tx/examples/berry-club/index.md b/static/ru/tx/examples/berry-club/index.md new file mode 100644 index 0000000..5606276 --- /dev/null +++ b/static/ru/tx/examples/berry-club/index.md @@ -0,0 +1,226 @@ +**Источник:** [https://docs.fastnear.com/ru/tx/examples/berry-club](https://docs.fastnear.com/ru/tx/examples/berry-club) + +{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как восстанавливать доски Berry Club через FastNear. Он отделяет текущее состояние из get_lines от исторического разбора через диапазоны блоков, историю аккаунта, раскрытие транзакций и проигрывание draw-аргументов. */} + +# Berry Club: как восстанавливать исторические доски + +Используйте этот разбор, когда вопрос звучит так: «как Berry Club выглядел в определённую эпоху и какие `draw`-вызовы сделали доску именно такой?» + +Это read-only разбор из семейства Transactions examples. Если нужна только доска прямо сейчас, используйте `get_lines` и остановитесь. Если нужно объяснить, как доска пришла к такому виду, переключайтесь на историю блоков, историю аккаунта, раскрытые `draw`-вызовы и проигрывание. + + Стратегия + Сначала прочитайте живую доску, затем ограничьте эпоху и только после этого проигрывайте draw-вызовы, которые её объясняют. + + 01RPC call_function get_lines даёт текущую доску 50x50 и показывает, как выглядит «сейчас». + 02POST /v0/blocks вместе с POST /v0/account ограничивают одну эпоху и дают кандидатные хеши draw. + 03POST /v0/transactions раскрывает эти draw-вызовы, чтобы их можно было проиграть в исторические контрольные точки. + +Держите рядом: + +- [js.fastnear.com](https://js.fastnear.com/) +- [fastnear/js-monorepo](https://github.com/fastnear/js-monorepo) +- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +- [Transactions API: диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) +- [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) + +В этом руководстве история Berry Club разбирается только на mainnet. Снимки ниже собраны из воспроизводимых данных mainnet, которые уже сохранены в этом репозитории. + +## Короткая версия + +Berry Club даёт чистый view текущего состояния через `get_lines`, но не даёт готового эндпоинта вида «доска на блоке N». + +Из-за этого задача делится на две части: + +- используйте RPC `call_function`, когда вопрос звучит как «как доска выглядит сейчас?» +- используйте индексированную историю, когда вопрос звучит как «какие записи привели к этой доске?» +- используйте архивный RPC только тогда, когда нужно напрямую материализовать уже известную контрольную точку + +```mermaid +flowchart TD + A["RPC call_function: get_lines"] --> B["Текущая доска 50x50"] + C["Transactions API: /v0/blocks"] --> D["Ограничить эпоху"] + D --> E["/v0/account для berryclub.ek.near"] + E --> F["Кандидатные хеши draw-транзакций"] + F --> G["Раскрытие через /v0/transactions"] + G --> H["Проигрывание draw-записей в историческую доску"] +``` + +## Почему Berry Club хорошо учит истории в NEAR + +Berry Club удобно показывает обе стороны задачи: + +- чистое чтение текущего состояния через `get_lines` +- длинную историю вызовов `draw` с обычными аргументами `FunctionCall` +- формат доски, который легко декодировать и рендерить обычным JavaScript + +Это очень NEAR-подобная форма: один view-метод для текущего состояния, один write-метод для изменений и индексированная история, когда нужно объяснить, как это состояние вообще появилось. + +## 1. Сначала прочитайте текущую доску + +Живое демо использует `berryclub.ek.near` и читает доску через view-вызов `get_lines`: + +```javascript +await near.view({ + contractId: 'berryclub.ek.near', + methodName: 'get_lines', + args: { + lines: [...Array(50).keys()], + }, +}); +``` + +Это путь текущего состояния. Он не отвечает на вопрос, как доска пришла к такому виду. + +| Вопрос | Лучшая поверхность | Почему | +| --- | --- | --- | +| как доска выглядит сейчас? | [RPC `call_function`](https://docs.fastnear.com/ru/rpc/contract/call-function) | контракт уже отдаёт текущее состояние через `get_lines` | +| какие `draw` были в этой эпохе? | [`/v0/account`](https://docs.fastnear.com/ru/tx/account) + [`/v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | индексированная история даёт ограниченный набор записей и раскрытые аргументы | +| как доска выглядела в известной контрольной точке? | архивный RPC или полное проигрывание | можно напрямую материализовать состояние из архива или восстановить его самому по историческим записям | + +## 2. Как декодировать `get_lines` в сетку 50x50 + +Полезная часть Berry Club-разметки из `js.fastnear.com` — это декодер строк: + +- каждая строка приходит в base64 +- её нужно декодировать в байты +- первые 4 байта нужно пропустить +- дальше цвета читаются как 32-битные little-endian значения каждые 8 байт + +```javascript +function decodeLine(encodedLine) { + const bytes = Buffer.from(encodedLine, 'base64'); + const colors = []; + + for (let offset = 4; offset < bytes.length; offset += 8) { + colors.push(bytes.readUInt32LE(offset) & 0xffffff); + } + + return colors; +} +``` + +Примените это ко всем 50 строкам — и получите полную сетку 50x50, готовую к рендерингу. + +## 3. Ограничьте эпоху, которую хотите изучить + +Сначала ограничьте эпоху, прежде чем искать draw-записи. Проверочный снимок запуска в этом репозитории находится на блоке `21898354`, а средний снимок — на блоке `97601515`. + +Сначала зафиксируйте ближайший диапазон блоков: + +```bash +curl -sS https://tx.main.fastnear.com/v0/blocks \ + -H 'content-type: application/json' \ + --data '{ + "from_block_height": 21898350, + "to_block_height": 21898355, + "desc": false, + "limit": 5 + }' +``` + +Затем переключитесь на историю аккаунта и запросите активность Berry Club внутри ограниченного диапазона блоков: + +```bash +curl -sS https://tx.main.fastnear.com/v0/account \ + -H 'content-type: application/json' \ + --data '{ + "account_id": "berryclub.ek.near", + "is_function_call": true, + "is_receiver": true, + "is_real_receiver": true, + "from_tx_block_height": 97576515, + "to_tx_block_height": 97601516, + "desc": true, + "limit": 40 + }' +``` + +Здесь полезна именно такая последовательность: + +- `/v0/blocks` помогает понять соседство по высотам блоков +- `/v0/account` возвращает кандидатные хеши транзакций Berry Club внутри этого диапазона + +## 4. Раскройте транзакции и оставьте только `draw` + +Когда кандидатные хеши уже есть, раскройте их и оставьте только верхнеуровневые вызовы `draw`, где получатель — `berryclub.ek.near`. + +Аргументы вызова — это обычные данные `FunctionCall` вида `{ pixels: [...] }`: + +```bash +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{ + "tx_hashes": [ + "Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ", + "8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL" + ] + }' | jq '.transactions[] + | select(.transaction.receiver_id == "berryclub.ek.near") + | .transaction.actions[]?.FunctionCall + | select(.method_name == "draw") + | { + method_name, + args: (.args | @base64d | fromjson) + }' +``` + +Это даёт всё, что нужно для проигрывания: + +- какая транзакция записывала пиксели +- какие координаты были затронуты +- какие цвета были записаны + +## 5. Проиграйте исторические `draw`-вызовы в доску + +Для полного проигрывания держите в памяти массив 50x50 и применяйте раскрытые транзакции `draw` от старых к новым. + +```javascript +const board = Array.from({ length: 50 }, () => Array(50).fill(0)); + +function applyDraw(boardState, drawArgs) { + for (const pixel of drawArgs.pixels) { + if (pixel.x < 0 || pixel.x >= 50 || pixel.y < 0 || pixel.y >= 50) { + continue; + } + + boardState[pixel.y][pixel.x] = pixel.color; + } +} + +for (const drawTx of drawTransactionsOldestFirst) { + applyDraw(board, drawTx.args); +} +``` + +Важно не путать два разных пути: + +- `get_lines` — это текущее состояние +- `tx/account` плюс `tx/transactions` — это материал для проигрывания + +## 6. Готовые контрольные точки по эпохам + +Галерея ниже использует уже сохранённые данные снимков, собранные из mainnet-истории Berry Club: + +- `launch` — последний успешный `draw` в пределах первых 24 часов после первого успешного draw +- `mid` — последний успешный `draw` не позже средней временной точки всей истории Berry Club +- `recent` — последний успешный `draw`, который увидел скрипт при пересборке снимков + +Галерея снимков: контрольные точки launch, mid и recent из сохранённого `src/data/berryClubSnapshots.json`. + +Сейчас эти снимки привязаны к таким транзакциям: + +- `launch`: `BDNFpCpLXjBrgjR6z6wCZmB9EWdHnVMdqau3iTWTRE5H` на блоке `21898354` +- `mid`: `Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ` на блоке `97601515` +- `recent`: `8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL` на блоке `194588754` + +## Куда идти за подписанными взаимодействиями + +Эта страница должна оставаться в режиме чтения. + +Если нужны живые подписанные сценарии для `draw` и `buy_tokens`, переходите сюда: + +- [js.fastnear.com](https://js.fastnear.com/) +- [Berry Club example в fastnear/js-monorepo](https://github.com/fastnear/js-monorepo/tree/main/examples/static/berryclub) + +Именно там уместны кошелёк и подписанные действия. Эта страница посвящена историческому восстановлению. diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index a42bb2d..0c24ba4 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -1,8 +1,10 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) -## Готовые расследования +Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. -Эти расследования намеренно выстроены от самого простого якоря к самой насыщенной форензике: сначала один tx hash, затем один receipt, затем паттерны с ошибками и async, и только потом более глубокие расследования по SocialDB и NEAR Intents. +## С чего начать + +Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. ### У меня есть один хеш транзакции. Что вообще произошло? @@ -10,6 +12,13 @@ Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. + Стратегия + Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно. + + 01POST /v0/transactions даёт signer, receiver, типы действий, высоту блока и первую receipt-точку передачи. + 02RPC EXPERIMENTAL_tx_status нужен только для точной протокольной семантики успеха. + 03POST /v0/receipt имеет смысл только тогда, когда именно первая receipt становится новой опорной точкой. + **Цель** - Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. @@ -46,7 +55,7 @@ flowchart LR - в какой блок попала - одно простое предложение, которое объясняет транзакцию без receipt-жаргона -### Shell-сценарий: от хеша транзакции к человеческой истории +#### Shell-сценарий: от хеша транзакции к человеческой истории Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. @@ -144,6 +153,13 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. + Стратегия + Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой. + + 01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt. + 02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий. + 03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола». + **Цель** - Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. @@ -181,7 +197,11 @@ flowchart LR - была ли квитанция главным событием или только шагом в большом каскаде - одно предложение простым языком, которое можно без правок вставить коллеге в чат -### Shell-сценарий: от страшного receipt ID к человеческой истории +#### Shell-сценарий: от страшного receipt ID к человеческой истории + +## Ошибки и async + +Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и порядок callback-ов. Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. @@ -271,6 +291,13 @@ jq -r ' В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. + Стратегия + Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов. + + 01POST /v0/transactions показывает упорядоченный пакет ровно в том виде, в каком его подписал signer. + 02RPC EXPERIMENTAL_tx_status показывает падающий FunctionCall и точную причину отказа на уровне протокола. + 03RPC view_account по предполагаемому новому аккаунту доказывает, закрепились ли вообще ранние create, fund и add-key действия. + **Цель** - На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. @@ -320,7 +347,7 @@ flowchart LR - доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality - короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` -### Shell-сценарий неудачной транзакции с пакетом действий +#### Shell-сценарий неудачной транзакции с пакетом действий Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. @@ -442,6 +469,13 @@ jq '{ Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. + Стратегия + Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась. + + 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. + 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. + 03RPC call_function на роутере показывает, закрепилось ли собственное локальное изменение состояния из первой receipt. + **Цель** - Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. @@ -489,7 +523,7 @@ flowchart LR - что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился - одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции -### Shell-сценарий более позднего сбоя receipt +#### Shell-сценарий более позднего сбоя receipt Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». @@ -628,6 +662,13 @@ jq '{ Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» + Стратегия + Смотрите на два хеша как на одну async-историю: докажите, что работа была жива, восстановите запрошенный порядок и сравните его с видимым downstream-state. + + 01RPC call_function на view отложенной работы доказывает, что promise-работа действительно уже была жива до resume-шага. + 02POST /v0/transactions даёт оба block-anchor и точный порядок, который запросила resume-транзакция. + 03RPC EXPERIMENTAL_tx_status вместе с downstream-view доказывают, где callback-и реально выполнились и в каком видимом порядке. + **Цель** - Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. @@ -675,10 +716,21 @@ flowchart LR - в каких блоках стали видны изменения состояния - какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования +## Доказательства по SocialDB + +Эти примеры стартуют с читаемого состояния в NEAR Social и откатываются назад к точной записи, которая это состояние создала. + ### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». + Стратегия + Начните с читаемого значения поля, а затем превратите его field-level block в один receipt и одну транзакцию записи. + + 01NEAR Social POST /get даёт текущее значение profile.name и field-level :block. + 02POST /v0/block превращает этот блок в конкретный receipt и хеш транзакции mike.near -> social.near. + 03POST /v0/transactions доказывает payload записи, а RPC call_function get подтверждает, что поле и сейчас разрешается так же. + **Цель** - Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. @@ -707,7 +759,7 @@ flowchart LR - доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload - различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) -### Shell-сценарий доказательства поля профиля в NEAR Social +#### Shell-сценарий доказательства поля профиля в NEAR Social Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. @@ -871,6 +923,13 @@ NEAR Social даёт семантическое значение поля. FastN Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». + Стратегия + Начните с семантической связи подписки, а затем используйте блок записи как мост назад к одному receipt и одной транзакции. + + 01NEAR Social POST /get даёт читаемую связь подписки и SocialDB :block, где она была записана. + 02POST /v0/block превращает этот блок записи в конкретный receipt и хеш транзакции за этой связью. + 03POST /v0/transactions доказывает payload с graph.follow и index.graph, а RPC call_function get подтверждает, что связь и сейчас существует. + **Цель** - Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. @@ -899,7 +958,7 @@ NEAR Social даёт семантическое значение поля. FastN - доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` - различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) -### Shell-сценарий доказательства подписки в NEAR Social +#### Shell-сценарий доказательства подписки в NEAR Social Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. @@ -1068,6 +1127,13 @@ NEAR Social даёт семантическую связь. FastNear block recei - восстанавливаем исходную транзакцию - декодируем payload `set` и доказываем, что он действительно нёс исходник виджета + Стратегия + Смотрите на write-block виджета как на весь мост сразу: блок в receipt, receipt в транзакцию, транзакцию в исходник. + + 01POST /v0/block начинает с блока виджета и сужает его до одного receipt mob.near -> social.near. + 02POST /v0/transactions превращает этот receipt в один читаемый payload set с исходником виджета. + 03RPC call_function get — это финальное подтверждение текущего состояния, что виджет и сейчас существует. + **Цель** - Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. @@ -1098,7 +1164,11 @@ NEAR Social даёт семантическую связь. FastNear block recei - доказательство, что payload записи был `set` с `mob.near/widget/Profile` - одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» -### Shell-сценарий доказательства записи виджета в NEAR Social +#### Shell-сценарий доказательства записи виджета в NEAR Social + +## Трассировка расчёта + +Это самое насыщенное расследование на странице: один живой расчёт NEAR Intents от верхнеуровневой транзакции до receipts и событий, которые его объясняют. Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. @@ -1236,6 +1306,13 @@ jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». + Стратегия + Смотрите на один расчёт как на читаемую трассу, а не как на теорию протокола с первой строки. + + 01POST /v0/transactions даёт каркас расчёта: входную точку, первые downstream-контракты и ранние логи. + 02POST /v0/block переиспользует тот же якорь, когда нужен контекст включающего блока вокруг этого расчёта. + 03RPC EXPERIMENTAL_tx_status нужен там, где уже требуется канонический DAG по receipt и имена событий, которые доказывают реальное движение активов. + **Цель** - Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. @@ -1294,7 +1371,7 @@ flowchart LR Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. -### Shell-сценарий расчёта NEAR Intents +#### Shell-сценарий расчёта NEAR Intents Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. diff --git a/static/ru/tx/examples/outlayer.md b/static/ru/tx/examples/outlayer.md new file mode 100644 index 0000000..804f0a3 --- /dev/null +++ b/static/ru/tx/examples/outlayer.md @@ -0,0 +1,250 @@ +**Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) + +{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как использовать FastNear RPC и Transactions API, чтобы разбирать живое исполнение OutLayer в терминах NEAR. Он отделяет видимый request/worker/callback-поток, который уже можно трассировать через FastNear, от задокументированного внутреннего пути yield/resume и CKD/MPC. */} + +# OutLayer: как проследить один запрос от вызова до callback + +Используйте этот разбор, когда вопрос звучит так: «я вижу OutLayer в цепочке. Какая транзакция открыла работу, какая более поздняя транзакция пришла от воркера и где проявились callback, списание и возврат средств?» + +Это продвинутый разбор асинхронного исполнения в семействе Transactions examples. Держите NEAR-рамку первой: один `FunctionCall` со стороны вызывающего, одна более поздняя транзакция со стороны воркера, и квитанции только тогда, когда действительно нужно разбирать фазу завершения. + + Стратегия + Сначала найдите caller-транзакцию и worker-транзакцию, а receipts подключайте только тогда, когда настоящим вопросом становится finish-путь. + + 01POST /v0/account — самый быстрый способ найти caller-side и worker-side хеши из одной и той же истории. + 02POST /v0/transactions раскрывает оба хеша и показывает читаемые request, worker-resolution и ранние логи. + 03Только после этого имеет смысл разбирать callback, списание и refund на уровне receipts или уходить в точные RPC-проверки идентичности. + +Полезные ссылки: + +- [История аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +- [Просмотр аккаунта](https://docs.fastnear.com/ru/rpc/account/view-account) +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) + +## Короткая версия + +Если вы видите активность OutLayer в цепочке, практические вопросы обычно такие: + +- какая транзакция создала асинхронную единицу работы? +- какая более поздняя транзакция пришла от воркера? +- где именно проявились callback, списание и возврат средств? + +Это не вопрос о текущем состоянии. Это вопрос об истории исполнения. + +Полезный ход через FastNear — связать одну транзакцию `request_execution` со стороны вызывающего с одной транзакцией разрешения со стороны воркера, а к receipt переходить только на этапе завершения. + +```mermaid +sequenceDiagram + autonumber + participant Caller as "Вызывающая сторона" + participant Outlayer as "outlayer.*" + participant Worker as "worker.outlayer.*" + participant Near as "исполняющая среда NEAR" + + Caller->>Outlayer: FunctionCall request_execution + Outlayer-->>Near: создаёт асинхронную работу и контекст учёта + Worker->>Outlayer: resolve_execution или submit_execution_output_and_resolve + Outlayer-->>Near: логи завершения, путь callback, списание, возвраты +``` + +Всё это уже видно сегодня через FastNear и RPC. + +## 1. Раскройте одну транзакцию запроса и одно разрешение воркера + +Если хотите сразу увидеть всю форму потока, начните с уже известной пары хешей и раскройте оба. + +Эта пара работала 18 апреля 2026 года: + +- `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` — `request_execution` со стороны вызывающего +- `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` — `submit_execution_output_and_resolve` со стороны воркера + +```bash title="Раскройте хеш запроса и хеш разрешения воркера" +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{ + "tx_hashes":[ + "AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4", + "AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs" + ] + }' | jq '.transactions[] | { + hash: .transaction.hash, + signer: .transaction.signer_id, + receiver: .transaction.receiver_id, + actions: [.transaction.actions[] | keys[0]], + logs: (.receipts[0].execution_outcome.outcome.logs[:2]) + }' +``` + +В этом выборочном выводе: + +- хеш запроса шёл от `solarflux.near` к `outlayer.near` +- в логах фигурировал разрешённый проект: `zavodil.near/near-email` +- хеш воркера шёл от `worker.outlayer.near` к `outlayer.near` +- в логах воркера было `Stored pending output` и `Resolving execution ... (combined flow)` + +Этого уже достаточно для видимой истории в терминах NEAR: исходный `FunctionCall` создал асинхронную единицу работы, позже воркер вернулся как отдельный подписант, а контракт разрешил результат в цепочке. + +Если копировать с этой страницы только одну команду, то именно эту. + +## 2. Найдите два нужных хеша сами + +Если пары хешей у вас ещё нет, переключитесь на [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account). + +```bash title="Недавняя mainnet-активность для outlayer.near" +curl -sS https://tx.main.fastnear.com/v0/account \ + -H 'content-type: application/json' \ + --data '{"account_id":"outlayer.near","desc":true}' \ + | jq '{txs_count, first: .account_txs[0]}' +``` + +18 апреля 2026 года эта поверхность показывала более 5 000 трассированных транзакций для `outlayer.near`, а самый свежий выборочный хеш был таким: + +```text +AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs +``` + +Этот хеш не был исходным пользовательским запросом. Это уже было последующее действие со стороны воркера. + +Именно поэтому история аккаунта — правильный первый поиск: здесь задача не в том, чтобы описать контракт целиком, а в том, чтобы найти две конкретные транзакции в одной истории исполнения. + +```mermaid +flowchart TD + A["Transactions API: /v0/account для outlayer.*"] --> B["Найдите недавние хеши транзакций"] + B --> C["Хеш request_execution со стороны вызывающего"] + B --> D["Хеш разрешения со стороны воркера"] + C --> E["Раскройте оба через /v0/transactions"] + D --> E + E --> F["Проверьте логи, request_id, project/source, callback-receipt, списание и возвраты"] +``` + +## 3. Разберите фазу callback и возврата средств + +Если нужно пройти дальше, чем просто «воркер вернул результат», посмотрите список receipt у раскрытой воркерской транзакции. + +```bash title="Показать последующие действия на уровне receipt для разрешения воркером" +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{"tx_hashes":["AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs"]}' \ + | jq '.transactions[0] | { + hash: .transaction.hash, + receipts: [ + .receipts[] | { + predecessor: .receipt.predecessor_id, + receiver: .receiver_id, + actions: [.receipt.receipt.Action.actions[] | keys[0]], + logs: .execution_outcome.outcome.logs + } + ] + }' +``` + +На что смотреть: + +- `FunctionCall:on_execution_response` +- логи списания вроде `[[yNEAR charged: "..."]]` +- события завершения вроде `execution_completed` +- последующие receipt `Transfer` + +Здесь понятие receipt как раз становится правильной абстракцией: не в начале урока, а тогда, когда уже отлаживается реальный путь завершения. + +## 4. Подтвердите контракт, если нужна точная проверка + +Если нужна точная проверка аккаунта и `code_hash`, используйте сырой RPC. Это шаг для проверки идентичности, а не для восстановления истории исполнения. + +```bash title="Mainnet: view_account для outlayer.near" +curl -sS https://rpc.mainnet.fastnear.com \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "id":"1", + "method":"query", + "params":{ + "request_type":"view_account", + "finality":"final", + "account_id":"outlayer.near" + } + }' | jq '.result | {amount, locked, code_hash, storage_usage}' +``` + +```bash title="Testnet: view_account для outlayer.testnet" +curl -sS https://rpc.testnet.fastnear.com \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "id":"1", + "method":"query", + "params":{ + "request_type":"view_account", + "finality":"final", + "account_id":"outlayer.testnet" + } + }' | jq '.result | {amount, locked, code_hash, storage_usage}' +``` + +По состоянию на 18 апреля 2026 года оба контракта возвращали один и тот же `code_hash`: + +```text +94uKcoDB3QbEpxDj1xsw9CQwu9bAY1PoVPr2BZYRRv4K +``` + +Это сильный сигнал, что на обеих сетях развёрнут один и тот же бинарник контракта. + +## 5. Что происходит внутри? + +Видимая история выше — это то, что NEAR-разработчику нужно сначала. Более глубокий слой объясняет, почему этот поток вообще интересен. + +### Что можно наблюдать уже сейчас + +Через FastNear и RPC уже видно: + +- вызывающую транзакцию `request_execution` +- воркерскую `resolve_execution` или `submit_execution_output_and_resolve` +- завершающие receipt, где материализуются callback, списание и возврат средств + +Интеграция вашего контракта при этом остаётся обычной асинхронной композицией в NEAR: вы вызываете `outlayer.*`, а потом обрабатываете свой callback. + +### Что задокументировано как внутренний механизм + +Документация OutLayer описывает более глубокий внутренний слой: `outlayer.*` использует семантику NEAR `yield/resume` как свою внутреннюю асинхронную границу, внешняя работа выполняется в TEE-воркерах, а защищённые секреты проходят через отдельный путь доверия, где TEE-keystore получает DAO-gated CKD через MPC signer. + +Для NEAR-разработчика здесь важна точность: мы не говорим, что ваш вызывающий контракт сам пишет `promise_yield_create`. Примитивы `yield/resume` в NEAR работают только в рамках одного и того же аккаунта, поэтому если этот механизм используется здесь, то yielding и resuming делает `outlayer.*`, а не исходный вызывающий контракт. Для сырой модели выполнения смотрите [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features). + +Документация Secrets / CKD описывает этот путь keystore как двухуровневый: сначала keystore получает derivation key через DAO-gated путь к MPC, а затем использует уже полученную derivation capability для защищённых секретов во время исполнений приложения. Это объяснение доверительной модели, а не утверждение, что каждое обычное исполнение OutLayer делает новый DAO -> MPC round trip. + +Публичный gateway-аккаунт для этого пути keystore / DAO в наших текущих публичных chain-данных всё ещё не подтверждён, поэтому эту часть надо держать в корзине «задокументировано внутри», а не в корзине «уже наблюдается сейчас». + +```mermaid +flowchart TB + subgraph Observable["Что уже можно наблюдать через FastNear / RPC"] + Caller["Пользователь или вызывающий контракт"] -->|FunctionCall request_execution| Entry["outlayer.*"] + Entry -->|поздняя активность воркера| Worker["worker.outlayer.*"] + Worker -->|resolve_execution или submit_execution_output_and_resolve| Entry + Entry -->|on_execution_response, списание, refund-переводы| Finish["Callback / finish receipt"] + end + + subgraph Internal["Что задокументировано как внутренний слой"] + Yield["внутренняя yield-точка в outlayer.*"] --> TEE["TEE-воркер исполняет WASM"] + TEE -->|resume с результатом| Yield + TEE --> Keystore["TEE-keystore для защищённых секретов"] + Keystore -->|задокументированный DAO-gated request_key| DAO["DAO gateway (задокументирован)"] + DAO -->|задокументированный CKD request| MPC["v1.signer / v1.signer-prod.testnet"] + MPC -->|derivation key для keystore| Keystore + end + + Entry -. та же логическая история исполнения .-> Yield + + classDef observable fill:#e7f1ff,stroke:#2563eb,color:#0f172a; + classDef internal fill:#fff4e5,stroke:#b45309,color:#0f172a; + class Caller,Entry,Worker,Finish observable; + class Yield,TEE,Keystore,DAO,MPC internal; +``` + +## Куда читать дальше + +- [Transactions API](https://docs.fastnear.com/ru/tx) для истории аккаунта, receipt и раскрытия транзакций +- [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features) для семантики `yield/resume` в NEAR +- [Асинхронная модель](https://docs.fastnear.com/ru/transaction-flow/async-model) для лексики promise и callback +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) для задокументированного контрактного интерфейса +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) для задокументированного пути keystore, DAO и MPC diff --git a/static/ru/tx/examples/outlayer/index.md b/static/ru/tx/examples/outlayer/index.md new file mode 100644 index 0000000..804f0a3 --- /dev/null +++ b/static/ru/tx/examples/outlayer/index.md @@ -0,0 +1,250 @@ +**Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) + +{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как использовать FastNear RPC и Transactions API, чтобы разбирать живое исполнение OutLayer в терминах NEAR. Он отделяет видимый request/worker/callback-поток, который уже можно трассировать через FastNear, от задокументированного внутреннего пути yield/resume и CKD/MPC. */} + +# OutLayer: как проследить один запрос от вызова до callback + +Используйте этот разбор, когда вопрос звучит так: «я вижу OutLayer в цепочке. Какая транзакция открыла работу, какая более поздняя транзакция пришла от воркера и где проявились callback, списание и возврат средств?» + +Это продвинутый разбор асинхронного исполнения в семействе Transactions examples. Держите NEAR-рамку первой: один `FunctionCall` со стороны вызывающего, одна более поздняя транзакция со стороны воркера, и квитанции только тогда, когда действительно нужно разбирать фазу завершения. + + Стратегия + Сначала найдите caller-транзакцию и worker-транзакцию, а receipts подключайте только тогда, когда настоящим вопросом становится finish-путь. + + 01POST /v0/account — самый быстрый способ найти caller-side и worker-side хеши из одной и той же истории. + 02POST /v0/transactions раскрывает оба хеша и показывает читаемые request, worker-resolution и ранние логи. + 03Только после этого имеет смысл разбирать callback, списание и refund на уровне receipts или уходить в точные RPC-проверки идентичности. + +Полезные ссылки: + +- [История аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +- [Просмотр аккаунта](https://docs.fastnear.com/ru/rpc/account/view-account) +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) + +## Короткая версия + +Если вы видите активность OutLayer в цепочке, практические вопросы обычно такие: + +- какая транзакция создала асинхронную единицу работы? +- какая более поздняя транзакция пришла от воркера? +- где именно проявились callback, списание и возврат средств? + +Это не вопрос о текущем состоянии. Это вопрос об истории исполнения. + +Полезный ход через FastNear — связать одну транзакцию `request_execution` со стороны вызывающего с одной транзакцией разрешения со стороны воркера, а к receipt переходить только на этапе завершения. + +```mermaid +sequenceDiagram + autonumber + participant Caller as "Вызывающая сторона" + participant Outlayer as "outlayer.*" + participant Worker as "worker.outlayer.*" + participant Near as "исполняющая среда NEAR" + + Caller->>Outlayer: FunctionCall request_execution + Outlayer-->>Near: создаёт асинхронную работу и контекст учёта + Worker->>Outlayer: resolve_execution или submit_execution_output_and_resolve + Outlayer-->>Near: логи завершения, путь callback, списание, возвраты +``` + +Всё это уже видно сегодня через FastNear и RPC. + +## 1. Раскройте одну транзакцию запроса и одно разрешение воркера + +Если хотите сразу увидеть всю форму потока, начните с уже известной пары хешей и раскройте оба. + +Эта пара работала 18 апреля 2026 года: + +- `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` — `request_execution` со стороны вызывающего +- `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` — `submit_execution_output_and_resolve` со стороны воркера + +```bash title="Раскройте хеш запроса и хеш разрешения воркера" +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{ + "tx_hashes":[ + "AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4", + "AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs" + ] + }' | jq '.transactions[] | { + hash: .transaction.hash, + signer: .transaction.signer_id, + receiver: .transaction.receiver_id, + actions: [.transaction.actions[] | keys[0]], + logs: (.receipts[0].execution_outcome.outcome.logs[:2]) + }' +``` + +В этом выборочном выводе: + +- хеш запроса шёл от `solarflux.near` к `outlayer.near` +- в логах фигурировал разрешённый проект: `zavodil.near/near-email` +- хеш воркера шёл от `worker.outlayer.near` к `outlayer.near` +- в логах воркера было `Stored pending output` и `Resolving execution ... (combined flow)` + +Этого уже достаточно для видимой истории в терминах NEAR: исходный `FunctionCall` создал асинхронную единицу работы, позже воркер вернулся как отдельный подписант, а контракт разрешил результат в цепочке. + +Если копировать с этой страницы только одну команду, то именно эту. + +## 2. Найдите два нужных хеша сами + +Если пары хешей у вас ещё нет, переключитесь на [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account). + +```bash title="Недавняя mainnet-активность для outlayer.near" +curl -sS https://tx.main.fastnear.com/v0/account \ + -H 'content-type: application/json' \ + --data '{"account_id":"outlayer.near","desc":true}' \ + | jq '{txs_count, first: .account_txs[0]}' +``` + +18 апреля 2026 года эта поверхность показывала более 5 000 трассированных транзакций для `outlayer.near`, а самый свежий выборочный хеш был таким: + +```text +AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs +``` + +Этот хеш не был исходным пользовательским запросом. Это уже было последующее действие со стороны воркера. + +Именно поэтому история аккаунта — правильный первый поиск: здесь задача не в том, чтобы описать контракт целиком, а в том, чтобы найти две конкретные транзакции в одной истории исполнения. + +```mermaid +flowchart TD + A["Transactions API: /v0/account для outlayer.*"] --> B["Найдите недавние хеши транзакций"] + B --> C["Хеш request_execution со стороны вызывающего"] + B --> D["Хеш разрешения со стороны воркера"] + C --> E["Раскройте оба через /v0/transactions"] + D --> E + E --> F["Проверьте логи, request_id, project/source, callback-receipt, списание и возвраты"] +``` + +## 3. Разберите фазу callback и возврата средств + +Если нужно пройти дальше, чем просто «воркер вернул результат», посмотрите список receipt у раскрытой воркерской транзакции. + +```bash title="Показать последующие действия на уровне receipt для разрешения воркером" +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{"tx_hashes":["AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs"]}' \ + | jq '.transactions[0] | { + hash: .transaction.hash, + receipts: [ + .receipts[] | { + predecessor: .receipt.predecessor_id, + receiver: .receiver_id, + actions: [.receipt.receipt.Action.actions[] | keys[0]], + logs: .execution_outcome.outcome.logs + } + ] + }' +``` + +На что смотреть: + +- `FunctionCall:on_execution_response` +- логи списания вроде `[[yNEAR charged: "..."]]` +- события завершения вроде `execution_completed` +- последующие receipt `Transfer` + +Здесь понятие receipt как раз становится правильной абстракцией: не в начале урока, а тогда, когда уже отлаживается реальный путь завершения. + +## 4. Подтвердите контракт, если нужна точная проверка + +Если нужна точная проверка аккаунта и `code_hash`, используйте сырой RPC. Это шаг для проверки идентичности, а не для восстановления истории исполнения. + +```bash title="Mainnet: view_account для outlayer.near" +curl -sS https://rpc.mainnet.fastnear.com \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "id":"1", + "method":"query", + "params":{ + "request_type":"view_account", + "finality":"final", + "account_id":"outlayer.near" + } + }' | jq '.result | {amount, locked, code_hash, storage_usage}' +``` + +```bash title="Testnet: view_account для outlayer.testnet" +curl -sS https://rpc.testnet.fastnear.com \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "id":"1", + "method":"query", + "params":{ + "request_type":"view_account", + "finality":"final", + "account_id":"outlayer.testnet" + } + }' | jq '.result | {amount, locked, code_hash, storage_usage}' +``` + +По состоянию на 18 апреля 2026 года оба контракта возвращали один и тот же `code_hash`: + +```text +94uKcoDB3QbEpxDj1xsw9CQwu9bAY1PoVPr2BZYRRv4K +``` + +Это сильный сигнал, что на обеих сетях развёрнут один и тот же бинарник контракта. + +## 5. Что происходит внутри? + +Видимая история выше — это то, что NEAR-разработчику нужно сначала. Более глубокий слой объясняет, почему этот поток вообще интересен. + +### Что можно наблюдать уже сейчас + +Через FastNear и RPC уже видно: + +- вызывающую транзакцию `request_execution` +- воркерскую `resolve_execution` или `submit_execution_output_and_resolve` +- завершающие receipt, где материализуются callback, списание и возврат средств + +Интеграция вашего контракта при этом остаётся обычной асинхронной композицией в NEAR: вы вызываете `outlayer.*`, а потом обрабатываете свой callback. + +### Что задокументировано как внутренний механизм + +Документация OutLayer описывает более глубокий внутренний слой: `outlayer.*` использует семантику NEAR `yield/resume` как свою внутреннюю асинхронную границу, внешняя работа выполняется в TEE-воркерах, а защищённые секреты проходят через отдельный путь доверия, где TEE-keystore получает DAO-gated CKD через MPC signer. + +Для NEAR-разработчика здесь важна точность: мы не говорим, что ваш вызывающий контракт сам пишет `promise_yield_create`. Примитивы `yield/resume` в NEAR работают только в рамках одного и того же аккаунта, поэтому если этот механизм используется здесь, то yielding и resuming делает `outlayer.*`, а не исходный вызывающий контракт. Для сырой модели выполнения смотрите [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features). + +Документация Secrets / CKD описывает этот путь keystore как двухуровневый: сначала keystore получает derivation key через DAO-gated путь к MPC, а затем использует уже полученную derivation capability для защищённых секретов во время исполнений приложения. Это объяснение доверительной модели, а не утверждение, что каждое обычное исполнение OutLayer делает новый DAO -> MPC round trip. + +Публичный gateway-аккаунт для этого пути keystore / DAO в наших текущих публичных chain-данных всё ещё не подтверждён, поэтому эту часть надо держать в корзине «задокументировано внутри», а не в корзине «уже наблюдается сейчас». + +```mermaid +flowchart TB + subgraph Observable["Что уже можно наблюдать через FastNear / RPC"] + Caller["Пользователь или вызывающий контракт"] -->|FunctionCall request_execution| Entry["outlayer.*"] + Entry -->|поздняя активность воркера| Worker["worker.outlayer.*"] + Worker -->|resolve_execution или submit_execution_output_and_resolve| Entry + Entry -->|on_execution_response, списание, refund-переводы| Finish["Callback / finish receipt"] + end + + subgraph Internal["Что задокументировано как внутренний слой"] + Yield["внутренняя yield-точка в outlayer.*"] --> TEE["TEE-воркер исполняет WASM"] + TEE -->|resume с результатом| Yield + TEE --> Keystore["TEE-keystore для защищённых секретов"] + Keystore -->|задокументированный DAO-gated request_key| DAO["DAO gateway (задокументирован)"] + DAO -->|задокументированный CKD request| MPC["v1.signer / v1.signer-prod.testnet"] + MPC -->|derivation key для keystore| Keystore + end + + Entry -. та же логическая история исполнения .-> Yield + + classDef observable fill:#e7f1ff,stroke:#2563eb,color:#0f172a; + classDef internal fill:#fff4e5,stroke:#b45309,color:#0f172a; + class Caller,Entry,Worker,Finish observable; + class Yield,TEE,Keystore,DAO,MPC internal; +``` + +## Куда читать дальше + +- [Transactions API](https://docs.fastnear.com/ru/tx) для истории аккаунта, receipt и раскрытия транзакций +- [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features) для семантики `yield/resume` в NEAR +- [Асинхронная модель](https://docs.fastnear.com/ru/transaction-flow/async-model) для лексики promise и callback +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) для задокументированного контрактного интерфейса +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) для задокументированного пути keystore, DAO и MPC From 0e4155e7e419d634538f947a891bf849bfd1bef0 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 19:46:57 -0700 Subject: [PATCH 08/35] docs: add tx submission tracking example --- docs/rpc/examples.md | 220 ++++++++++++++++++ .../current/rpc/examples.md | 220 ++++++++++++++++++ static/ru/llms-full.txt | 215 +++++++++++++++++ static/ru/rpc/examples.md | 215 +++++++++++++++++ static/ru/rpc/examples/index.md | 215 +++++++++++++++++ 5 files changed, 1085 insertions(+) diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index c145f3d..738b8b5 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -12,6 +12,225 @@ page_actions: Use this page when you already know the answer lives in RPC and you want the shortest path to it. The goal is not to memorize every method. It is to start with the right RPC read or write, stop as soon as the response answers the question, and only switch to a higher-level API when that would save time. +## Transaction Submission and Tracking + +Start here when the real question is not just “how do I send this?” but “which RPC endpoint should I use, and how do I track the transaction all the way to done?” + +### Submit a transaction, then track it from hash to final execution + +Use this when the user story is simple: “I have a signed transaction. Which endpoint do I call first, and what should I poll after I get the hash?” Not every tx question wants the same RPC method. The practical pattern is to submit fast, then track deliberately. + +This walkthrough is intentionally pinned and historical. It uses one real mainnet transaction that wrote a NEAR Social follow edge: + +- transaction hash: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- signer: `mike.near` +- receiver: `social.near` +- included block height: `79574923` +- receipt execution block for the SocialDB write: `79574924` + +Because this transaction is already old and finalized, you cannot literally replay its true pending window. That is fine. The point here is to teach the right submission and tracking pattern, then inspect one pinned transaction with the same tools. + +
+
+ Strategy +

Submit fast, poll the simpler status path first, and only drop into the receipt tree when the headline status stops being enough.

+
+
+

01RPC broadcast_tx_async is the low-latency submission surface when your client will track separately.

+

02RPC tx is the default polling surface for included, optimistic, and final guarantees.

+

03RPC EXPERIMENTAL_tx_status is the deeper follow-up when you need the receipt tree, not the default loop.

+
+
+ +**What you're deciding** + +- which submission endpoint to reach for first +- what to poll after you have a tx hash +- how `wait_until` thresholds relate to included, optimistic, and final guarantees +- when to stop using `tx` and switch to `EXPERIMENTAL_tx_status` + +```mermaid +flowchart LR + S["Sign transaction"] --> A["broadcast_tx_async
returns tx hash"] + A --> T["tx polling
INCLUDED_FINAL -> FINAL"] + T --> F["Transaction fully done"] + T -. "only when needed" .-> E["EXPERIMENTAL_tx_status
receipt tree + outcomes"] + F -. "optional readable story" .-> X["POST /v0/transactions"] +``` + +| Method | Use it when | What comes back | Position here | +| --- | --- | --- | --- | +| [`broadcast_tx_async`](/rpc/transaction/broadcast-tx-async) | your client wants to own tracking after submission | just the tx hash | **default submit path** | +| [`send_tx`](/rpc/transaction/send-tx) | you want the node to wait to a chosen threshold for you | tx result up to `wait_until` | blocking alternative | +| [`broadcast_tx_commit`](/rpc/transaction/broadcast-tx-commit) | older code or quick one-call confirmation is the point | execution result with commit-style waiting | legacy convenience | +| [`tx`](/rpc/transaction/tx-status) | you already have the tx hash and want to know how far it got | status plus outcomes at the threshold you asked for | **default tracking path** | +| [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | you need receipt-tree detail or a richer async story | full receipt tree and detailed outcomes | deep follow-up only | + +**Status and wait map** + +`wait_until` values are waiting thresholds, not a permanent lifecycle enum you should treat as the user's one true transaction status. The word `pending` is still useful in human conversation, but here it means “the transaction has been submitted and is not yet included.” + +| Phase or threshold | What it means in practice | Best RPC surface | +| --- | --- | --- | +| pre-inclusion / pending | the client has submitted the tx, but it is not yet anchored in a block | your own submission state plus retry/backoff logic | +| `INCLUDED` | the tx is in a block, but that block may not be final yet | `tx` | +| `INCLUDED_FINAL` | the inclusion block is final | `tx` | +| `EXECUTED_OPTIMISTIC` | execution has happened with optimistic finality | `tx` or `send_tx` | +| `FINAL` | all relevant execution has completed and finalized | `tx` by default, `EXPERIMENTAL_tx_status` when you need more detail | + +The key practical distinction is simple: + +- use `broadcast_tx_async` when the tx hash is enough to keep going +- use `tx` as the normal tracking loop +- use `EXPERIMENTAL_tx_status` when the next question is about the receipt tree rather than the headline status + +**What you're doing** + +- Show what a live submission would look like with `broadcast_tx_async`. +- Poll the pinned tx with `tx` at two thresholds: `INCLUDED_FINAL` and `FINAL`. +- Only after that inspect the same tx with `EXPERIMENTAL_tx_status`. +- Optionally pivot to Transactions API if the human-readable story is what matters next. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near +RECEIVER_ID=social.near +``` + +1. If this were a live client flow, submit with `broadcast_tx_async` and keep the returned hash. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc": "2.0", + "id": "fastnear", + "method": "broadcast_tx_async", + "params": ["BASE64_SIGNED_TX"] + }' \ + | jq . +``` + +In a real app, that response is the moment you stop waiting on submission and start tracking by tx hash. + +2. Poll with `tx` at the first threshold that answers the user question. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "INCLUDED_FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' +``` + +What to notice: + +- on a live tx, this threshold is useful when you care that the tx is safely included before you claim success to the user +- on this historical tx, it returns immediately because the transaction is long past inclusion +- `transaction_outcome.outcome.status` still tells you that the original action handed off into receipt execution + +3. Poll again with `FINAL` when you want the completed transaction story rather than just safe inclusion. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + receipts_outcome_count: (.result.receipts_outcome | length) + }' +``` + +What to notice: + +- for a historical tx, this call also returns immediately +- in a real tracking loop, this is the threshold that answers “is the transaction actually done?” +- for many apps, this is where you stop and move on + +4. Only switch to `EXPERIMENTAL_tx_status` when you need the richer receipt tree. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status, + receipts_outcome_count: (.result.receipts_outcome | length) + }' +``` + +This is where you go when “did it finish?” turns into “show me the receipt tree and the full async execution story.” + +5. Optional: pivot to Transactions API only when you want the readable story surface. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transaction_handoff: .transactions[0].transaction_outcome.outcome.status + }' +``` + +That last step is intentionally optional. The RPC truth is already enough for submission and tracking. This is only the human-readable story surface when the next user question becomes “what actually happened?” instead of “how far did the tx get?” + +**Recommended pattern** + +- Use `broadcast_tx_async` plus `tx` polling when you want the best client control and the fastest feedback. +- Use `send_tx` when you really do want one blocking call to wait up to a chosen threshold. +- Use `EXPERIMENTAL_tx_status` when the normal polling loop stops being enough and the receipt tree becomes the real question. + ## Account and Key Mechanics Start here when the question is about exact permissions, exact key state, or one contract-level write flow. @@ -1218,6 +1437,7 @@ Sometimes the right RPC answer is just: here is the widget, here is the live sou **Start here** +- Start with the worked example above when the real question is which submission endpoint to use and how to track the transaction through completion. - [Send Transaction](/rpc/transaction/send-tx) when you want RPC submission with explicit waiting semantics. - [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) or [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit) when those exact submission modes are the point. - [Transaction Status](/rpc/transaction/tx-status) to confirm the final result. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index d6745a2..2342540 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -12,6 +12,225 @@ page_actions: Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. +## Отправка и отслеживание транзакции + +Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» + +### Отправить транзакцию и затем проследить её от хеша до финального исполнения + +Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. + +Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: + +- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- signer: `mike.near` +- receiver: `social.near` +- высота блока включения: `79574923` +- высота блока исполнения receipt для записи в SocialDB: `79574924` + +Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. + +
+
+ Стратегия +

Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно.

+
+
+

01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше.

+

02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения.

+

03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts.

+
+
+ +**Что вы здесь решаете** + +- какой эндпоинт отправки брать первым +- что опрашивать после того, как у вас появился tx hash +- как `wait_until` связан с included-, optimistic- и final-гарантиями +- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` + +```mermaid +flowchart LR + S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] + A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] + T --> F["Транзакция полностью завершена"] + T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] + F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] +``` + +| Метод | Когда использовать | Что вернётся | Роль здесь | +| --- | --- | --- | --- | +| [`broadcast_tx_async`](/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | +| [`send_tx`](/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | +| [`broadcast_tx_commit`](/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | +| [`tx`](/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | +| [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | + +**Карта статусов и ожидания** + +Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. + +| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | +| --- | --- | --- | +| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | +| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | +| `INCLUDED_FINAL` | блок включения уже финален | `tx` | +| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | +| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | + +Практическое различие очень простое: + +- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash +- используйте `tx` как обычный цикл опроса +- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу + +**Что вы делаете** + +- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. +- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. +- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. +- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near +RECEIVER_ID=social.near +``` + +1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc": "2.0", + "id": "fastnear", + "method": "broadcast_tx_async", + "params": ["BASE64_SIGNED_TX"] + }' \ + | jq . +``` + +В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. + +2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "INCLUDED_FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' +``` + +Что здесь важно заметить: + +- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality +- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения +- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt + +3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + receipts_outcome_count: (.result.receipts_outcome | length) + }' +``` + +Что здесь важно заметить: + +- для исторической tx этот вызов тоже возвращается сразу +- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» +- для многих приложений именно здесь и стоит остановиться + +4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status, + receipts_outcome_count: (.result.receipts_outcome | length) + }' +``` + +Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». + +5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transaction_handoff: .transactions[0].transaction_outcome.outcome.status + }' +``` + +Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» + +**Рекомендуемый паттерн** + +- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. +- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. +- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. + ## Механика аккаунтов и ключей Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. @@ -1218,6 +1437,7 @@ jq -r \ **Начните здесь** +- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. - [Send Transaction](/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. - [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. - [Transaction Status](/rpc/transaction/tx-status), чтобы подтвердить финальный результат. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index de81222..59ffcad 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -2451,6 +2451,220 @@ https://archival-rpc.testnet.fastnear.com Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. +## Отправка и отслеживание транзакции + +Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» + +### Отправить транзакцию и затем проследить её от хеша до финального исполнения + +Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. + +Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: + +- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- signer: `mike.near` +- receiver: `social.near` +- высота блока включения: `79574923` +- высота блока исполнения receipt для записи в SocialDB: `79574924` + +Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. + + Стратегия + Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. + + 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. + 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. + 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. + +**Что вы здесь решаете** + +- какой эндпоинт отправки брать первым +- что опрашивать после того, как у вас появился tx hash +- как `wait_until` связан с included-, optimistic- и final-гарантиями +- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` + +```mermaid +flowchart LR + S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] + A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] + T --> F["Транзакция полностью завершена"] + T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] + F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] +``` + +| Метод | Когда использовать | Что вернётся | Роль здесь | +| --- | --- | --- | --- | +| [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | +| [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | +| [`broadcast_tx_commit`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | +| [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | +| [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | + +**Карта статусов и ожидания** + +Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. + +| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | +| --- | --- | --- | +| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | +| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | +| `INCLUDED_FINAL` | блок включения уже финален | `tx` | +| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | +| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | + +Практическое различие очень простое: + +- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash +- используйте `tx` как обычный цикл опроса +- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу + +**Что вы делаете** + +- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. +- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. +- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. +- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near +RECEIVER_ID=social.near +``` + +1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc": "2.0", + "id": "fastnear", + "method": "broadcast_tx_async", + "params": ["BASE64_SIGNED_TX"] + }' \ + | jq . +``` + +В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. + +2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "INCLUDED_FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' +``` + +Что здесь важно заметить: + +- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality +- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения +- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt + +3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + receipts_outcome_count: (.result.receipts_outcome | length) + }' +``` + +Что здесь важно заметить: + +- для исторической tx этот вызов тоже возвращается сразу +- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» +- для многих приложений именно здесь и стоит остановиться + +4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status, + receipts_outcome_count: (.result.receipts_outcome | length) + }' +``` + +Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». + +5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transaction_handoff: .transactions[0].transaction_outcome.outcome.status + }' +``` + +Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» + +**Рекомендуемый паттерн** + +- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. +- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. +- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. + ## Механика аккаунтов и ключей Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. @@ -3630,6 +3844,7 @@ jq -r \ **Начните здесь** +- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. - [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. - [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. - [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index 4f9eb62..d3bb3aa 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -4,6 +4,220 @@ Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. +## Отправка и отслеживание транзакции + +Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» + +### Отправить транзакцию и затем проследить её от хеша до финального исполнения + +Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. + +Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: + +- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- signer: `mike.near` +- receiver: `social.near` +- высота блока включения: `79574923` +- высота блока исполнения receipt для записи в SocialDB: `79574924` + +Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. + + Стратегия + Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. + + 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. + 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. + 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. + +**Что вы здесь решаете** + +- какой эндпоинт отправки брать первым +- что опрашивать после того, как у вас появился tx hash +- как `wait_until` связан с included-, optimistic- и final-гарантиями +- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` + +```mermaid +flowchart LR + S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] + A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] + T --> F["Транзакция полностью завершена"] + T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] + F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] +``` + +| Метод | Когда использовать | Что вернётся | Роль здесь | +| --- | --- | --- | --- | +| [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | +| [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | +| [`broadcast_tx_commit`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | +| [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | +| [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | + +**Карта статусов и ожидания** + +Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. + +| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | +| --- | --- | --- | +| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | +| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | +| `INCLUDED_FINAL` | блок включения уже финален | `tx` | +| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | +| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | + +Практическое различие очень простое: + +- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash +- используйте `tx` как обычный цикл опроса +- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу + +**Что вы делаете** + +- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. +- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. +- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. +- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near +RECEIVER_ID=social.near +``` + +1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc": "2.0", + "id": "fastnear", + "method": "broadcast_tx_async", + "params": ["BASE64_SIGNED_TX"] + }' \ + | jq . +``` + +В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. + +2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "INCLUDED_FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' +``` + +Что здесь важно заметить: + +- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality +- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения +- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt + +3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + receipts_outcome_count: (.result.receipts_outcome | length) + }' +``` + +Что здесь важно заметить: + +- для исторической tx этот вызов тоже возвращается сразу +- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» +- для многих приложений именно здесь и стоит остановиться + +4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status, + receipts_outcome_count: (.result.receipts_outcome | length) + }' +``` + +Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». + +5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transaction_handoff: .transactions[0].transaction_outcome.outcome.status + }' +``` + +Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» + +**Рекомендуемый паттерн** + +- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. +- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. +- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. + ## Механика аккаунтов и ключей Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. @@ -1183,6 +1397,7 @@ jq -r \ **Начните здесь** +- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. - [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. - [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. - [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index 4f9eb62..d3bb3aa 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -4,6 +4,220 @@ Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. +## Отправка и отслеживание транзакции + +Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» + +### Отправить транзакцию и затем проследить её от хеша до финального исполнения + +Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. + +Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: + +- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- signer: `mike.near` +- receiver: `social.near` +- высота блока включения: `79574923` +- высота блока исполнения receipt для записи в SocialDB: `79574924` + +Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. + + Стратегия + Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. + + 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. + 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. + 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. + +**Что вы здесь решаете** + +- какой эндпоинт отправки брать первым +- что опрашивать после того, как у вас появился tx hash +- как `wait_until` связан с included-, optimistic- и final-гарантиями +- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` + +```mermaid +flowchart LR + S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] + A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] + T --> F["Транзакция полностью завершена"] + T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] + F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] +``` + +| Метод | Когда использовать | Что вернётся | Роль здесь | +| --- | --- | --- | --- | +| [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | +| [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | +| [`broadcast_tx_commit`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | +| [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | +| [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | + +**Карта статусов и ожидания** + +Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. + +| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | +| --- | --- | --- | +| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | +| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | +| `INCLUDED_FINAL` | блок включения уже финален | `tx` | +| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | +| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | + +Практическое различие очень простое: + +- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash +- используйте `tx` как обычный цикл опроса +- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу + +**Что вы делаете** + +- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. +- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. +- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. +- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near +RECEIVER_ID=social.near +``` + +1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc": "2.0", + "id": "fastnear", + "method": "broadcast_tx_async", + "params": ["BASE64_SIGNED_TX"] + }' \ + | jq . +``` + +В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. + +2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "INCLUDED_FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' +``` + +Что здесь важно заметить: + +- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality +- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения +- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt + +3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + receipts_outcome_count: (.result.receipts_outcome | length) + }' +``` + +Что здесь важно заметить: + +- для исторической tx этот вызов тоже возвращается сразу +- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» +- для многих приложений именно здесь и стоит остановиться + +4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status, + receipts_outcome_count: (.result.receipts_outcome | length) + }' +``` + +Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». + +5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transaction_handoff: .transactions[0].transaction_outcome.outcome.status + }' +``` + +Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» + +**Рекомендуемый паттерн** + +- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. +- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. +- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. + ## Механика аккаунтов и ключей Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. @@ -1183,6 +1397,7 @@ jq -r \ **Начните здесь** +- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. - [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. - [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. - [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. From 2493c1f9ac32f5b7809e5198b6f2175bc702b3f5 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 20:02:35 -0700 Subject: [PATCH 09/35] docs: add raw state counter example --- docs/rpc/examples.md | 179 ++++++++++++++++++ .../current/rpc/examples.md | 179 ++++++++++++++++++ static/ru/llms-full.txt | 169 +++++++++++++++++ static/ru/rpc/examples.md | 169 +++++++++++++++++ static/ru/rpc/examples/index.md | 169 +++++++++++++++++ 5 files changed, 865 insertions(+) diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index 738b8b5..136d9bc 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -1021,6 +1021,184 @@ curl -s "$RPC_URL" \ This is a good RPC example because every step stays close to the contract itself: first check storage state, then send the minimum required change calls, then verify the post-transfer balance directly on the contract. +## Contract Reads and Raw State + +Start here when the question is “does this contract method tell me enough?” versus “should I read the storage directly?” + +### Read a counter straight from contract state, then confirm it with the view method + +Use this when the user story is simple: “I know this contract exposes a counter, but can I read that number straight from storage without calling the contract code?” + +This walkthrough uses the live public testnet contract `counter.near-examples.testnet`. The number can change over time. That is fine. The point is that both reads agree when you run them: + +- `view_state` reads the raw `STATE` entry directly from contract storage +- `call_function get_num` asks the contract for the same current number through its public view API + +
+
+ Strategy +

Read the raw storage first, decode the bytes you got back, then let the contract confirm the same answer through its view method.

+
+
+

01RPC view_state reads the raw STATE entry without running contract code.

+

02Decode the base64 value into bytes, then interpret those bytes with the contract’s known Borsh layout.

+

03RPC call_function get_num is the friendly cross-check that the raw-state read and the view method still agree.

+
+
+ +The mental model matters more than the counter itself: + +- `view_state` is a direct storage read from the trie +- `call_function` executes a read-only method on the contract +- both can answer the same question, but they do different work to get there + +```mermaid +flowchart LR + S["RPC view_state
prefix STATE"] --> R["Raw STATE bytes"] + R --> D["Decode base64 + Borsh"] + D --> N["Signed counter value"] + C["RPC call_function get_num"] --> J["JSON method result"] + N --> X["Compare"] + J --> X + X --> A["Same current counter value"] +``` + +**What you're doing** + +- Read the raw `STATE` key from contract storage. +- Decode the returned bytes into the current signed counter value. +- Call `get_num` through the view method and confirm that the method answer matches the raw-state decode. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export CONTRACT_ID=counter.near-examples.testnet +export STATE_PREFIX_BASE64=U1RBVEU= +``` + +1. Read the raw contract state first. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$CONTRACT_ID" \ + --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_state", + account_id: $account_id, + prefix_base64: $prefix_base64, + finality: "final" + } + }')" \ + | tee /tmp/counter-view-state.json >/dev/null + +jq '{ + block_height: .result.block_height, + key_base64: .result.values[0].key, + value_base64: .result.values[0].value +}' /tmp/counter-view-state.json + +jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json +``` + +That last command should print `STATE`. This is the key family you already knew ahead of time, so `view_state` can go straight to the raw storage entry without asking the contract to execute any method. + +2. Decode the returned value bytes into the signed counter. + +```bash +RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" + +python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . +import base64 +import json +import sys + +raw = base64.b64decode(sys.argv[1]) + +print(json.dumps({ + "value_base64": sys.argv[1], + "bytes": list(raw), + "hex": raw.hex(), + "signed_i8": int.from_bytes(raw, "little", signed=True), + "unsigned_u8": int.from_bytes(raw, "little", signed=False), +})) +PY +``` + +For this specific contract, one byte is enough because the Rust counter stores `val: i8` inside the contract state. That is why a raw value like `CQ==` decodes to one byte `0x09`, which reads as the signed integer `9`. + +One small signed-value note is worth keeping in your head: if the counter were negative, the same one-byte payload would still decode correctly as a signed two's-complement `i8`. For example, `/w==` is the single byte `0xff`, which means `-1` as `signed_i8`, not `255`. + +The reusable recipe is small: + +- `view_state` gives you base64-encoded raw bytes +- you decode those bytes with the contract’s known storage layout +- for larger contracts, that layout may be more complex, but the idea is the same: bytes first, schema second + +3. Now ask the contract the friendly way and compare. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$CONTRACT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_num", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/counter-call-function.json >/dev/null + +jq '{ + block_height: .result.block_height, + view_method_value: (.result.result | implode | fromjson) +}' /tmp/counter-call-function.json +``` + +4. Compare both answers directly. + +```bash +RAW_STATE_NUMBER="$( + python3 - "$RAW_VALUE_BASE64" <<'PY' +import base64 +import sys + +raw = base64.b64decode(sys.argv[1]) +print(int.from_bytes(raw, "little", signed=True)) +PY +)" + +VIEW_METHOD_NUMBER="$( + jq -r '.result.result | implode | fromjson' /tmp/counter-call-function.json +)" + +jq -n \ + --argjson raw_state "$RAW_STATE_NUMBER" \ + --argjson view_method "$VIEW_METHOD_NUMBER" '{ + raw_state: $raw_state, + view_method: $view_method, + agrees_now: ($raw_state == $view_method) + }' +``` + +If `agrees_now` is `true`, you have proved the point of the example: + +- `view_state` answered the question by reading storage directly +- `call_function get_num` answered the same question by running the contract’s public read method + +**Why this next step?** + +Use `view_state` when the real question is about exact storage and you already know the key family. Use `call_function` when you want the contract’s public read API. If the next question becomes historical instead of “what is it right now?”, that is the moment to widen into [KV FastData API](/fastdata/kv). + ## NEAR Social and BOS Exact Reads These stay on exact SocialDB reads and on-chain readiness checks until the question turns historical. @@ -1415,6 +1593,7 @@ Sometimes the right RPC answer is just: here is the widget, here is the live sou **Start here** +- Start with the counter example above when the real decision is “should I use `call_function` or `view_state`?” or “can I read this storage directly instead of calling a method?” - [Call Function](/rpc/contract/call-function) when you already know the view method you want and just need the exact return value. - [View State](/rpc/contract/view-state) when the real question is about raw contract storage or key prefixes, not a method result. - [View Code](/rpc/contract/view-code) when the real question is “is there code here at all?” or “which code hash is deployed?” diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index 2342540..d9798dd 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -1021,6 +1021,184 @@ curl -s "$RPC_URL" \ Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +## Чтения контракта и сырое состояние + +Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» + +### Прочитать счётчик прямо из состояния контракта, а потом подтвердить его через view-метод + +Используйте этот сценарий, когда история простая: «я знаю, что этот контракт держит счётчик, но можно ли прочитать это число напрямую из storage, не вызывая код контракта?» + +В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: оба чтения должны совпасть в тот момент, когда вы их запускаете: + +- `view_state` читает сырой ключ `STATE` прямо из storage контракта +- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API + +
+
+ Стратегия +

Сначала прочитайте raw storage, затем декодируйте байты, а потом дайте контракту подтвердить тот же ответ через view-метод.

+
+
+

01RPC view_state читает сырой ключ STATE, не запуская код контракта.

+

02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта.

+

03RPC call_function get_num — это удобная перепроверка того, что raw-state-чтение и view-метод по-прежнему дают один и тот же ответ.

+
+
+ +Здесь важнее ментальная модель, чем сам счётчик: + +- `view_state` — это прямое чтение storage из trie +- `call_function` исполняет read-only-метод контракта +- оба способа могут ответить на один и тот же вопрос, но делают разную работу + +```mermaid +flowchart LR + S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] + R --> D["Декодировать base64 + Borsh"] + D --> N["Знаковое значение счётчика"] + C["RPC call_function get_num"] --> J["JSON-результат метода"] + N --> X["Сравнить"] + J --> X + X --> A["Одно и то же текущее значение"] +``` + +**Что вы делаете** + +- Читаете сырой ключ `STATE` из storage контракта. +- Декодируете возвращённые байты в текущее знаковое значение счётчика. +- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export CONTRACT_ID=counter.near-examples.testnet +export STATE_PREFIX_BASE64=U1RBVEU= +``` + +1. Сначала прочитайте сырое состояние контракта. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$CONTRACT_ID" \ + --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_state", + account_id: $account_id, + prefix_base64: $prefix_base64, + finality: "final" + } + }')" \ + | tee /tmp/counter-view-state.json >/dev/null + +jq '{ + block_height: .result.block_height, + key_base64: .result.values[0].key, + value_base64: .result.values[0].value +}' /tmp/counter-view-state.json + +jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json +``` + +Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. + +2. Декодируйте байты значения в знаковое число счётчика. + +```bash +RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" + +python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . +import base64 +import json +import sys + +raw = base64.b64decode(sys.argv[1]) + +print(json.dumps({ + "value_base64": sys.argv[1], + "bytes": list(raw), + "hex": raw.hex(), + "signed_i8": int.from_bytes(raw, "little", signed=True), + "unsigned_u8": int.from_bytes(raw, "little", signed=False), +})) +PY +``` + +Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. + +Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. + +Переиспользуемый рецепт здесь короткий: + +- `view_state` возвращает сырые байты в base64 +- вы декодируете эти байты по известной схеме хранения контракта +- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема + +3. Теперь спросите контракт более привычным способом и сравните. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$CONTRACT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_num", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/counter-call-function.json >/dev/null + +jq '{ + block_height: .result.block_height, + view_method_value: (.result.result | implode | fromjson) +}' /tmp/counter-call-function.json +``` + +4. Сравните оба ответа напрямую. + +```bash +RAW_STATE_NUMBER="$( + python3 - "$RAW_VALUE_BASE64" <<'PY' +import base64 +import sys + +raw = base64.b64decode(sys.argv[1]) +print(int.from_bytes(raw, "little", signed=True)) +PY +)" + +VIEW_METHOD_NUMBER="$( + jq -r '.result.result | implode | fromjson' /tmp/counter-call-function.json +)" + +jq -n \ + --argjson raw_state "$RAW_STATE_NUMBER" \ + --argjson view_method "$VIEW_METHOD_NUMBER" '{ + raw_state: $raw_state, + view_method: $view_method, + agrees_now: ($raw_state == $view_method) + }' +``` + +Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: + +- `view_state` ответил на вопрос, прочитав storage напрямую +- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта + +**Зачем нужен следующий шаг?** + +Используйте `view_state`, когда настоящий вопрос относится к точному storage и вы уже знаете семейство ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](/fastdata/kv). + ## Точные чтения NEAR Social и BOS Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. @@ -1415,6 +1593,7 @@ jq -r \ **Начните здесь** +- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» - [Call Function](/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. - [View State](/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. - [View Code](/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 59ffcad..3c2b82b 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -3438,6 +3438,174 @@ curl -s "$RPC_URL" \ Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +## Чтения контракта и сырое состояние + +Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» + +### Прочитать счётчик прямо из состояния контракта, а потом подтвердить его через view-метод + +Используйте этот сценарий, когда история простая: «я знаю, что этот контракт держит счётчик, но можно ли прочитать это число напрямую из storage, не вызывая код контракта?» + +В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: оба чтения должны совпасть в тот момент, когда вы их запускаете: + +- `view_state` читает сырой ключ `STATE` прямо из storage контракта +- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API + + Стратегия + Сначала прочитайте raw storage, затем декодируйте байты, а потом дайте контракту подтвердить тот же ответ через view-метод. + + 01RPC view_state читает сырой ключ STATE, не запуская код контракта. + 02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта. + 03RPC call_function get_num — это удобная перепроверка того, что raw-state-чтение и view-метод по-прежнему дают один и тот же ответ. + +Здесь важнее ментальная модель, чем сам счётчик: + +- `view_state` — это прямое чтение storage из trie +- `call_function` исполняет read-only-метод контракта +- оба способа могут ответить на один и тот же вопрос, но делают разную работу + +```mermaid +flowchart LR + S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] + R --> D["Декодировать base64 + Borsh"] + D --> N["Знаковое значение счётчика"] + C["RPC call_function get_num"] --> J["JSON-результат метода"] + N --> X["Сравнить"] + J --> X + X --> A["Одно и то же текущее значение"] +``` + +**Что вы делаете** + +- Читаете сырой ключ `STATE` из storage контракта. +- Декодируете возвращённые байты в текущее знаковое значение счётчика. +- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export CONTRACT_ID=counter.near-examples.testnet +export STATE_PREFIX_BASE64=U1RBVEU= +``` + +1. Сначала прочитайте сырое состояние контракта. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$CONTRACT_ID" \ + --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_state", + account_id: $account_id, + prefix_base64: $prefix_base64, + finality: "final" + } + }')" \ + | tee /tmp/counter-view-state.json >/dev/null + +jq '{ + block_height: .result.block_height, + key_base64: .result.values[0].key, + value_base64: .result.values[0].value +}' /tmp/counter-view-state.json + +jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json +``` + +Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. + +2. Декодируйте байты значения в знаковое число счётчика. + +```bash +RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" + +python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . + +raw = base64.b64decode(sys.argv[1]) + +print(json.dumps({ + "value_base64": sys.argv[1], + "bytes": list(raw), + "hex": raw.hex(), + "signed_i8": int.from_bytes(raw, "little", signed=True), + "unsigned_u8": int.from_bytes(raw, "little", signed=False), +})) +PY +``` + +Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. + +Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. + +Переиспользуемый рецепт здесь короткий: + +- `view_state` возвращает сырые байты в base64 +- вы декодируете эти байты по известной схеме хранения контракта +- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема + +3. Теперь спросите контракт более привычным способом и сравните. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$CONTRACT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_num", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/counter-call-function.json >/dev/null + +jq '{ + block_height: .result.block_height, + view_method_value: (.result.result | implode | fromjson) +}' /tmp/counter-call-function.json +``` + +4. Сравните оба ответа напрямую. + +```bash +RAW_STATE_NUMBER="$( + python3 - "$RAW_VALUE_BASE64" <<'PY' + +raw = base64.b64decode(sys.argv[1]) +print(int.from_bytes(raw, "little", signed=True)) +PY +)" + +VIEW_METHOD_NUMBER="$( + jq -r '.result.result | implode | fromjson' /tmp/counter-call-function.json +)" + +jq -n \ + --argjson raw_state "$RAW_STATE_NUMBER" \ + --argjson view_method "$VIEW_METHOD_NUMBER" '{ + raw_state: $raw_state, + view_method: $view_method, + agrees_now: ($raw_state == $view_method) + }' +``` + +Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: + +- `view_state` ответил на вопрос, прочитав storage напрямую +- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта + +**Зачем нужен следующий шаг?** + +Используйте `view_state`, когда настоящий вопрос относится к точному storage и вы уже знаете семейство ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). + ## Точные чтения NEAR Social и BOS Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. @@ -3822,6 +3990,7 @@ jq -r \ **Начните здесь** +- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» - [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. - [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. - [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index d3bb3aa..52456e4 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -991,6 +991,174 @@ curl -s "$RPC_URL" \ Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +## Чтения контракта и сырое состояние + +Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» + +### Прочитать счётчик прямо из состояния контракта, а потом подтвердить его через view-метод + +Используйте этот сценарий, когда история простая: «я знаю, что этот контракт держит счётчик, но можно ли прочитать это число напрямую из storage, не вызывая код контракта?» + +В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: оба чтения должны совпасть в тот момент, когда вы их запускаете: + +- `view_state` читает сырой ключ `STATE` прямо из storage контракта +- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API + + Стратегия + Сначала прочитайте raw storage, затем декодируйте байты, а потом дайте контракту подтвердить тот же ответ через view-метод. + + 01RPC view_state читает сырой ключ STATE, не запуская код контракта. + 02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта. + 03RPC call_function get_num — это удобная перепроверка того, что raw-state-чтение и view-метод по-прежнему дают один и тот же ответ. + +Здесь важнее ментальная модель, чем сам счётчик: + +- `view_state` — это прямое чтение storage из trie +- `call_function` исполняет read-only-метод контракта +- оба способа могут ответить на один и тот же вопрос, но делают разную работу + +```mermaid +flowchart LR + S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] + R --> D["Декодировать base64 + Borsh"] + D --> N["Знаковое значение счётчика"] + C["RPC call_function get_num"] --> J["JSON-результат метода"] + N --> X["Сравнить"] + J --> X + X --> A["Одно и то же текущее значение"] +``` + +**Что вы делаете** + +- Читаете сырой ключ `STATE` из storage контракта. +- Декодируете возвращённые байты в текущее знаковое значение счётчика. +- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export CONTRACT_ID=counter.near-examples.testnet +export STATE_PREFIX_BASE64=U1RBVEU= +``` + +1. Сначала прочитайте сырое состояние контракта. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$CONTRACT_ID" \ + --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_state", + account_id: $account_id, + prefix_base64: $prefix_base64, + finality: "final" + } + }')" \ + | tee /tmp/counter-view-state.json >/dev/null + +jq '{ + block_height: .result.block_height, + key_base64: .result.values[0].key, + value_base64: .result.values[0].value +}' /tmp/counter-view-state.json + +jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json +``` + +Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. + +2. Декодируйте байты значения в знаковое число счётчика. + +```bash +RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" + +python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . + +raw = base64.b64decode(sys.argv[1]) + +print(json.dumps({ + "value_base64": sys.argv[1], + "bytes": list(raw), + "hex": raw.hex(), + "signed_i8": int.from_bytes(raw, "little", signed=True), + "unsigned_u8": int.from_bytes(raw, "little", signed=False), +})) +PY +``` + +Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. + +Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. + +Переиспользуемый рецепт здесь короткий: + +- `view_state` возвращает сырые байты в base64 +- вы декодируете эти байты по известной схеме хранения контракта +- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема + +3. Теперь спросите контракт более привычным способом и сравните. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$CONTRACT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_num", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/counter-call-function.json >/dev/null + +jq '{ + block_height: .result.block_height, + view_method_value: (.result.result | implode | fromjson) +}' /tmp/counter-call-function.json +``` + +4. Сравните оба ответа напрямую. + +```bash +RAW_STATE_NUMBER="$( + python3 - "$RAW_VALUE_BASE64" <<'PY' + +raw = base64.b64decode(sys.argv[1]) +print(int.from_bytes(raw, "little", signed=True)) +PY +)" + +VIEW_METHOD_NUMBER="$( + jq -r '.result.result | implode | fromjson' /tmp/counter-call-function.json +)" + +jq -n \ + --argjson raw_state "$RAW_STATE_NUMBER" \ + --argjson view_method "$VIEW_METHOD_NUMBER" '{ + raw_state: $raw_state, + view_method: $view_method, + agrees_now: ($raw_state == $view_method) + }' +``` + +Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: + +- `view_state` ответил на вопрос, прочитав storage напрямую +- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта + +**Зачем нужен следующий шаг?** + +Используйте `view_state`, когда настоящий вопрос относится к точному storage и вы уже знаете семейство ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). + ## Точные чтения NEAR Social и BOS Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. @@ -1375,6 +1543,7 @@ jq -r \ **Начните здесь** +- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» - [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. - [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. - [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index d3bb3aa..52456e4 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -991,6 +991,174 @@ curl -s "$RPC_URL" \ Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +## Чтения контракта и сырое состояние + +Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» + +### Прочитать счётчик прямо из состояния контракта, а потом подтвердить его через view-метод + +Используйте этот сценарий, когда история простая: «я знаю, что этот контракт держит счётчик, но можно ли прочитать это число напрямую из storage, не вызывая код контракта?» + +В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: оба чтения должны совпасть в тот момент, когда вы их запускаете: + +- `view_state` читает сырой ключ `STATE` прямо из storage контракта +- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API + + Стратегия + Сначала прочитайте raw storage, затем декодируйте байты, а потом дайте контракту подтвердить тот же ответ через view-метод. + + 01RPC view_state читает сырой ключ STATE, не запуская код контракта. + 02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта. + 03RPC call_function get_num — это удобная перепроверка того, что raw-state-чтение и view-метод по-прежнему дают один и тот же ответ. + +Здесь важнее ментальная модель, чем сам счётчик: + +- `view_state` — это прямое чтение storage из trie +- `call_function` исполняет read-only-метод контракта +- оба способа могут ответить на один и тот же вопрос, но делают разную работу + +```mermaid +flowchart LR + S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] + R --> D["Декодировать base64 + Borsh"] + D --> N["Знаковое значение счётчика"] + C["RPC call_function get_num"] --> J["JSON-результат метода"] + N --> X["Сравнить"] + J --> X + X --> A["Одно и то же текущее значение"] +``` + +**Что вы делаете** + +- Читаете сырой ключ `STATE` из storage контракта. +- Декодируете возвращённые байты в текущее знаковое значение счётчика. +- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export CONTRACT_ID=counter.near-examples.testnet +export STATE_PREFIX_BASE64=U1RBVEU= +``` + +1. Сначала прочитайте сырое состояние контракта. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$CONTRACT_ID" \ + --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_state", + account_id: $account_id, + prefix_base64: $prefix_base64, + finality: "final" + } + }')" \ + | tee /tmp/counter-view-state.json >/dev/null + +jq '{ + block_height: .result.block_height, + key_base64: .result.values[0].key, + value_base64: .result.values[0].value +}' /tmp/counter-view-state.json + +jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json +``` + +Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. + +2. Декодируйте байты значения в знаковое число счётчика. + +```bash +RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" + +python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . + +raw = base64.b64decode(sys.argv[1]) + +print(json.dumps({ + "value_base64": sys.argv[1], + "bytes": list(raw), + "hex": raw.hex(), + "signed_i8": int.from_bytes(raw, "little", signed=True), + "unsigned_u8": int.from_bytes(raw, "little", signed=False), +})) +PY +``` + +Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. + +Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. + +Переиспользуемый рецепт здесь короткий: + +- `view_state` возвращает сырые байты в base64 +- вы декодируете эти байты по известной схеме хранения контракта +- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема + +3. Теперь спросите контракт более привычным способом и сравните. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$CONTRACT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_num", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/counter-call-function.json >/dev/null + +jq '{ + block_height: .result.block_height, + view_method_value: (.result.result | implode | fromjson) +}' /tmp/counter-call-function.json +``` + +4. Сравните оба ответа напрямую. + +```bash +RAW_STATE_NUMBER="$( + python3 - "$RAW_VALUE_BASE64" <<'PY' + +raw = base64.b64decode(sys.argv[1]) +print(int.from_bytes(raw, "little", signed=True)) +PY +)" + +VIEW_METHOD_NUMBER="$( + jq -r '.result.result | implode | fromjson' /tmp/counter-call-function.json +)" + +jq -n \ + --argjson raw_state "$RAW_STATE_NUMBER" \ + --argjson view_method "$VIEW_METHOD_NUMBER" '{ + raw_state: $raw_state, + view_method: $view_method, + agrees_now: ($raw_state == $view_method) + }' +``` + +Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: + +- `view_state` ответил на вопрос, прочитав storage напрямую +- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта + +**Зачем нужен следующий шаг?** + +Используйте `view_state`, когда настоящий вопрос относится к точному storage и вы уже знаете семейство ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). + ## Точные чтения NEAR Social и BOS Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. @@ -1375,6 +1543,7 @@ jq -r \ **Начните здесь** +- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» - [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. - [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. - [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» From 5e5dc66469f63c718991a37bf557b9fd6fc01f27 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 20:55:50 -0700 Subject: [PATCH 10/35] docs: tighten example walkthrough correctness --- docs/api/examples.md | 6 +- docs/fastdata/kv/examples.md | 21 +-- docs/neardata/examples.md | 51 ++++-- docs/snapshots/examples.mdx | 32 +++- docs/tx/examples.md | 42 +---- .../current/api/examples.md | 6 +- .../current/fastdata/kv/examples.md | 21 +-- .../current/neardata/examples.md | 51 ++++-- .../current/snapshots/examples.mdx | 32 +++- .../current/tx/examples.md | 42 +---- static/ru/api/examples.md | 6 +- static/ru/api/examples/index.md | 6 +- static/ru/fastdata/kv/examples.md | 19 +-- static/ru/fastdata/kv/examples/index.md | 19 +-- static/ru/guides/llms.txt | 2 +- static/ru/llms-full.txt | 150 ++++++++++-------- static/ru/llms.txt | 2 +- static/ru/neardata/examples.md | 51 ++++-- static/ru/neardata/examples/index.md | 51 ++++-- static/ru/snapshots/examples.md | 32 +++- static/ru/snapshots/examples/index.md | 32 +++- static/ru/tx/examples.md | 42 +---- static/ru/tx/examples/index.md | 42 +---- 23 files changed, 439 insertions(+), 319 deletions(-) diff --git a/docs/api/examples.md b/docs/api/examples.md index 0ac9e2b..3983cfa 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -164,7 +164,7 @@ Use this when the user story is “this BOS widget is a real on-chain artifact.

01GET /v1/account/.../nft checks whether the receiver already holds archive NFTs from this collection.

02RPC call_function get on social.near reads the exact widget source and its SocialDB write block.

-

03Hash the source, mint nft_mint on testnet, then verify the provenance fields through nft_tokens_for_owner.

+

03Hash the source, mint nft_mint on testnet, then verify the exact provenance fields through nft_token.

@@ -318,7 +318,7 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Verify that the minted NFT carries the provenance fields you expect. +5. Verify through the exact `nft_token` read that the minted NFT carries the provenance fields you expect. Poll a few times instead of assuming failure if the token does not appear immediately after the mint transaction returns. @@ -365,7 +365,7 @@ jq '{ **Why this next step?** -FastNear API gives you the quick receiver-side check. Mainnet RPC gives you the exact widget body and SocialDB block. Testnet minting turns that into a durable NFT record. If you later want to prove which historical transaction wrote the widget, hand off to the NEAR Social proof investigations on [Transactions API examples](/tx/examples). +FastNear API gives you the quick receiver-side check. Mainnet RPC gives you the exact widget body and SocialDB block. The exact `nft_token` read on testnet confirms that minting turned that into a durable NFT record. If you later want to prove which historical transaction wrote the widget, hand off to the NEAR Social proof investigations on [Transactions API examples](/tx/examples). ## Common jobs diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index 21ad761..ba90d82 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: KV FastData Examples -description: Plain-language workflows for checking exact storage keys, following indexed write history, and confirming current state with RPC. +description: Plain-language workflows for checking exact storage keys, following indexed write history, and deciding when exact RPC state follow-up is practical. displayed_sidebar: kvFastDataSidebar page_actions: - markdown @@ -12,36 +12,36 @@ page_actions: ### Check one contract key, then follow its history -Use this investigation when one contract storage key looks suspicious and you want the latest indexed value, the write history for that same key, and one final `view_state` check. +Use this investigation when one contract storage key looks suspicious and you want the latest indexed value and the write history for that same key, and need to know whether a raw RPC state check is practical for this contract.
Strategy -

Start with one exact key, widen only into that key’s history, then finish with one chain-state confirmation.

+

Start with one exact key, widen only into that key’s history, then decide whether raw RPC state is realistic for this contract.

01get-latest-key gives the newest indexed row for the exact key you care about.

02get-history-key or history-by-key shows how that same key changed over time.

-

03RPC view_state is the final exact read when you need to compare index history with the chain right now.

+

03RPC view_state is only the final step on contracts whose raw state is practical to query directly.

**Goal** -- Explain what this storage key looks like in the index, how it changed, and whether `view_state` agrees right now. +- Explain what this storage key looks like in the index, how it changed, and whether an exact RPC state check is practical here. | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | | Latest indexed value | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Fetch the newest indexed row for the exact key first | Gives the fastest narrow answer before widening into history | | Indexed key history | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) or [`history-by-key`](/fastdata/kv/history-by-key) | Pull the same key’s change history over time | Shows whether the current value is stable, recent, or part of a suspicious sequence | | Broader write pattern | KV FastData [`latest-by-account`](/fastdata/kv/latest-by-account) or [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Check the account or predecessor if the one key is only part of a larger pattern | Helps explain whether the key changed by itself or as part of a bigger write set | -| Exact state check | RPC [`view_state`](/rpc/contract/view-state) | Confirm the current on-chain state once the indexed pattern is clear | Separates indexed storage history from the exact state the chain would return now | +| Contract-specific exact state check | RPC [`view_state`](/rpc/contract/view-state) | Use it only on contracts whose raw state can actually be queried directly | Keeps you from promising a raw-state confirmation that the target contract cannot serve | **What a useful answer should include** - the exact key and contract scope investigated - the latest indexed value and what changed in history -- whether `view_state` matched the indexed current value +- whether an exact raw-state follow-up is practical for this contract ### Exact key history shell walkthrough @@ -99,7 +99,7 @@ curl -s "$KV_BASE_URL/v0/history" \ **Why this next step?** -The first lookup answers “what do we have right now?” Reusing the exact `key` in `POST /v0/history` answers “how did it get here?” If that result is too broad, narrow back down with [GET History by Exact Key](/fastdata/kv/get-history-key). +The first lookup answers “what do we have right now?” Reusing the exact `key` in `POST /v0/history` answers “how did it get here?” For this exact `social.near` key, the indexed history is the correct finish: a raw `view_state` follow-up is not practical here because the contract state is too large. If you need exact chain-state confirmation on a smaller contract, that is the moment to pivot to [View State](/rpc/contract/view-state). ## Common jobs @@ -119,7 +119,7 @@ The first lookup answers “what do we have right now?” Reusing the exact `key **Switch when** -- The user needs the exact current chain state rather than the index. Move to [View State](/rpc/contract/view-state). +- The user needs exact current chain state on a contract small enough for direct raw-state inspection. Move to [View State](/rpc/contract/view-state) or the contract's own read method. ### Turn one exact key into a change history @@ -138,7 +138,7 @@ The first lookup answers “what do we have right now?” Reusing the exact `key **Switch when** -- The user asks whether the latest indexed value matches the chain right now. +- The user now needs exact current chain state, not just indexed history. On smaller contracts, move to [View State](/rpc/contract/view-state); otherwise use the contract's own read method. ### Trace writes from one predecessor @@ -182,6 +182,7 @@ The first lookup answers “what do we have right now?” Reusing the exact `key - Starting with broad account or predecessor scans when an exact key is already known. - Using KV FastData when the user really wants balances or holdings. - Confusing indexed history with exact current chain state. +- Promising a raw `view_state` proof on very large contracts such as `social.near`. - Reusing pagination tokens or changing filters mid-scan. ## Related guides diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index 05b8b77..9b1db5f 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -28,7 +28,7 @@ Use this investigation when you want to notice a new block as early as possible, **Goal** -- Notice a recent block quickly, then check the same thing again once finality catches up. +- Notice one recent block-family change early, then confirm which finalized block caught up. | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | @@ -40,37 +40,66 @@ Use this investigation when you want to notice a new block as early as possible, **What a useful answer should include** -- which optimistic observation first triggered the investigation -- when the same observation became finalized +- which optimistic redirect target and resolved block first triggered the investigation +- when the finalized helper caught up and which block it resolved to - whether the exact RPC block changed the interpretation -### Finalized block follow-up shell walkthrough +### Optimistic signal to finalized confirmation shell walkthrough -Use this when you want the helper route to pick the latest finalized block for you, but you still want to confirm the exact block in RPC. +Use this when you want to notice a fresh block-family change immediately, then prove which finalized block caught up and confirm that exact height in RPC. **What you're doing** -- Inspect the redirect returned by `GET /v0/last_block/final`. -- Fetch the resolved block document. -- Extract `block.header.height` with `jq`. -- Reuse that height in RPC `block` by height. +- Inspect the redirect returned by `GET /v0/last_block/optimistic`. +- Fetch the resolved optimistic block document and keep its height and hash. +- Inspect the redirect returned by `GET /v0/last_block/final` and keep the finalized counterpart. +- Compare the optimistic and finalized observations, then reuse the finalized height in RPC `block` by height. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz RPC_URL=https://rpc.mainnet.fastnear.com +OPTIMISTIC_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +printf 'Optimistic redirect target: %s\n' "$OPTIMISTIC_LOCATION" + +curl -s "$NEARDATA_BASE_URL$OPTIMISTIC_LOCATION" \ + | tee /tmp/neardata-optimistic-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ | awk 'tolower($1) == "location:" {print $2}' \ | tr -d '\r' )" -printf 'Redirect target: %s\n' "$FINAL_LOCATION" +printf 'Final redirect target: %s\n' "$FINAL_LOCATION" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | tee /tmp/neardata-final-block.json \ | jq '{height: .block.header.height, hash: .block.header.hash}' +jq -n \ + --slurpfile optimistic /tmp/neardata-optimistic-block.json \ + --slurpfile final /tmp/neardata-final-block.json '{ + optimistic: { + height: $optimistic[0].block.header.height, + hash: $optimistic[0].block.header.hash + }, + final: { + height: $final[0].block.header.height, + hash: $final[0].block.header.hash + }, + same_height: ( + $optimistic[0].block.header.height + == $final[0].block.header.height + ) + }' + BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" curl -s "$RPC_URL" \ @@ -88,7 +117,7 @@ curl -s "$RPC_URL" \ **Why this next step?** -The redirect helper is the easiest way to poll for “latest finalized.” Once it gives you a concrete block height, RPC is the natural next read if you want the exact block object the protocol would return. +This gives you both sides of the story: the earliest optimistic anchor and the later finalized anchor. Once the finalized helper gives you a concrete block height, RPC is the natural next read if you want the exact block object the protocol would return. ## Common jobs diff --git a/docs/snapshots/examples.mdx b/docs/snapshots/examples.mdx index a7261b7..4ef654a 100644 --- a/docs/snapshots/examples.mdx +++ b/docs/snapshots/examples.mdx @@ -45,6 +45,28 @@ Use this investigation when an operator says “I need this node back online” - where data should land on disk - whether the operator should stay in FastNear snapshot docs or move to general nearcore bootstrap docs +### Optimized mainnet `fast-rpc` command anchor + +Use this when you have already decided the target is a high-performance mainnet RPC node and just need the smallest runnable command anchor. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash +``` + +### Standard mainnet RPC command anchor + +Use this when you want the default mainnet RPC recovery path without the optimized `fast-rpc` profile. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet bash +``` + ### Mainnet archival recovery shell walkthrough Use this when you have already decided that archival mainnet is the right path and now need the exact command sequence with one shared block anchor. @@ -79,11 +101,11 @@ Hot and cold archival data need to come from the same snapshot cut. Reusing one **Start here** -- [Mainnet snapshots](/snapshots/mainnet), specifically the optimized `fast-rpc` path. +- Use the optimized mainnet `fast-rpc` command anchor above. **Next page if needed** -- Revisit the standard mainnet RPC path if the node cannot support the optimized profile. +- [Mainnet snapshots](/snapshots/mainnet) if you need to tune `THREADS`, `BWLIMIT`, or a custom `DATA_PATH`. **Stop when** @@ -97,11 +119,11 @@ Hot and cold archival data need to come from the same snapshot cut. Reusing one **Start here** -- [Mainnet snapshots](/snapshots/mainnet) or [Testnet snapshots](/snapshots/testnet), depending on network, and choose the standard RPC snapshot path for that environment. +- Use the standard mainnet RPC command anchor above. **Next page if needed** -- Adjust `DATA_PATH`, `THREADS`, or bandwidth settings only after the standard path is clear. +- [Mainnet snapshots](/snapshots/mainnet) if you need to tune `DATA_PATH`, `THREADS`, or bandwidth settings further. **Stop when** @@ -115,7 +137,7 @@ Hot and cold archival data need to come from the same snapshot cut. Reusing one **Start here** -- [Mainnet snapshots](/snapshots/mainnet), archival section. +- Use the archival walkthrough above. **Next page if needed** diff --git a/docs/tx/examples.md b/docs/tx/examples.md index a2129e0..c820337 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -500,7 +500,7 @@ This is the opposite of the failed batch example above. There, one action failed

01POST /v0/transactions gives the easiest first pass: which receipt ran first, and which receipt failed later.

02RPC EXPERIMENTAL_tx_status proves the important NEAR nuance that top-level success and later descendant failure can both be true.

-

03RPC call_function on the router contract tells you whether the first receipt's own local state change stuck.

+

03Once those two views agree on the split, stop. This example stays on preserved historical evidence rather than a live router-state read.

@@ -529,26 +529,24 @@ This pinned async failure was captured on **April 18, 2026** on testnet: ```mermaid flowchart LR T["Signed tx
kickoff_append(...)"] --> R["First receipt on seq-dr.mike.testnet
SuccessValue + kickoff log"] - R --> S["Router stores local state
kicked += late-failure"] R --> D["Detached cross-contract receipt
append(...)"] D --> F["Later failure
CodeDoesNotExist"] - S -. "state from the first receipt still sticks" .-> K["kicked() still contains late-failure"] + T -. "outer transaction still resolves" .-> X["RPC top-level status
SuccessValue"] ``` | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | | Transaction skeleton | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the pinned transaction and print the included block plus the per-receipt timeline | Gives the shortest readable overview of which receipt ran first and which receipt failed later | | Exact status semantics | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Inspect the top-level `status`, the first contract receipt outcome, and the later failed receipt outcome | Proves that top-level success and later descendant failure can coexist in one async story | -| Current contract state | RPC [`query(call_function)`](/rpc/contract/call-function) | Call `seq-dr.mike.testnet.kicked()` | Shows that the first receipt's local state change stuck even though the later detached receipt failed | -One NEAR detail matters here: receipt success is not transitive. `seq-dr.mike.testnet` returned success on its own receipt because `kickoff_append(...)` only logged and detached the next hop. The detached `append(...)` receipt was a separate piece of async work, so its later failure did not rewind the router's earlier state change. +One NEAR detail matters here: receipt success is not transitive. `seq-dr.mike.testnet` returned success on its own receipt because `kickoff_append(...)` only logged and detached the next hop. The detached `append(...)` receipt was a separate piece of async work, so its later failure did not change the fact that the router's own receipt had already completed successfully. **What a useful answer should include** - that the signed transaction successfully handed off into the first router receipt - that the router receipt itself succeeded and emitted the `dishonest_router:kickoff:late-failure` log - that the later detached receipt to `asyncfail-in2hwikn.temp.mike.testnet` failed with `CodeDoesNotExist` -- that the router's own state still contains `late-failure`, so the first receipt's local side effect stuck +- that RPC still reports the outer transaction as `SuccessValue` even though a later detached receipt failed - one sentence explaining why this is different from a failed batched transaction #### Later receipt failure shell walkthrough @@ -559,14 +557,13 @@ Use this when the user story is “the contract call looked fine, but something - Read the transaction and its receipt timeline from the indexed view. - Use RPC transaction status to show that the top-level story still ended in `SuccessValue` even though a later receipt failed. -- Read the router's current state to show that the first receipt's local side effect stuck. +- Stop once those two preserved views agree on the split. ```bash TX_BASE_URL=https://tx.test.fastnear.com RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz SIGNER_ACCOUNT_ID=temp.mike.testnet -ROUTER_ACCOUNT_ID=seq-dr.mike.testnet FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es ``` @@ -655,36 +652,11 @@ jq \ # - the later append(...) receipt failed with CodeDoesNotExist ``` -3. Read the router's current state and confirm that the first receipt's local side effect stuck. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ROUTER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "kicked", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee /tmp/later-receipt-failure-kicked.json >/dev/null - -jq '{ - kicked: (.result.result | implode | fromjson), - contains_late_failure: ((.result.result | implode | fromjson) | index("late-failure") != null) -}' /tmp/later-receipt-failure-kicked.json -``` - -That last read is the practical proof that the first receipt's local state change stuck. The later failed receipt did not rewind the router's earlier `kicked.push(...)`. +Stop here. As of **April 18, 2026**, `seq-dr.mike.testnet` no longer resolves on testnet, so a live router-state proof would no longer be truthful. The indexed receipt timeline plus `EXPERIMENTAL_tx_status` are the preserved historical evidence that still matters. **Why this next step?** -When a NEAR app “looked successful” and still broke later, the thing to ask is not just “what was the transaction status?” but “which receipt succeeded, and which later receipt failed?” This example gives you that exact split: indexed receipt timeline for the shape, RPC status for the exact semantics, and one contract-state read to prove the earlier side effect stuck. +When a NEAR app “looked successful” and still broke later, the thing to ask is not just “what was the transaction status?” but “which receipt succeeded, and which later receipt failed?” This example gives you that exact split: indexed receipt timeline for the shape, RPC status for the exact semantics, and no pretend live router-state read after the historical contract disappeared. ### Trace an async promise chain and prove callback order diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index eb20bc6..b982c8f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -164,7 +164,7 @@ jq -n \

01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции.

02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB.

-

03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner.

+

03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте точные provenance-поля через nft_token.

@@ -318,7 +318,7 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. +5. Подтвердите через точное чтение `nft_token`, что выпущенный NFT действительно несёт ожидаемые provenance-поля. Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. @@ -365,7 +365,7 @@ jq '{ **Зачем нужен следующий шаг?** -FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](/tx/examples). +FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](/tx/examples). ## Частые задачи diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index 0efd2cd..d09a046 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: "Примеры KV FastData" -description: "Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC." +description: "Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и понимания, когда точный RPC follow-up по состоянию вообще практичен." displayed_sidebar: kvFastDataSidebar page_actions: - markdown @@ -12,36 +12,36 @@ page_actions: ### Проверить один ключ контракта, а затем пройти по его истории -Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и финальную проверку через `view_state`. +Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и понять, практичен ли для этого контракта сырой RPC-чек состояния.
Стратегия -

Начните с одного точного ключа, расширяйтесь только до его истории и завершайте одной проверкой текущего chain-state.

+

Начните с одного точного ключа, расширяйтесь только до его истории, а затем решите, реалистичен ли для этого контракта сырой RPC state-read.

01get-latest-key даёт самую новую индексированную запись по точному ключу.

02get-history-key или history-by-key показывают, как тот же ключ менялся во времени.

-

03RPC view_state — это финальное точное чтение, когда нужно сравнить индексированную историю с тем, что цепочка возвращает прямо сейчас.

+

03RPC view_state — это финальный шаг только для контрактов, чьё сырое состояние вообще реально запросить напрямую.

**Цель** -- Объяснить, как этот ключ выглядит в индексе, как он менялся и совпадает ли с этим `view_state` прямо сейчас. +- Объяснить, как этот ключ выглядит в индексе, как он менялся и практичен ли здесь точный RPC-чек состояния. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | | Последнее индексированное значение | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | | История индексированного ключа | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) или [`history-by-key`](/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | | Более широкий паттерн записей | KV FastData [`latest-by-account`](/fastdata/kv/latest-by-account) или [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Смотрим аккаунт или предшественника, если один ключ — только часть более широкой картины | Помогает понять, менялся ли ключ сам по себе или как часть большего набора записей | -| Точная проверка состояния | RPC [`view_state`](/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | +| Контрактно-специфичная точная проверка состояния | RPC [`view_state`](/rpc/contract/view-state) | Используем только на контрактах, у которых сырое состояние действительно можно читать напрямую | Не даёт пообещать raw-state-подтверждение там, где сам контракт этого не позволяет | **Что должен включать полезный ответ** - какой именно ключ и какая область контракта были исследованы - как выглядит последнее индексированное значение и какие изменения видны в истории -- совпал ли `view_state` с текущим индексированным значением +- практичен ли для этого контракта точный raw-state follow-up ### Shell-сценарий истории точного ключа @@ -99,7 +99,7 @@ curl -s "$KV_BASE_URL/v0/history" \ **Зачем нужен следующий шаг?** -Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](/fastdata/kv/get-history-key). +Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Для этого конкретного ключа в `social.near` именно индексированная история и является правильной конечной точкой: сырой `view_state`-follow-up здесь непрактичен, потому что состояние контракта слишком велико. Если нужен точный chain-state на меньшем контракте, тогда и стоит переходить к [View State](/rpc/contract/view-state). ## Частые задачи @@ -119,7 +119,7 @@ curl -s "$KV_BASE_URL/v0/history" \ **Переходите дальше, когда** -- Пользователю нужно точное текущее состояние в цепочке, а не индексированное хранилище. Переходите к [View State](/rpc/contract/view-state). +- Пользователю нужно точное текущее состояние в цепочке на контракте, который подходит для прямого raw-state чтения. Переходите к [View State](/rpc/contract/view-state) или к собственному read-методу контракта. ### Превратить один точный ключ в историю изменений @@ -138,7 +138,7 @@ curl -s "$KV_BASE_URL/v0/history" \ **Переходите дальше, когда** -- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. +- Теперь нужен точный chain-state, а не только индексированная история. На меньших контрактах переходите к [View State](/rpc/contract/view-state); в остальных случаях используйте собственный read-метод контракта. ### Проследить записи от одного `predecessor_id` @@ -182,6 +182,7 @@ curl -s "$KV_BASE_URL/v0/history" \ - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. - Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. - Путать индексированную историю с точным текущим состоянием в цепочке. +- Обещать raw `view_state`-доказательство на очень больших контрактах вроде `social.near`. - Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. ## Полезные связанные страницы diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index 65af47d..e13fb89 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -28,7 +28,7 @@ page_actions: **Цель** -- Быстро заметить недавний блок, а затем проверить то же самое, когда догонит finality. +- Как можно раньше заметить одно свежее изменение в семействе блоков, а затем подтвердить, какой финализированный блок его догнал. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | @@ -40,37 +40,66 @@ page_actions: **Что должен включать полезный ответ** -- какое наблюдение по оптимистичному блоку впервые запустило расследование -- когда то же наблюдение стало финализированным +- какой redirect target и какой разрешённый оптимистичный блок впервые запустили расследование +- когда helper для finality догнал его и в какой блок он разрешился - изменил ли точный разбор через RPC интерпретацию -### Shell-сценарий проверки финализированного блока +### Shell-сценарий от оптимистичного сигнала к финализированному подтверждению -Используйте этот сценарий, когда вспомогательный маршрут сам выбирает для вас последний финализированный блок, но следующий шаг всё равно требует точной проверки через RPC. +Используйте этот сценарий, когда нужно сразу заметить свежее изменение в семействе блоков, а затем доказать, какой финализированный блок его догнал, и подтвердить именно эту высоту через RPC. **Что вы делаете** -- Смотрите redirect, который возвращает `GET /v0/last_block/final`. -- Загружаете итоговый документ блока. -- Извлекаете `block.header.height` через `jq`. -- Переиспользуете эту высоту в RPC `block` по высоте. +- Смотрите redirect, который возвращает `GET /v0/last_block/optimistic`. +- Загружаете разрешённый оптимистичный блок и сохраняете его высоту и хеш. +- Смотрите redirect, который возвращает `GET /v0/last_block/final`, и сохраняете финализированный counterpart. +- Сравниваете оптимистичное и финализированное наблюдения, а затем переиспользуете финализированную высоту в RPC `block` по высоте. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz RPC_URL=https://rpc.mainnet.fastnear.com +OPTIMISTIC_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +printf 'Optimistic redirect target: %s\n' "$OPTIMISTIC_LOCATION" + +curl -s "$NEARDATA_BASE_URL$OPTIMISTIC_LOCATION" \ + | tee /tmp/neardata-optimistic-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ | awk 'tolower($1) == "location:" {print $2}' \ | tr -d '\r' )" -printf 'Redirect target: %s\n' "$FINAL_LOCATION" +printf 'Final redirect target: %s\n' "$FINAL_LOCATION" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | tee /tmp/neardata-final-block.json \ | jq '{height: .block.header.height, hash: .block.header.hash}' +jq -n \ + --slurpfile optimistic /tmp/neardata-optimistic-block.json \ + --slurpfile final /tmp/neardata-final-block.json '{ + optimistic: { + height: $optimistic[0].block.header.height, + hash: $optimistic[0].block.header.hash + }, + final: { + height: $final[0].block.header.height, + hash: $final[0].block.header.hash + }, + same_height: ( + $optimistic[0].block.header.height + == $final[0].block.header.height + ) + }' + BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" curl -s "$RPC_URL" \ @@ -88,7 +117,7 @@ curl -s "$RPC_URL" \ **Зачем нужен следующий шаг?** -Helper route — самый простой способ опрашивать сценарий «последний финализированный блок». Как только он сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. +Так вы получаете обе стороны истории: самый ранний оптимистичный якорь и более поздний финализированный якорь. Как только helper для finality сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. ## Частые задачи diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx index 5babf4c..d364d1a 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx @@ -45,6 +45,28 @@ page_actions: - куда на диске должны попасть данные - должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap +### Минимальная команда для optimized mainnet `fast-rpc` + +Используйте этот якорь, когда уже ясно, что нужен высокопроизводительный mainnet RPC-узел, и требуется только минимальная рабочая команда. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash +``` + +### Минимальная команда для стандартного mainnet RPC + +Используйте этот якорь, когда нужен обычный путь восстановления mainnet RPC без optimized-профиля `fast-rpc`. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet bash +``` + ### Shell-сценарий архивного восстановления mainnet Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. @@ -79,11 +101,11 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ **Начните здесь** -- [Снапшоты mainnet](/snapshots/mainnet), конкретно путь для optimized `fast-rpc`. +- Используйте якорь с optimized mainnet `fast-rpc` выше. **Следующая страница при необходимости** -- Вернитесь к обычному сценарию mainnet RPC, если узел не подходит для optimized profile. +- [Снапшоты mainnet](/snapshots/mainnet), если нужно дополнительно настроить `THREADS`, `BWLIMIT` или кастомный `DATA_PATH`. **Остановитесь, когда** @@ -97,11 +119,11 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ **Начните здесь** -- [Снапшоты mainnet](/snapshots/mainnet) или [Снапшоты testnet](/snapshots/testnet) в зависимости от сети и выберите обычный RPC-сценарий для нужного окружения. +- Используйте якорь со стандартным mainnet RPC выше. **Следующая страница при необходимости** -- Настраивайте `DATA_PATH`, `THREADS` или ограничения по пропускной способности только после того, как понятен стандартный сценарий. +- [Снапшоты mainnet](/snapshots/mainnet), если нужно дополнительно настроить `DATA_PATH`, `THREADS` или ограничения по пропускной способности. **Остановитесь, когда** @@ -115,7 +137,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ **Начните здесь** -- [Снапшоты mainnet](/snapshots/mainnet), раздел архивного режима. +- Используйте архивный walkthrough выше. **Следующая страница при необходимости** diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index 59b4510..1493da3 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -500,7 +500,7 @@ jq '{

01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже.

02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой.

-

03RPC call_function на роутере показывает, закрепилось ли собственное локальное изменение состояния из первой receipt.

+

03Как только эти два представления сходятся на одном и том же разрезе истории, остановитесь. Этот пример держится за сохранённые исторические свидетельства, а не за живой read состояния роутера.

@@ -529,26 +529,24 @@ jq '{ ```mermaid flowchart LR T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] - R --> S["Роутер сохраняет локальное состояние
kicked += late-failure"] R --> D["Detached cross-contract receipt
append(...)"] D --> F["Более поздний сбой
CodeDoesNotExist"] - S -. "состояние из первой receipt всё равно закрепилось" .-> K["kicked() всё ещё содержит late-failure"] + T -. "внешняя транзакция всё равно завершается" .-> X["RPC top-level status
SuccessValue"] ``` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | | Каркас транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | | Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | -| Текущее состояние контракта | RPC [`query(call_function)`](/rpc/contract/call-function) | Вызываем `seq-dr.mike.testnet.kicked()` | Показывает, что локальное изменение состояния из первой receipt закрепилось, хотя более поздняя detached-receipt упала | -Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не откатил более раннее изменение состояния роутера. +Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. **Что должен включать полезный ответ** - что подписанная транзакция успешно передала управление в первую router-receipt - что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` - что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` -- что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился +- что RPC всё ещё показывает верхнеуровневый `SuccessValue`, хотя более поздняя detached-receipt упала - одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции #### Shell-сценарий более позднего сбоя receipt @@ -559,14 +557,13 @@ flowchart LR - Читаете транзакцию и её таймлайн receipt из индексированного представления. - Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. -- Читаете текущее состояние роутера, чтобы показать: локальный побочный эффект первой receipt закрепился. +- Останавливаетесь, как только эти два сохранённых представления сходятся на одном и том же разрезе истории. ```bash TX_BASE_URL=https://tx.test.fastnear.com RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz SIGNER_ACCOUNT_ID=temp.mike.testnet -ROUTER_ACCOUNT_ID=seq-dr.mike.testnet FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es ``` @@ -655,36 +652,11 @@ jq \ # - более поздняя receipt append(...) упала с CodeDoesNotExist ``` -3. Прочитайте текущее состояние роутера и подтвердите, что локальный побочный эффект первой receipt закрепился. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ROUTER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "kicked", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee /tmp/later-receipt-failure-kicked.json >/dev/null - -jq '{ - kicked: (.result.result | implode | fromjson), - contains_late_failure: ((.result.result | implode | fromjson) | index("late-failure") != null) -}' /tmp/later-receipt-failure-kicked.json -``` - -Этот последний read и есть практическое доказательство того, что локальное изменение из первой receipt закрепилось. Более поздняя упавшая receipt не откатила более ранний `kicked.push(...)` внутри роутера. +Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. **Зачем нужен следующий шаг?** -Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. +Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. ### Проследить асинхронную promise-цепочку и доказать порядок callback-ов diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index 1946346..49f7e22 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -143,7 +143,7 @@ jq -n \ 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. - 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner. + 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте точные provenance-поля через nft_token. **Сети** @@ -295,7 +295,7 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. +5. Подтвердите через точное чтение `nft_token`, что выпущенный NFT действительно несёт ожидаемые provenance-поля. Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. @@ -342,7 +342,7 @@ jq '{ **Зачем нужен следующий шаг?** -FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). +FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). ## Частые задачи diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index 1946346..49f7e22 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -143,7 +143,7 @@ jq -n \ 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. - 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner. + 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте точные provenance-поля через nft_token. **Сети** @@ -295,7 +295,7 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. +5. Подтвердите через точное чтение `nft_token`, что выпущенный NFT действительно несёт ожидаемые provenance-поля. Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. @@ -342,7 +342,7 @@ jq '{ **Зачем нужен следующий шаг?** -FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). +FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). ## Частые задачи diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index 77e9db8..5e561fe 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -4,31 +4,31 @@ ### Проверить один ключ контракта, а затем пройти по его истории -Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и финальную проверку через `view_state`. +Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и понять, практичен ли для этого контракта сырой RPC-чек состояния. Стратегия - Начните с одного точного ключа, расширяйтесь только до его истории и завершайте одной проверкой текущего chain-state. + Начните с одного точного ключа, расширяйтесь только до его истории, а затем решите, реалистичен ли для этого контракта сырой RPC state-read. 01get-latest-key даёт самую новую индексированную запись по точному ключу. 02get-history-key или history-by-key показывают, как тот же ключ менялся во времени. - 03RPC view_state — это финальное точное чтение, когда нужно сравнить индексированную историю с тем, что цепочка возвращает прямо сейчас. + 03RPC view_state — это финальный шаг только для контрактов, чьё сырое состояние вообще реально запросить напрямую. **Цель** -- Объяснить, как этот ключ выглядит в индексе, как он менялся и совпадает ли с этим `view_state` прямо сейчас. +- Объяснить, как этот ключ выглядит в индексе, как он менялся и практичен ли здесь точный RPC-чек состояния. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | | Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | | История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | | Более широкий паттерн записей | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Смотрим аккаунт или предшественника, если один ключ — только часть более широкой картины | Помогает понять, менялся ли ключ сам по себе или как часть большего набора записей | -| Точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | +| Контрактно-специфичная точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Используем только на контрактах, у которых сырое состояние действительно можно читать напрямую | Не даёт пообещать raw-state-подтверждение там, где сам контракт этого не позволяет | **Что должен включать полезный ответ** - какой именно ключ и какая область контракта были исследованы - как выглядит последнее индексированное значение и какие изменения видны в истории -- совпал ли `view_state` с текущим индексированным значением +- практичен ли для этого контракта точный raw-state follow-up ### Shell-сценарий истории точного ключа @@ -86,7 +86,7 @@ curl -s "$KV_BASE_URL/v0/history" \ **Зачем нужен следующий шаг?** -Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). +Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Для этого конкретного ключа в `social.near` именно индексированная история и является правильной конечной точкой: сырой `view_state`-follow-up здесь непрактичен, потому что состояние контракта слишком велико. Если нужен точный chain-state на меньшем контракте, тогда и стоит переходить к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). ## Частые задачи @@ -106,7 +106,7 @@ curl -s "$KV_BASE_URL/v0/history" \ **Переходите дальше, когда** -- Пользователю нужно точное текущее состояние в цепочке, а не индексированное хранилище. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). +- Пользователю нужно точное текущее состояние в цепочке на контракте, который подходит для прямого raw-state чтения. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) или к собственному read-методу контракта. ### Превратить один точный ключ в историю изменений @@ -125,7 +125,7 @@ curl -s "$KV_BASE_URL/v0/history" \ **Переходите дальше, когда** -- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. +- Теперь нужен точный chain-state, а не только индексированная история. На меньших контрактах переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state); в остальных случаях используйте собственный read-метод контракта. ### Проследить записи от одного `predecessor_id` @@ -169,6 +169,7 @@ curl -s "$KV_BASE_URL/v0/history" \ - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. - Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. - Путать индексированную историю с точным текущим состоянием в цепочке. +- Обещать raw `view_state`-доказательство на очень больших контрактах вроде `social.near`. - Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. ## Полезные связанные страницы diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index 77e9db8..5e561fe 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -4,31 +4,31 @@ ### Проверить один ключ контракта, а затем пройти по его истории -Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и финальную проверку через `view_state`. +Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и понять, практичен ли для этого контракта сырой RPC-чек состояния. Стратегия - Начните с одного точного ключа, расширяйтесь только до его истории и завершайте одной проверкой текущего chain-state. + Начните с одного точного ключа, расширяйтесь только до его истории, а затем решите, реалистичен ли для этого контракта сырой RPC state-read. 01get-latest-key даёт самую новую индексированную запись по точному ключу. 02get-history-key или history-by-key показывают, как тот же ключ менялся во времени. - 03RPC view_state — это финальное точное чтение, когда нужно сравнить индексированную историю с тем, что цепочка возвращает прямо сейчас. + 03RPC view_state — это финальный шаг только для контрактов, чьё сырое состояние вообще реально запросить напрямую. **Цель** -- Объяснить, как этот ключ выглядит в индексе, как он менялся и совпадает ли с этим `view_state` прямо сейчас. +- Объяснить, как этот ключ выглядит в индексе, как он менялся и практичен ли здесь точный RPC-чек состояния. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | | Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | | История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | | Более широкий паттерн записей | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Смотрим аккаунт или предшественника, если один ключ — только часть более широкой картины | Помогает понять, менялся ли ключ сам по себе или как часть большего набора записей | -| Точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | +| Контрактно-специфичная точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Используем только на контрактах, у которых сырое состояние действительно можно читать напрямую | Не даёт пообещать raw-state-подтверждение там, где сам контракт этого не позволяет | **Что должен включать полезный ответ** - какой именно ключ и какая область контракта были исследованы - как выглядит последнее индексированное значение и какие изменения видны в истории -- совпал ли `view_state` с текущим индексированным значением +- практичен ли для этого контракта точный raw-state follow-up ### Shell-сценарий истории точного ключа @@ -86,7 +86,7 @@ curl -s "$KV_BASE_URL/v0/history" \ **Зачем нужен следующий шаг?** -Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). +Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Для этого конкретного ключа в `social.near` именно индексированная история и является правильной конечной точкой: сырой `view_state`-follow-up здесь непрактичен, потому что состояние контракта слишком велико. Если нужен точный chain-state на меньшем контракте, тогда и стоит переходить к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). ## Частые задачи @@ -106,7 +106,7 @@ curl -s "$KV_BASE_URL/v0/history" \ **Переходите дальше, когда** -- Пользователю нужно точное текущее состояние в цепочке, а не индексированное хранилище. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). +- Пользователю нужно точное текущее состояние в цепочке на контракте, который подходит для прямого raw-state чтения. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) или к собственному read-методу контракта. ### Превратить один точный ключ в историю изменений @@ -125,7 +125,7 @@ curl -s "$KV_BASE_URL/v0/history" \ **Переходите дальше, когда** -- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. +- Теперь нужен точный chain-state, а не только индексированная история. На меньших контрактах переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state); в остальных случаях используйте собственный read-метод контракта. ### Проследить записи от одного `predecessor_id` @@ -169,6 +169,7 @@ curl -s "$KV_BASE_URL/v0/history" \ - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. - Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. - Путать индексированную историю с точным текущим состоянием в цепочке. +- Обещать raw `view_state`-доказательство на очень больших контрактах вроде `social.near`. - Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. ## Полезные связанные страницы diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index 731499f..f04e8ad 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -13,7 +13,7 @@ - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и понимания, когда точный RPC follow-up по состоянию вообще практичен. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. - [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 3c2b82b..c831b5b 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -1137,7 +1137,7 @@ jq -n \ 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. - 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner. + 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте точные provenance-поля через nft_token. **Сети** @@ -1289,7 +1289,7 @@ near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ --networkId testnet ``` -5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. +5. Подтвердите через точное чтение `nft_token`, что выпущенный NFT действительно несёт ожидаемые provenance-поля. Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. @@ -1336,7 +1336,7 @@ jq '{ **Зачем нужен следующий шаг?** -FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). +FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). ## Частые задачи @@ -1613,31 +1613,31 @@ https://kv.test.fastnear.com ### Проверить один ключ контракта, а затем пройти по его истории -Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и финальную проверку через `view_state`. +Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и понять, практичен ли для этого контракта сырой RPC-чек состояния. Стратегия - Начните с одного точного ключа, расширяйтесь только до его истории и завершайте одной проверкой текущего chain-state. + Начните с одного точного ключа, расширяйтесь только до его истории, а затем решите, реалистичен ли для этого контракта сырой RPC state-read. 01get-latest-key даёт самую новую индексированную запись по точному ключу. 02get-history-key или history-by-key показывают, как тот же ключ менялся во времени. - 03RPC view_state — это финальное точное чтение, когда нужно сравнить индексированную историю с тем, что цепочка возвращает прямо сейчас. + 03RPC view_state — это финальный шаг только для контрактов, чьё сырое состояние вообще реально запросить напрямую. **Цель** -- Объяснить, как этот ключ выглядит в индексе, как он менялся и совпадает ли с этим `view_state` прямо сейчас. +- Объяснить, как этот ключ выглядит в индексе, как он менялся и практичен ли здесь точный RPC-чек состояния. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | | Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | | История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | | Более широкий паттерн записей | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Смотрим аккаунт или предшественника, если один ключ — только часть более широкой картины | Помогает понять, менялся ли ключ сам по себе или как часть большего набора записей | -| Точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | +| Контрактно-специфичная точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Используем только на контрактах, у которых сырое состояние действительно можно читать напрямую | Не даёт пообещать raw-state-подтверждение там, где сам контракт этого не позволяет | **Что должен включать полезный ответ** - какой именно ключ и какая область контракта были исследованы - как выглядит последнее индексированное значение и какие изменения видны в истории -- совпал ли `view_state` с текущим индексированным значением +- практичен ли для этого контракта точный raw-state follow-up ### Shell-сценарий истории точного ключа @@ -1695,7 +1695,7 @@ curl -s "$KV_BASE_URL/v0/history" \ **Зачем нужен следующий шаг?** -Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Если результат получается слишком широким, снова сузьте его через [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). +Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Для этого конкретного ключа в `social.near` именно индексированная история и является правильной конечной точкой: сырой `view_state`-follow-up здесь непрактичен, потому что состояние контракта слишком велико. Если нужен точный chain-state на меньшем контракте, тогда и стоит переходить к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). ## Частые задачи @@ -1715,7 +1715,7 @@ curl -s "$KV_BASE_URL/v0/history" \ **Переходите дальше, когда** -- Пользователю нужно точное текущее состояние в цепочке, а не индексированное хранилище. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). +- Пользователю нужно точное текущее состояние в цепочке на контракте, который подходит для прямого raw-state чтения. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) или к собственному read-методу контракта. ### Превратить один точный ключ в историю изменений @@ -1734,7 +1734,7 @@ curl -s "$KV_BASE_URL/v0/history" \ **Переходите дальше, когда** -- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. +- Теперь нужен точный chain-state, а не только индексированная история. На меньших контрактах переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state); в остальных случаях используйте собственный read-метод контракта. ### Проследить записи от одного `predecessor_id` @@ -1778,6 +1778,7 @@ curl -s "$KV_BASE_URL/v0/history" \ - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. - Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. - Путать индексированную историю с точным текущим состоянием в цепочке. +- Обещать raw `view_state`-доказательство на очень больших контрактах вроде `social.near`. - Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. ## Полезные связанные страницы @@ -2138,7 +2139,7 @@ https://testnet.neardata.xyz **Цель** -- Быстро заметить недавний блок, а затем проверить то же самое, когда догонит finality. +- Как можно раньше заметить одно свежее изменение в семействе блоков, а затем подтвердить, какой финализированный блок его догнал. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | @@ -2150,37 +2151,66 @@ https://testnet.neardata.xyz **Что должен включать полезный ответ** -- какое наблюдение по оптимистичному блоку впервые запустило расследование -- когда то же наблюдение стало финализированным +- какой redirect target и какой разрешённый оптимистичный блок впервые запустили расследование +- когда helper для finality догнал его и в какой блок он разрешился - изменил ли точный разбор через RPC интерпретацию -### Shell-сценарий проверки финализированного блока +### Shell-сценарий от оптимистичного сигнала к финализированному подтверждению -Используйте этот сценарий, когда вспомогательный маршрут сам выбирает для вас последний финализированный блок, но следующий шаг всё равно требует точной проверки через RPC. +Используйте этот сценарий, когда нужно сразу заметить свежее изменение в семействе блоков, а затем доказать, какой финализированный блок его догнал, и подтвердить именно эту высоту через RPC. **Что вы делаете** -- Смотрите redirect, который возвращает `GET /v0/last_block/final`. -- Загружаете итоговый документ блока. -- Извлекаете `block.header.height` через `jq`. -- Переиспользуете эту высоту в RPC `block` по высоте. +- Смотрите redirect, который возвращает `GET /v0/last_block/optimistic`. +- Загружаете разрешённый оптимистичный блок и сохраняете его высоту и хеш. +- Смотрите redirect, который возвращает `GET /v0/last_block/final`, и сохраняете финализированный counterpart. +- Сравниваете оптимистичное и финализированное наблюдения, а затем переиспользуете финализированную высоту в RPC `block` по высоте. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz RPC_URL=https://rpc.mainnet.fastnear.com +OPTIMISTIC_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +printf 'Optimistic redirect target: %s\n' "$OPTIMISTIC_LOCATION" + +curl -s "$NEARDATA_BASE_URL$OPTIMISTIC_LOCATION" \ + | tee /tmp/neardata-optimistic-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ | awk 'tolower($1) == "location:" {print $2}' \ | tr -d '\r' )" -printf 'Redirect target: %s\n' "$FINAL_LOCATION" +printf 'Final redirect target: %s\n' "$FINAL_LOCATION" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | tee /tmp/neardata-final-block.json \ | jq '{height: .block.header.height, hash: .block.header.hash}' +jq -n \ + --slurpfile optimistic /tmp/neardata-optimistic-block.json \ + --slurpfile final /tmp/neardata-final-block.json '{ + optimistic: { + height: $optimistic[0].block.header.height, + hash: $optimistic[0].block.header.hash + }, + final: { + height: $final[0].block.header.height, + hash: $final[0].block.header.hash + }, + same_height: ( + $optimistic[0].block.header.height + == $final[0].block.header.height + ) + }' + BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" curl -s "$RPC_URL" \ @@ -2198,7 +2228,7 @@ curl -s "$RPC_URL" \ **Зачем нужен следующий шаг?** -Helper route — самый простой способ опрашивать сценарий «последний финализированный блок». Как только он сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. +Так вы получаете обе стороны истории: самый ранний оптимистичный якорь и более поздний финализированный якорь. Как только helper для finality сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. ## Частые задачи @@ -4155,6 +4185,28 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash - куда на диске должны попасть данные - должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap +### Минимальная команда для optimized mainnet `fast-rpc` + +Используйте этот якорь, когда уже ясно, что нужен высокопроизводительный mainnet RPC-узел, и требуется только минимальная рабочая команда. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash +``` + +### Минимальная команда для стандартного mainnet RPC + +Используйте этот якорь, когда нужен обычный путь восстановления mainnet RPC без optimized-профиля `fast-rpc`. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet bash +``` + ### Shell-сценарий архивного восстановления mainnet Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. @@ -4189,11 +4241,11 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ **Начните здесь** -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), конкретно путь для optimized `fast-rpc`. +- Используйте якорь с optimized mainnet `fast-rpc` выше. **Следующая страница при необходимости** -- Вернитесь к обычному сценарию mainnet RPC, если узел не подходит для optimized profile. +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), если нужно дополнительно настроить `THREADS`, `BWLIMIT` или кастомный `DATA_PATH`. **Остановитесь, когда** @@ -4207,11 +4259,11 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ **Начните здесь** -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) или [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) в зависимости от сети и выберите обычный RPC-сценарий для нужного окружения. +- Используйте якорь со стандартным mainnet RPC выше. **Следующая страница при необходимости** -- Настраивайте `DATA_PATH`, `THREADS` или ограничения по пропускной способности только после того, как понятен стандартный сценарий. +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), если нужно дополнительно настроить `DATA_PATH`, `THREADS` или ограничения по пропускной способности. **Остановитесь, когда** @@ -4225,7 +4277,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ **Начните здесь** -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), раздел архивного режима. +- Используйте архивный walkthrough выше. **Следующая страница при необходимости** @@ -5285,7 +5337,7 @@ jq '{ 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. - 03RPC call_function на роутере показывает, закрепилось ли собственное локальное изменение состояния из первой receipt. + 03Как только эти два представления сходятся на одном и том же разрезе истории, остановитесь. Этот пример держится за сохранённые исторические свидетельства, а не за живой read состояния роутера. **Цель** @@ -5312,26 +5364,24 @@ jq '{ ```mermaid flowchart LR T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] - R --> S["Роутер сохраняет локальное состояние
kicked += late-failure"] R --> D["Detached cross-contract receipt
append(...)"] D --> F["Более поздний сбой
CodeDoesNotExist"] - S -. "состояние из первой receipt всё равно закрепилось" .-> K["kicked() всё ещё содержит late-failure"] + T -. "внешняя транзакция всё равно завершается" .-> X["RPC top-level status
SuccessValue"] ``` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | | Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | | Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | -| Текущее состояние контракта | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `seq-dr.mike.testnet.kicked()` | Показывает, что локальное изменение состояния из первой receipt закрепилось, хотя более поздняя detached-receipt упала | -Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не откатил более раннее изменение состояния роутера. +Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. **Что должен включать полезный ответ** - что подписанная транзакция успешно передала управление в первую router-receipt - что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` - что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` -- что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился +- что RPC всё ещё показывает верхнеуровневый `SuccessValue`, хотя более поздняя detached-receipt упала - одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции #### Shell-сценарий более позднего сбоя receipt @@ -5342,14 +5392,13 @@ flowchart LR - Читаете транзакцию и её таймлайн receipt из индексированного представления. - Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. -- Читаете текущее состояние роутера, чтобы показать: локальный побочный эффект первой receipt закрепился. +- Останавливаетесь, как только эти два сохранённых представления сходятся на одном и том же разрезе истории. ```bash TX_BASE_URL=https://tx.test.fastnear.com RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz SIGNER_ACCOUNT_ID=temp.mike.testnet -ROUTER_ACCOUNT_ID=seq-dr.mike.testnet FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es ``` @@ -5438,36 +5487,11 @@ jq \ # - более поздняя receipt append(...) упала с CodeDoesNotExist ``` -3. Прочитайте текущее состояние роутера и подтвердите, что локальный побочный эффект первой receipt закрепился. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ROUTER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "kicked", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee /tmp/later-receipt-failure-kicked.json >/dev/null - -jq '{ - kicked: (.result.result | implode | fromjson), - contains_late_failure: ((.result.result | implode | fromjson) | index("late-failure") != null) -}' /tmp/later-receipt-failure-kicked.json -``` - -Этот последний read и есть практическое доказательство того, что локальное изменение из первой receipt закрепилось. Более поздняя упавшая receipt не откатила более ранний `kicked.push(...)` внутри роутера. +Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. **Зачем нужен следующий шаг?** -Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. +Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. ### Проследить асинхронную promise-цепочку и доказать порядок callback-ов diff --git a/static/ru/llms.txt b/static/ru/llms.txt index ed0765d..a5ac5f9 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -16,7 +16,7 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и понимания, когда точный RPC follow-up по состоянию вообще практичен. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. - [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index 9e91c08..1b710d3 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -15,7 +15,7 @@ **Цель** -- Быстро заметить недавний блок, а затем проверить то же самое, когда догонит finality. +- Как можно раньше заметить одно свежее изменение в семействе блоков, а затем подтвердить, какой финализированный блок его догнал. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | @@ -27,37 +27,66 @@ **Что должен включать полезный ответ** -- какое наблюдение по оптимистичному блоку впервые запустило расследование -- когда то же наблюдение стало финализированным +- какой redirect target и какой разрешённый оптимистичный блок впервые запустили расследование +- когда helper для finality догнал его и в какой блок он разрешился - изменил ли точный разбор через RPC интерпретацию -### Shell-сценарий проверки финализированного блока +### Shell-сценарий от оптимистичного сигнала к финализированному подтверждению -Используйте этот сценарий, когда вспомогательный маршрут сам выбирает для вас последний финализированный блок, но следующий шаг всё равно требует точной проверки через RPC. +Используйте этот сценарий, когда нужно сразу заметить свежее изменение в семействе блоков, а затем доказать, какой финализированный блок его догнал, и подтвердить именно эту высоту через RPC. **Что вы делаете** -- Смотрите redirect, который возвращает `GET /v0/last_block/final`. -- Загружаете итоговый документ блока. -- Извлекаете `block.header.height` через `jq`. -- Переиспользуете эту высоту в RPC `block` по высоте. +- Смотрите redirect, который возвращает `GET /v0/last_block/optimistic`. +- Загружаете разрешённый оптимистичный блок и сохраняете его высоту и хеш. +- Смотрите redirect, который возвращает `GET /v0/last_block/final`, и сохраняете финализированный counterpart. +- Сравниваете оптимистичное и финализированное наблюдения, а затем переиспользуете финализированную высоту в RPC `block` по высоте. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz RPC_URL=https://rpc.mainnet.fastnear.com +OPTIMISTIC_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +printf 'Optimistic redirect target: %s\n' "$OPTIMISTIC_LOCATION" + +curl -s "$NEARDATA_BASE_URL$OPTIMISTIC_LOCATION" \ + | tee /tmp/neardata-optimistic-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ | awk 'tolower($1) == "location:" {print $2}' \ | tr -d '\r' )" -printf 'Redirect target: %s\n' "$FINAL_LOCATION" +printf 'Final redirect target: %s\n' "$FINAL_LOCATION" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | tee /tmp/neardata-final-block.json \ | jq '{height: .block.header.height, hash: .block.header.hash}' +jq -n \ + --slurpfile optimistic /tmp/neardata-optimistic-block.json \ + --slurpfile final /tmp/neardata-final-block.json '{ + optimistic: { + height: $optimistic[0].block.header.height, + hash: $optimistic[0].block.header.hash + }, + final: { + height: $final[0].block.header.height, + hash: $final[0].block.header.hash + }, + same_height: ( + $optimistic[0].block.header.height + == $final[0].block.header.height + ) + }' + BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" curl -s "$RPC_URL" \ @@ -75,7 +104,7 @@ curl -s "$RPC_URL" \ **Зачем нужен следующий шаг?** -Helper route — самый простой способ опрашивать сценарий «последний финализированный блок». Как только он сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. +Так вы получаете обе стороны истории: самый ранний оптимистичный якорь и более поздний финализированный якорь. Как только helper для finality сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. ## Частые задачи diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index 9e91c08..1b710d3 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -15,7 +15,7 @@ **Цель** -- Быстро заметить недавний блок, а затем проверить то же самое, когда догонит finality. +- Как можно раньше заметить одно свежее изменение в семействе блоков, а затем подтвердить, какой финализированный блок его догнал. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | @@ -27,37 +27,66 @@ **Что должен включать полезный ответ** -- какое наблюдение по оптимистичному блоку впервые запустило расследование -- когда то же наблюдение стало финализированным +- какой redirect target и какой разрешённый оптимистичный блок впервые запустили расследование +- когда helper для finality догнал его и в какой блок он разрешился - изменил ли точный разбор через RPC интерпретацию -### Shell-сценарий проверки финализированного блока +### Shell-сценарий от оптимистичного сигнала к финализированному подтверждению -Используйте этот сценарий, когда вспомогательный маршрут сам выбирает для вас последний финализированный блок, но следующий шаг всё равно требует точной проверки через RPC. +Используйте этот сценарий, когда нужно сразу заметить свежее изменение в семействе блоков, а затем доказать, какой финализированный блок его догнал, и подтвердить именно эту высоту через RPC. **Что вы делаете** -- Смотрите redirect, который возвращает `GET /v0/last_block/final`. -- Загружаете итоговый документ блока. -- Извлекаете `block.header.height` через `jq`. -- Переиспользуете эту высоту в RPC `block` по высоте. +- Смотрите redirect, который возвращает `GET /v0/last_block/optimistic`. +- Загружаете разрешённый оптимистичный блок и сохраняете его высоту и хеш. +- Смотрите redirect, который возвращает `GET /v0/last_block/final`, и сохраняете финализированный counterpart. +- Сравниваете оптимистичное и финализированное наблюдения, а затем переиспользуете финализированную высоту в RPC `block` по высоте. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz RPC_URL=https://rpc.mainnet.fastnear.com +OPTIMISTIC_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +printf 'Optimistic redirect target: %s\n' "$OPTIMISTIC_LOCATION" + +curl -s "$NEARDATA_BASE_URL$OPTIMISTIC_LOCATION" \ + | tee /tmp/neardata-optimistic-block.json \ + | jq '{height: .block.header.height, hash: .block.header.hash}' + FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ | awk 'tolower($1) == "location:" {print $2}' \ | tr -d '\r' )" -printf 'Redirect target: %s\n' "$FINAL_LOCATION" +printf 'Final redirect target: %s\n' "$FINAL_LOCATION" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | tee /tmp/neardata-final-block.json \ | jq '{height: .block.header.height, hash: .block.header.hash}' +jq -n \ + --slurpfile optimistic /tmp/neardata-optimistic-block.json \ + --slurpfile final /tmp/neardata-final-block.json '{ + optimistic: { + height: $optimistic[0].block.header.height, + hash: $optimistic[0].block.header.hash + }, + final: { + height: $final[0].block.header.height, + hash: $final[0].block.header.hash + }, + same_height: ( + $optimistic[0].block.header.height + == $final[0].block.header.height + ) + }' + BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" curl -s "$RPC_URL" \ @@ -75,7 +104,7 @@ curl -s "$RPC_URL" \ **Зачем нужен следующий шаг?** -Helper route — самый простой способ опрашивать сценарий «последний финализированный блок». Как только он сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. +Так вы получаете обе стороны истории: самый ранний оптимистичный якорь и более поздний финализированный якорь. Как только helper для finality сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. ## Частые задачи diff --git a/static/ru/snapshots/examples.md b/static/ru/snapshots/examples.md index 83106a7..650c732 100644 --- a/static/ru/snapshots/examples.md +++ b/static/ru/snapshots/examples.md @@ -32,6 +32,28 @@ - куда на диске должны попасть данные - должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap +### Минимальная команда для optimized mainnet `fast-rpc` + +Используйте этот якорь, когда уже ясно, что нужен высокопроизводительный mainnet RPC-узел, и требуется только минимальная рабочая команда. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash +``` + +### Минимальная команда для стандартного mainnet RPC + +Используйте этот якорь, когда нужен обычный путь восстановления mainnet RPC без optimized-профиля `fast-rpc`. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet bash +``` + ### Shell-сценарий архивного восстановления mainnet Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. @@ -66,11 +88,11 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ **Начните здесь** -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), конкретно путь для optimized `fast-rpc`. +- Используйте якорь с optimized mainnet `fast-rpc` выше. **Следующая страница при необходимости** -- Вернитесь к обычному сценарию mainnet RPC, если узел не подходит для optimized profile. +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), если нужно дополнительно настроить `THREADS`, `BWLIMIT` или кастомный `DATA_PATH`. **Остановитесь, когда** @@ -84,11 +106,11 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ **Начните здесь** -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) или [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) в зависимости от сети и выберите обычный RPC-сценарий для нужного окружения. +- Используйте якорь со стандартным mainnet RPC выше. **Следующая страница при необходимости** -- Настраивайте `DATA_PATH`, `THREADS` или ограничения по пропускной способности только после того, как понятен стандартный сценарий. +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), если нужно дополнительно настроить `DATA_PATH`, `THREADS` или ограничения по пропускной способности. **Остановитесь, когда** @@ -102,7 +124,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ **Начните здесь** -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), раздел архивного режима. +- Используйте архивный walkthrough выше. **Следующая страница при необходимости** diff --git a/static/ru/snapshots/examples/index.md b/static/ru/snapshots/examples/index.md index 83106a7..650c732 100644 --- a/static/ru/snapshots/examples/index.md +++ b/static/ru/snapshots/examples/index.md @@ -32,6 +32,28 @@ - куда на диске должны попасть данные - должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap +### Минимальная команда для optimized mainnet `fast-rpc` + +Используйте этот якорь, когда уже ясно, что нужен высокопроизводительный mainnet RPC-узел, и требуется только минимальная рабочая команда. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash +``` + +### Минимальная команда для стандартного mainnet RPC + +Используйте этот якорь, когда нужен обычный путь восстановления mainnet RPC без optimized-профиля `fast-rpc`. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet bash +``` + ### Shell-сценарий архивного восстановления mainnet Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. @@ -66,11 +88,11 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ **Начните здесь** -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), конкретно путь для optimized `fast-rpc`. +- Используйте якорь с optimized mainnet `fast-rpc` выше. **Следующая страница при необходимости** -- Вернитесь к обычному сценарию mainnet RPC, если узел не подходит для optimized profile. +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), если нужно дополнительно настроить `THREADS`, `BWLIMIT` или кастомный `DATA_PATH`. **Остановитесь, когда** @@ -84,11 +106,11 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ **Начните здесь** -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) или [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) в зависимости от сети и выберите обычный RPC-сценарий для нужного окружения. +- Используйте якорь со стандартным mainnet RPC выше. **Следующая страница при необходимости** -- Настраивайте `DATA_PATH`, `THREADS` или ограничения по пропускной способности только после того, как понятен стандартный сценарий. +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), если нужно дополнительно настроить `DATA_PATH`, `THREADS` или ограничения по пропускной способности. **Остановитесь, когда** @@ -102,7 +124,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ **Начните здесь** -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), раздел архивного режима. +- Используйте архивный walkthrough выше. **Следующая страница при необходимости** diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index 0c24ba4..0b60ca0 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -474,7 +474,7 @@ jq '{ 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. - 03RPC call_function на роутере показывает, закрепилось ли собственное локальное изменение состояния из первой receipt. + 03Как только эти два представления сходятся на одном и том же разрезе истории, остановитесь. Этот пример держится за сохранённые исторические свидетельства, а не за живой read состояния роутера. **Цель** @@ -501,26 +501,24 @@ jq '{ ```mermaid flowchart LR T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] - R --> S["Роутер сохраняет локальное состояние
kicked += late-failure"] R --> D["Detached cross-contract receipt
append(...)"] D --> F["Более поздний сбой
CodeDoesNotExist"] - S -. "состояние из первой receipt всё равно закрепилось" .-> K["kicked() всё ещё содержит late-failure"] + T -. "внешняя транзакция всё равно завершается" .-> X["RPC top-level status
SuccessValue"] ``` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | | Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | | Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | -| Текущее состояние контракта | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `seq-dr.mike.testnet.kicked()` | Показывает, что локальное изменение состояния из первой receipt закрепилось, хотя более поздняя detached-receipt упала | -Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не откатил более раннее изменение состояния роутера. +Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. **Что должен включать полезный ответ** - что подписанная транзакция успешно передала управление в первую router-receipt - что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` - что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` -- что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился +- что RPC всё ещё показывает верхнеуровневый `SuccessValue`, хотя более поздняя detached-receipt упала - одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции #### Shell-сценарий более позднего сбоя receipt @@ -531,14 +529,13 @@ flowchart LR - Читаете транзакцию и её таймлайн receipt из индексированного представления. - Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. -- Читаете текущее состояние роутера, чтобы показать: локальный побочный эффект первой receipt закрепился. +- Останавливаетесь, как только эти два сохранённых представления сходятся на одном и том же разрезе истории. ```bash TX_BASE_URL=https://tx.test.fastnear.com RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz SIGNER_ACCOUNT_ID=temp.mike.testnet -ROUTER_ACCOUNT_ID=seq-dr.mike.testnet FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es ``` @@ -627,36 +624,11 @@ jq \ # - более поздняя receipt append(...) упала с CodeDoesNotExist ``` -3. Прочитайте текущее состояние роутера и подтвердите, что локальный побочный эффект первой receipt закрепился. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ROUTER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "kicked", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee /tmp/later-receipt-failure-kicked.json >/dev/null - -jq '{ - kicked: (.result.result | implode | fromjson), - contains_late_failure: ((.result.result | implode | fromjson) | index("late-failure") != null) -}' /tmp/later-receipt-failure-kicked.json -``` - -Этот последний read и есть практическое доказательство того, что локальное изменение из первой receipt закрепилось. Более поздняя упавшая receipt не откатила более ранний `kicked.push(...)` внутри роутера. +Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. **Зачем нужен следующий шаг?** -Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. +Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. ### Проследить асинхронную promise-цепочку и доказать порядок callback-ов diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index 0c24ba4..0b60ca0 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -474,7 +474,7 @@ jq '{ 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. - 03RPC call_function на роутере показывает, закрепилось ли собственное локальное изменение состояния из первой receipt. + 03Как только эти два представления сходятся на одном и том же разрезе истории, остановитесь. Этот пример держится за сохранённые исторические свидетельства, а не за живой read состояния роутера. **Цель** @@ -501,26 +501,24 @@ jq '{ ```mermaid flowchart LR T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] - R --> S["Роутер сохраняет локальное состояние
kicked += late-failure"] R --> D["Detached cross-contract receipt
append(...)"] D --> F["Более поздний сбой
CodeDoesNotExist"] - S -. "состояние из первой receipt всё равно закрепилось" .-> K["kicked() всё ещё содержит late-failure"] + T -. "внешняя транзакция всё равно завершается" .-> X["RPC top-level status
SuccessValue"] ``` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | | Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | | Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | -| Текущее состояние контракта | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `seq-dr.mike.testnet.kicked()` | Показывает, что локальное изменение состояния из первой receipt закрепилось, хотя более поздняя detached-receipt упала | -Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не откатил более раннее изменение состояния роутера. +Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. **Что должен включать полезный ответ** - что подписанная транзакция успешно передала управление в первую router-receipt - что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` - что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` -- что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился +- что RPC всё ещё показывает верхнеуровневый `SuccessValue`, хотя более поздняя detached-receipt упала - одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции #### Shell-сценарий более позднего сбоя receipt @@ -531,14 +529,13 @@ flowchart LR - Читаете транзакцию и её таймлайн receipt из индексированного представления. - Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. -- Читаете текущее состояние роутера, чтобы показать: локальный побочный эффект первой receipt закрепился. +- Останавливаетесь, как только эти два сохранённых представления сходятся на одном и том же разрезе истории. ```bash TX_BASE_URL=https://tx.test.fastnear.com RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz SIGNER_ACCOUNT_ID=temp.mike.testnet -ROUTER_ACCOUNT_ID=seq-dr.mike.testnet FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es ``` @@ -627,36 +624,11 @@ jq \ # - более поздняя receipt append(...) упала с CodeDoesNotExist ``` -3. Прочитайте текущее состояние роутера и подтвердите, что локальный побочный эффект первой receipt закрепился. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ROUTER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "kicked", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee /tmp/later-receipt-failure-kicked.json >/dev/null - -jq '{ - kicked: (.result.result | implode | fromjson), - contains_late_failure: ((.result.result | implode | fromjson) | index("late-failure") != null) -}' /tmp/later-receipt-failure-kicked.json -``` - -Этот последний read и есть практическое доказательство того, что локальное изменение из первой receipt закрепилось. Более поздняя упавшая receipt не откатила более ранний `kicked.push(...)` внутри роутера. +Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. **Зачем нужен следующий шаг?** -Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. +Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. ### Проследить асинхронную promise-цепочку и доказать порядок callback-ов From 5163eeca876ed55d05c0873b2e9b4c7f56708e08 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 21:51:18 -0700 Subject: [PATCH 11/35] docs: verify kv examples with live testnet flow --- docs/fastdata/kv/examples.md | 204 +++++++++++++---- docs/fastdata/kv/index.md | 12 +- .../current/fastdata/kv/examples.md | 204 +++++++++++++---- .../current/fastdata/kv/index.md | 12 +- static/ru/fastdata/kv.md | 12 +- static/ru/fastdata/kv/examples.md | 202 +++++++++++++---- static/ru/fastdata/kv/examples/index.md | 202 +++++++++++++---- static/ru/fastdata/kv/index.md | 12 +- static/ru/guides/llms.txt | 2 +- static/ru/llms-full.txt | 212 +++++++++++++----- static/ru/llms.txt | 2 +- 11 files changed, 808 insertions(+), 268 deletions(-) diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index ba90d82..2275c7d 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: KV FastData Examples -description: Plain-language workflows for checking exact storage keys, following indexed write history, and deciding when exact RPC state follow-up is practical. +description: Plain-language workflows for verifying exact FastData keys, following exact-key history, and bridging indexed rows back to the originating transaction. displayed_sidebar: kvFastDataSidebar page_actions: - markdown @@ -10,65 +10,120 @@ page_actions: ## Worked investigation -### Check one contract key, then follow its history +### Write one testnet FastData row, then verify the exact indexed keys -Use this investigation when one contract storage key looks suspicious and you want the latest indexed value and the write history for that same key, and need to know whether a raw RPC state check is practical for this contract. +Use this investigation when you want to prove exactly which FastData rows one write produced, confirm the exact keyed history for one of those rows, and then bridge the indexed row back to the originating transaction.
Strategy -

Start with one exact key, widen only into that key’s history, then decide whether raw RPC state is realistic for this contract.

+

Create one controlled FastData write, verify the exact key rows it emitted, then hydrate the transaction that produced them.

-

01get-latest-key gives the newest indexed row for the exact key you care about.

-

02get-history-key or history-by-key shows how that same key changed over time.

-

03RPC view_state is only the final step on contracts whose raw state is practical to query directly.

+

01near call __fastdata_kv creates one controlled testnet write from your own predecessor account.

+

02get-latest-key and get-history-key verify the exact FastData rows that call emitted.

+

03latest-by-predecessor with metadata plus POST /v0/transactions bridges those indexed rows back to the originating call.

**Goal** -- Explain what this storage key looks like in the index, how it changed, and whether an exact RPC state check is practical here. +- Prove which exact FastData rows a write produced and show how to trace those rows back to the transaction that created them. | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | -| Latest indexed value | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Fetch the newest indexed row for the exact key first | Gives the fastest narrow answer before widening into history | -| Indexed key history | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) or [`history-by-key`](/fastdata/kv/history-by-key) | Pull the same key’s change history over time | Shows whether the current value is stable, recent, or part of a suspicious sequence | -| Broader write pattern | KV FastData [`latest-by-account`](/fastdata/kv/latest-by-account) or [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Check the account or predecessor if the one key is only part of a larger pattern | Helps explain whether the key changed by itself or as part of a bigger write set | -| Contract-specific exact state check | RPC [`view_state`](/rpc/contract/view-state) | Use it only on contracts whose raw state can actually be queried directly | Keeps you from promising a raw-state confirmation that the target contract cannot serve | +| Controlled write | `near` CLI | Submit one testnet `__fastdata_kv` call with a unique value | Gives you a row you can verify immediately instead of relying on someone else's old data | +| Exact indexed row | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Read the exact `value` row, then the exact `key` row, under one predecessor and contract | Proves the precise FastData rows currently indexed for this write | +| Exact keyed history | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) | Pull the exact-key history for the same `value` row | Shows whether that exact row changed again after the write | +| Broader write pattern + metadata | KV FastData [`latest-by-predecessor`](/fastdata/kv/latest-by-predecessor) | List the latest rows for the same predecessor with `include_metadata: true` | Recovers both emitted rows plus the `tx_hash` and `receipt_id` that produced them | +| Transaction hydration | Transactions API [`POST /v0/transactions`](/tx/transactions) | Hydrate the recovered `tx_hash` and decode the `FunctionCall.args` payload | Proves which method call created the indexed FastData rows | **What a useful answer should include** -- the exact key and contract scope investigated -- the latest indexed value and what changed in history -- whether an exact raw-state follow-up is practical for this contract +- the exact `current_account_id`, `predecessor_id`, and `key` investigated +- the latest indexed row and the exact-key history for that same row +- the `tx_hash` or `receipt_id` that produced the row, if provenance matters +- whether the question is still about FastData rows or has widened into contract-specific on-chain state -### Exact key history shell walkthrough +### Verified testnet shell walkthrough -Use this when one fully qualified key is already known and you want to move cleanly from “what is the latest indexed row?” to “how did this exact key get here?” +Use this when you have a testnet account configured in `near` CLI and want one reproducible write that you can verify end to end. **What you're doing** -- Read one latest indexed key with the exact contract, predecessor, and key path. -- Extract the exact `key` with `jq`. -- Reuse that key in `POST /v0/history` to pull the write history for the same key. +- Write one fresh FastData entry to `kv.gork-agent.testnet`. +- Wait for KV FastData to index that transaction. +- Read the exact `value` row and the exact `key` row that the contract emitted. +- Pull the exact-key history for the `value` row. +- Widen to predecessor scope with metadata so you recover the indexed `tx_hash`. +- Hydrate that transaction and decode the original `__fastdata_kv` call args. ```bash -KV_BASE_URL=https://kv.main.fastnear.com -CURRENT_ACCOUNT_ID=social.near -PREDECESSOR_ID=james.near -KEY='graph/follow/sleet.near' - -ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" +KV_BASE_URL=https://kv.test.fastnear.com +TX_BASE_URL=https://tx.test.fastnear.com +CURRENT_ACCOUNT_ID=kv.gork-agent.testnet +SIGNER_ID=YOUR_TESTNET_ACCOUNT +PREDECESSOR_ID="$SIGNER_ID" +FASTDATA_FIELD=verification +FASTDATA_VALUE="verify-$(date -u +%Y%m%dT%H%M%SZ)" + +near call "$CURRENT_ACCOUNT_ID" __fastdata_kv \ + "$(jq -nc --arg key "$FASTDATA_FIELD" --arg value "$FASTDATA_VALUE" '{key: $key, value: $value}')" \ + --accountId "$SIGNER_ID" \ + --networkId testnet \ + --gas 30000000000000 \ + | tee /tmp/kv-fastdata-call.txt + +CLI_TX_HASH="$( + awk '/Transaction Id/{print $3}' /tmp/kv-fastdata-call.txt +)" -EXACT_KEY="$( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | tee /tmp/kv-latest.json \ - | jq -r '.entries[0].key' +ATTEMPTS=0 +until curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ + -H 'content-type: application/json' \ + --data '{"include_metadata": true, "limit": 20}' \ + | tee /tmp/kv-predecessor-latest.json \ + | jq -e --arg tx_hash "$CLI_TX_HASH" ' + .entries + | map(select(.tx_hash == $tx_hash)) + | length > 0 + ' >/dev/null +do + ATTEMPTS=$((ATTEMPTS + 1)) + if [ "$ATTEMPTS" -ge 30 ]; then + echo "Timed out waiting for KV FastData to index $CLI_TX_HASH" >&2 + exit 1 + fi + sleep 2 +done + +INDEXED_TX_HASH="$( + jq -r --arg tx_hash "$CLI_TX_HASH" ' + first(.entries[] | select(.tx_hash == $tx_hash) | .tx_hash) + ' /tmp/kv-predecessor-latest.json )" +test "$CLI_TX_HASH" = "$INDEXED_TX_HASH" \ + && echo "CLI tx hash matches indexed metadata" + +jq --arg tx_hash "$CLI_TX_HASH" '{ + tx_hashes: ([.entries[] | select(.tx_hash == $tx_hash) | .tx_hash] | unique), + receipt_ids: ([.entries[] | select(.tx_hash == $tx_hash) | .receipt_id] | unique), + entries: [ + .entries[] + | select(.tx_hash == $tx_hash) + | { + block_height, + key, + value, + tx_hash, + receipt_id + } + ] + }' /tmp/kv-predecessor-latest.json + jq '{ - latest: ( + latest_value_row: ( .entries[0] | { current_account_id, @@ -78,32 +133,63 @@ jq '{ value } ) -}' /tmp/kv-latest.json +}' <( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" +) -curl -s "$KV_BASE_URL/v0/history" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ + | jq '{ + latest_key_row: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' + +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ | jq '{ page_token, entries: [ .entries[] | { - current_account_id, - predecessor_id, block_height, + key, value } ] }' + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$INDEXED_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + args: ( + .transactions[0].transaction.actions[0].FunctionCall.args + | @base64d + | fromjson + ), + first_receipt_id: .transactions[0].execution_outcome.outcome.receipt_ids[0] + }' ``` **Why this next step?** -The first lookup answers “what do we have right now?” Reusing the exact `key` in `POST /v0/history` answers “how did it get here?” For this exact `social.near` key, the indexed history is the correct finish: a raw `view_state` follow-up is not practical here because the contract state is too large. If you need exact chain-state confirmation on a smaller contract, that is the moment to pivot to [View State](/rpc/contract/view-state). +This contract emits two FastData rows from one call: a `key` row that contains the logical field name you passed in, and a `value` row that contains the actual value. The exact-key routes prove those rows directly. The predecessor-scoped lookup is the bridge back to provenance, because it gives you the `tx_hash` and `receipt_id` that created those rows. Hydrating that transaction proves the indexed rows came from one `__fastdata_kv` call with the same decoded args. + +That is also the important boundary for this surface: FastData rows are indexed FastData output, not a guarantee that raw RPC `view_state` will expose the same keys. Because this is an indexed surface, a fresh write may take a short moment to appear; wait until the indexed `tx_hash` shows up before treating the latest rows as final. If the user's question changes from “which FastData rows were emitted?” to “what does this contract's canonical on-chain state look like?”, move to the contract's own read method or [View State](/rpc/contract/view-state) only when you independently know the storage layout you are asking for. ## Common jobs -### Look up one exact key right now +### Look up one exact FastData key right now **Start here** @@ -115,18 +201,18 @@ The first lookup answers “what do we have right now?” Reusing the exact `key **Stop when** -- The latest indexed row already answers the storage question. +- The latest indexed row already answers the FastData question. **Switch when** -- The user needs exact current chain state on a contract small enough for direct raw-state inspection. Move to [View State](/rpc/contract/view-state) or the contract's own read method. +- The user is no longer asking about indexed FastData rows. Move to the contract's own read method or [View State](/rpc/contract/view-state) only if you know the raw-state layout you need. -### Turn one exact key into a change history +### Turn one exact FastData key into a change history **Start here** -- [GET History by Exact Key](/fastdata/kv/get-history-key) for path-based history lookup. -- [History by Key](/fastdata/kv/history-by-key) when the fully qualified key route is the better fit. +- [GET History by Exact Key](/fastdata/kv/get-history-key) for path-based history under one contract and one predecessor. +- [History by Key](/fastdata/kv/history-by-key) only when you intentionally want the same key string across all contracts and predecessors. **Next page if needed** @@ -138,18 +224,19 @@ The first lookup answers “what do we have right now?” Reusing the exact `key **Switch when** -- The user now needs exact current chain state, not just indexed history. On smaller contracts, move to [View State](/rpc/contract/view-state); otherwise use the contract's own read method. +- The user now needs canonical contract state, not just indexed FastData history. Use the contract's own read method or [View State](/rpc/contract/view-state) only when the raw-state shape is known. ### Trace writes from one predecessor **Start here** - [All by Predecessor](/fastdata/kv/all-by-predecessor) for latest rows across contracts touched by one predecessor. +- [Latest by Predecessor](/fastdata/kv/latest-by-predecessor) when you need the rows for one contract and one predecessor, optionally with metadata. - [History by Predecessor](/fastdata/kv/history-by-predecessor) when you need the write history over time. **Next page if needed** -- Narrow to an exact key if one row becomes the real focus. +- Narrow to an exact key if one row becomes the real focus, or widen to [Transactions by Hash](/tx/transactions) if provenance becomes the real question. **Stop when** @@ -159,6 +246,25 @@ The first lookup answers “what do we have right now?” Reusing the exact `key - The user stops asking about indexed writes and starts asking about the current chain state. +### Bridge one FastData row back to its transaction + +**Start here** + +- [Latest by Predecessor](/fastdata/kv/latest-by-predecessor) with `include_metadata: true` to recover `tx_hash` and `receipt_id`. +- [Transactions by Hash](/tx/transactions) to hydrate the originating call and decode its args. + +**Next page if needed** + +- Move to [Receipt by Id](/tx/receipt) if the next question is about one downstream receipt rather than the original call. + +**Stop when** + +- You can explain which call produced the indexed FastData row. + +**Switch when** + +- The user needs canonical execution semantics or exact executor-level status. Move to RPC transaction status. + ### Batch-check several known keys **Start here** @@ -180,9 +286,11 @@ The first lookup answers “what do we have right now?” Reusing the exact `key ## Common mistakes - Starting with broad account or predecessor scans when an exact key is already known. +- Mixing up [GET History by Exact Key](/fastdata/kv/get-history-key) with [History by Key](/fastdata/kv/history-by-key). The former stays inside one contract and predecessor; the latter searches the same key string globally. - Using KV FastData when the user really wants balances or holdings. -- Confusing indexed history with exact current chain state. -- Promising a raw `view_state` proof on very large contracts such as `social.near`. +- Confusing indexed FastData rows with exact canonical contract state. +- Assuming a FastData key is directly queryable through raw RPC `view_state`. +- Assuming a fresh write will be indexed synchronously with chain inclusion. - Reusing pagination tokens or changing filters mid-scan. ## Related guides diff --git a/docs/fastdata/kv/index.md b/docs/fastdata/kv/index.md index 7fa6a52..72465f6 100644 --- a/docs/fastdata/kv/index.md +++ b/docs/fastdata/kv/index.md @@ -10,7 +10,7 @@ page_actions: # KV FastData API -KV FastData API is the indexed key-value family. Use it when you already know the contract, account, predecessor, or key scope you want to inspect and you want indexed rows without building your own storage indexing layer. +KV FastData API is the indexed key-value family. Use it when you already know the contract, account, predecessor, or key scope you want to inspect and you want indexed rows without building your own FastData indexing layer. ## Base URLs @@ -27,7 +27,7 @@ https://kv.test.fastnear.com - you want latest indexed state for one key or a known key family - you want historical key changes by account, key, or predecessor - you want batch lookups for known exact keys -- you are debugging contract storage in indexed form +- you are debugging indexed FastData rows emitted by a contract or predecessor ## Do not start here when @@ -53,18 +53,18 @@ Use [FastNear API](/api) for higher-level account views, [NEAR Data API](/nearda ## Need a workflow? -Use [KV FastData Examples](/fastdata/kv/examples) for plain-language flows like exact-key lookups, key-history investigation, predecessor-scoped inspection, and canonical RPC follow-up. +Use [KV FastData Examples](/fastdata/kv/examples) for plain-language flows like exact-key lookups, exact-key history, predecessor-scoped inspection, and transaction bridging. ## Default workflow 1. Pick the narrowest scope that matches the user's question. 2. Stay within KV FastData first when the question is still about indexed key-value data. 3. Use the latest endpoints for current indexed views and the history endpoints only when the user needs change-over-time answers. -4. Stop once the indexed rows already answer the storage question. +4. Stop once the indexed rows already answer the FastData question. ## Auth and availability -- Public indexed storage reads often work without a key. +- Public indexed FastData reads often work without a key. - If you standardize on one FastNear API key across FastNear surfaces, reuse the same header or query-param shape here too. - Add `?network=testnet` to switch the page to the testnet backend where supported. - List responses omit `page_token` when there are no more results. @@ -75,7 +75,7 @@ Use [KV FastData Examples](/fastdata/kv/examples) for plain-language flows like - the user needs canonical contract-state semantics - the indexed storage view is the wrong abstraction for the question -When that happens, widen to [View State](/rpc/contract/view-state) in [RPC Reference](/rpc). +When that happens, widen to [View State](/rpc/contract/view-state) in [RPC Reference](/rpc) or to the contract's own read method. Do not assume one FastData key maps directly to one raw `view_state` key. ## Troubleshooting diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index d09a046..7ffb910 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: "Примеры KV FastData" -description: "Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и понимания, когда точный RPC follow-up по состоянию вообще практичен." +description: "Пошаговые сценарии для проверки точных FastData-ключей, чтения истории точного ключа и привязки индексированных строк к исходной транзакции." displayed_sidebar: kvFastDataSidebar page_actions: - markdown @@ -10,65 +10,120 @@ page_actions: ## Готовое расследование -### Проверить один ключ контракта, а затем пройти по его истории +### Сделать одну testnet-запись FastData и проверить точные индексированные ключи -Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и понять, практичен ли для этого контракта сырой RPC-чек состояния. +Используйте это расследование, когда нужно доказать, какие именно строки FastData породила одна запись, подтвердить точную историю одного из этих ключей, а затем привязать индексированную строку к исходной транзакции.
Стратегия -

Начните с одного точного ключа, расширяйтесь только до его истории, а затем решите, реалистичен ли для этого контракта сырой RPC state-read.

+

Сделайте одну управляемую запись FastData, проверьте точные строки ключей, которые она эмитировала, а затем гидратируйте транзакцию, которая их породила.

-

01get-latest-key даёт самую новую индексированную запись по точному ключу.

-

02get-history-key или history-by-key показывают, как тот же ключ менялся во времени.

-

03RPC view_state — это финальный шаг только для контрактов, чьё сырое состояние вообще реально запросить напрямую.

+

01near call __fastdata_kv создаёт одну управляемую запись на testnet от вашего собственного аккаунта-предшественника.

+

02get-latest-key и get-history-key проверяют точные строки FastData, которые породил этот вызов.

+

03latest-by-predecessor с метаданными плюс POST /v0/transactions привязывают эти индексированные строки к исходному вызову.

**Цель** -- Объяснить, как этот ключ выглядит в индексе, как он менялся и практичен ли здесь точный RPC-чек состояния. +- Доказать, какие именно строки FastData породила запись, и показать, как проследить эти строки обратно до создавшей их транзакции. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Последнее индексированное значение | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | -| История индексированного ключа | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) или [`history-by-key`](/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | -| Более широкий паттерн записей | KV FastData [`latest-by-account`](/fastdata/kv/latest-by-account) или [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Смотрим аккаунт или предшественника, если один ключ — только часть более широкой картины | Помогает понять, менялся ли ключ сам по себе или как часть большего набора записей | -| Контрактно-специфичная точная проверка состояния | RPC [`view_state`](/rpc/contract/view-state) | Используем только на контрактах, у которых сырое состояние действительно можно читать напрямую | Не даёт пообещать raw-state-подтверждение там, где сам контракт этого не позволяет | +| Управляемая запись | CLI `near` | Отправляем один testnet-вызов `__fastdata_kv` с уникальным значением | Даёт запись, которую можно сразу проверить, не полагаясь на чужие старые данные | +| Точная индексированная строка | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Читаем точную строку `value`, а затем точную строку `key`, внутри одного предшественника и контракта | Доказывает точные строки FastData, которые сейчас индексированы для этой записи | +| История точного ключа | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) | Забираем историю того же точного ключа `value` | Показывает, менялась ли эта точная строка снова после записи | +| Более широкий паттерн + метаданные | KV FastData [`latest-by-predecessor`](/fastdata/kv/latest-by-predecessor) | Список последних строк для того же предшественника с `include_metadata: true` | Возвращает обе эмитированные строки вместе с `tx_hash` и `receipt_id`, которые их породили | +| Гидратация транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем payload из `FunctionCall.args` | Доказывает, какой именно вызов создал индексированные строки FastData | **Что должен включать полезный ответ** -- какой именно ключ и какая область контракта были исследованы -- как выглядит последнее индексированное значение и какие изменения видны в истории -- практичен ли для этого контракта точный raw-state follow-up +- какие именно `current_account_id`, `predecessor_id` и `key` были исследованы +- как выглядит последняя индексированная строка и история этого же точного ключа +- какой `tx_hash` или `receipt_id` породил строку, если важна provenance-цепочка +- остаётся ли вопрос про строки FastData или уже перешёл к контрактно-специфичному состоянию в цепочке -### Shell-сценарий истории точного ключа +### Проверенный testnet shell-сценарий -Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» +Используйте этот сценарий, когда у вас уже настроен testnet-аккаунт в `near` CLI и нужен один воспроизводимый сценарий записи, который можно проверить от начала до конца. **Что вы делаете** -- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. -- Извлекаете точный `key` через `jq`. -- Переиспользуете этот ключ в `POST /v0/history`, чтобы получить историю записей по тому же ключу. +- Пишете одну свежую запись FastData в `kv.gork-agent.testnet`. +- Ждёте, пока KV FastData проиндексирует эту транзакцию. +- Читаете точную строку `value` и точную строку `key`, которые эмитировал контракт. +- Забираете историю точного ключа `value`. +- Расширяетесь до области предшественника с метаданными, чтобы получить индексированный `tx_hash`. +- Гидратируете эту транзакцию и декодируете исходные аргументы вызова `__fastdata_kv`. ```bash -KV_BASE_URL=https://kv.main.fastnear.com -CURRENT_ACCOUNT_ID=social.near -PREDECESSOR_ID=james.near -KEY='graph/follow/sleet.near' - -ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" +KV_BASE_URL=https://kv.test.fastnear.com +TX_BASE_URL=https://tx.test.fastnear.com +CURRENT_ACCOUNT_ID=kv.gork-agent.testnet +SIGNER_ID=YOUR_TESTNET_ACCOUNT +PREDECESSOR_ID="$SIGNER_ID" +FASTDATA_FIELD=verification +FASTDATA_VALUE="verify-$(date -u +%Y%m%dT%H%M%SZ)" + +near call "$CURRENT_ACCOUNT_ID" __fastdata_kv \ + "$(jq -nc --arg key "$FASTDATA_FIELD" --arg value "$FASTDATA_VALUE" '{key: $key, value: $value}')" \ + --accountId "$SIGNER_ID" \ + --networkId testnet \ + --gas 30000000000000 \ + | tee /tmp/kv-fastdata-call.txt + +CLI_TX_HASH="$( + awk '/Transaction Id/{print $3}' /tmp/kv-fastdata-call.txt +)" -EXACT_KEY="$( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | tee /tmp/kv-latest.json \ - | jq -r '.entries[0].key' +ATTEMPTS=0 +until curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ + -H 'content-type: application/json' \ + --data '{"include_metadata": true, "limit": 20}' \ + | tee /tmp/kv-predecessor-latest.json \ + | jq -e --arg tx_hash "$CLI_TX_HASH" ' + .entries + | map(select(.tx_hash == $tx_hash)) + | length > 0 + ' >/dev/null +do + ATTEMPTS=$((ATTEMPTS + 1)) + if [ "$ATTEMPTS" -ge 30 ]; then + echo "Timed out waiting for KV FastData to index $CLI_TX_HASH" >&2 + exit 1 + fi + sleep 2 +done + +INDEXED_TX_HASH="$( + jq -r --arg tx_hash "$CLI_TX_HASH" ' + first(.entries[] | select(.tx_hash == $tx_hash) | .tx_hash) + ' /tmp/kv-predecessor-latest.json )" +test "$CLI_TX_HASH" = "$INDEXED_TX_HASH" \ + && echo "CLI tx hash matches indexed metadata" + +jq --arg tx_hash "$CLI_TX_HASH" '{ + tx_hashes: ([.entries[] | select(.tx_hash == $tx_hash) | .tx_hash] | unique), + receipt_ids: ([.entries[] | select(.tx_hash == $tx_hash) | .receipt_id] | unique), + entries: [ + .entries[] + | select(.tx_hash == $tx_hash) + | { + block_height, + key, + value, + tx_hash, + receipt_id + } + ] + }' /tmp/kv-predecessor-latest.json + jq '{ - latest: ( + latest_value_row: ( .entries[0] | { current_account_id, @@ -78,32 +133,63 @@ jq '{ value } ) -}' /tmp/kv-latest.json +}' <( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" +) -curl -s "$KV_BASE_URL/v0/history" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ + | jq '{ + latest_key_row: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' + +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ | jq '{ page_token, entries: [ .entries[] | { - current_account_id, - predecessor_id, block_height, + key, value } ] }' + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$INDEXED_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + args: ( + .transactions[0].transaction.actions[0].FunctionCall.args + | @base64d + | fromjson + ), + first_receipt_id: .transactions[0].execution_outcome.outcome.receipt_ids[0] + }' ``` **Зачем нужен следующий шаг?** -Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Для этого конкретного ключа в `social.near` именно индексированная история и является правильной конечной точкой: сырой `view_state`-follow-up здесь непрактичен, потому что состояние контракта слишком велико. Если нужен точный chain-state на меньшем контракте, тогда и стоит переходить к [View State](/rpc/contract/view-state). +Этот контракт эмитирует две строки FastData из одного вызова: строку `key`, в которой лежит логическое имя поля, и строку `value`, в которой лежит само значение. Маршруты точного ключа напрямую доказывают наличие этих строк. Lookup по предшественнику — это мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, породившие эти строки. Гидратация транзакции доказывает, что индексированные строки возникли из одного вызова `__fastdata_kv` с теми же декодированными аргументами. + +Именно здесь проходит важная граница этой поверхности: строки FastData — это индексированный вывод FastData, а не обещание, что сырое RPC `view_state` покажет те же ключи. Поскольку это индексированная поверхность, свежая запись может появиться не мгновенно; дождитесь, пока в индексе появится ваш `tx_hash`, прежде чем считать latest-строки окончательными. Если вопрос пользователя меняется с «какие строки FastData были эмитированы?» на «как выглядит каноническое on-chain-состояние контракта?», переходите к собственному read-методу контракта или к [View State](/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную вам layout-структуру хранилища. ## Частые задачи -### Посмотреть один точный ключ прямо сейчас +### Посмотреть один точный ключ FastData прямо сейчас **Начните здесь** @@ -115,18 +201,18 @@ curl -s "$KV_BASE_URL/v0/history" \ **Остановитесь, когда** -- Последняя индексированная запись уже отвечает на вопрос о хранилище. +- Последняя индексированная запись уже отвечает на вопрос по FastData. **Переходите дальше, когда** -- Пользователю нужно точное текущее состояние в цепочке на контракте, который подходит для прямого raw-state чтения. Переходите к [View State](/rpc/contract/view-state) или к собственному read-методу контракта. +- Пользователя больше не интересуют индексированные строки FastData. Переходите к собственному read-методу контракта или к [View State](/rpc/contract/view-state) только если знаете, какой raw-state layout нужен. -### Превратить один точный ключ в историю изменений +### Превратить один точный ключ FastData в историю изменений **Начните здесь** -- [История по точному ключу](/fastdata/kv/get-history-key) для поиска истории по пути. -- [History by Key](/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. +- [История по точному ключу](/fastdata/kv/get-history-key) для path-based истории внутри одного контракта и одного предшественника. +- [History by Key](/fastdata/kv/history-by-key) только тогда, когда вы специально хотите искать один и тот же текст ключа по всем контрактам и предшественникам. **Следующая страница при необходимости** @@ -138,18 +224,19 @@ curl -s "$KV_BASE_URL/v0/history" \ **Переходите дальше, когда** -- Теперь нужен точный chain-state, а не только индексированная история. На меньших контрактах переходите к [View State](/rpc/contract/view-state); в остальных случаях используйте собственный read-метод контракта. +- Теперь нужно каноническое состояние контракта, а не только индексированная история FastData. Используйте собственный read-метод контракта или [View State](/rpc/contract/view-state) только если форма raw-state уже известна. ### Проследить записи от одного `predecessor_id` **Начните здесь** - [Всё по `predecessor_id`](/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. +- [Последнее по `predecessor_id`](/fastdata/kv/latest-by-predecessor), когда нужны строки для одного контракта и одного предшественника, при необходимости вместе с метаданными. - [История по `predecessor_id`](/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. **Следующая страница при необходимости** -- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. +- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования, или переходите к [Transactions by Hash](/tx/transactions), если главным становится provenance-вопрос. **Остановитесь, когда** @@ -159,6 +246,25 @@ curl -s "$KV_BASE_URL/v0/history" \ - Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. +### Привязать одну строку FastData обратно к транзакции + +**Начните здесь** + +- [Последнее по `predecessor_id`](/fastdata/kv/latest-by-predecessor) с `include_metadata: true`, чтобы получить `tx_hash` и `receipt_id`. +- [Transactions by Hash](/tx/transactions), чтобы гидратировать исходный вызов и декодировать его аргументы. + +**Следующая страница при необходимости** + +- Переходите к [Receipt by Id](/tx/receipt), если следующий вопрос уже не про исходный вызов, а про конкретный downstream receipt. + +**Остановитесь, когда** + +- Уже можно объяснить, какой вызов породил индексированную строку FastData. + +**Переходите дальше, когда** + +- Пользователю нужна каноническая семантика исполнения или точный статус на уровне executor. Тогда переходите к RPC transaction status. + ### Пакетно проверить несколько известных ключей **Начните здесь** @@ -180,9 +286,11 @@ curl -s "$KV_BASE_URL/v0/history" \ ## Частые ошибки - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. +- Путать [Историю по точному ключу](/fastdata/kv/get-history-key) с [History by Key](/fastdata/kv/history-by-key). Первый маршрут остаётся внутри одного контракта и предшественника, второй ищет одинаковый текст ключа глобально. - Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. -- Путать индексированную историю с точным текущим состоянием в цепочке. -- Обещать raw `view_state`-доказательство на очень больших контрактах вроде `social.near`. +- Путать индексированные строки FastData с точным каноническим состоянием контракта. +- Предполагать, что ключ FastData можно напрямую запросить через raw RPC `view_state`. +- Предполагать, что свежая запись будет проиндексирована синхронно с включением в блокчейн. - Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. ## Полезные связанные страницы diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md index 9025cdf..90dfaf4 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md @@ -10,7 +10,7 @@ page_actions: # KV FastData API -KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации хранилища. +KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации FastData. ## Базовые URL @@ -27,7 +27,7 @@ https://kv.test.fastnear.com - нужно последнее индексированное состояние по одному ключу или известному семейству ключей - нужна история изменений по аккаунту, ключу или `predecessor_id` - нужны пакетные поиски по уже известным точным ключам -- идёт отладка хранилища контракта в индексированном виде +- идёт отладка индексированных строк FastData, эмитированных контрактом или предшественником ## Не стартуйте здесь, когда @@ -53,18 +53,18 @@ https://kv.test.fastnear.com ## Нужен сценарий? -Используйте [примеры KV FastData](/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. +Используйте [примеры KV FastData](/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории точного ключа, анализа по `predecessor_id` и привязки к транзакции. ## Рабочий цикл по умолчанию 1. Выберите самую узкую область, подходящую под вопрос пользователя. 2. Оставайтесь внутри KV FastData, пока вопрос остаётся про индексированные данные «ключ–значение». 3. Используйте эндпоинты «последнего значения» для текущих индексированных представлений, а исторические эндпоинты — только когда пользователю нужны ответы с изменением во времени. -4. Остановитесь, как только индексированных строк уже достаточно для ответа на вопрос о хранилище. +4. Остановитесь, как только индексированных строк уже достаточно для ответа на FastData-вопрос. ## Аутентификация и доступность -- Публичные индексированные чтения хранилища часто работают и без ключа. +- Публичные индексированные чтения FastData часто работают и без ключа. - Если вы стандартизируете один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. - Добавьте `?network=testnet`, чтобы переключить страницу на testnet-бэкенд там, где это поддерживается. - В ответах со списками поле `page_token` отсутствует, когда новых результатов больше нет. @@ -75,7 +75,7 @@ https://kv.test.fastnear.com - пользователю нужна каноническая семантика состояния контракта - индексированное представление хранилища — неподходящая абстракция для вопроса -Тогда расширяйтесь на [Просмотр состояния контракта](/rpc/contract/view-state) в [Справочнике RPC](/rpc). +Тогда расширяйтесь на [Просмотр состояния контракта](/rpc/contract/view-state) в [Справочнике RPC](/rpc) или на собственный read-метод контракта. Не предполагайте, что один ключ FastData напрямую соответствует одному raw-ключу из `view_state`. ## Устранение неполадок diff --git a/static/ru/fastdata/kv.md b/static/ru/fastdata/kv.md index af02a0e..91089bc 100644 --- a/static/ru/fastdata/kv.md +++ b/static/ru/fastdata/kv.md @@ -2,7 +2,7 @@ # KV FastData API -KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации хранилища. +KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации FastData. ## Базовые URL @@ -19,7 +19,7 @@ https://kv.test.fastnear.com - нужно последнее индексированное состояние по одному ключу или известному семейству ключей - нужна история изменений по аккаунту, ключу или `predecessor_id` - нужны пакетные поиски по уже известным точным ключам -- идёт отладка хранилища контракта в индексированном виде +- идёт отладка индексированных строк FastData, эмитированных контрактом или предшественником ## Не стартуйте здесь, когда @@ -45,18 +45,18 @@ https://kv.test.fastnear.com ## Нужен сценарий? -Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. +Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории точного ключа, анализа по `predecessor_id` и привязки к транзакции. ## Рабочий цикл по умолчанию 1. Выберите самую узкую область, подходящую под вопрос пользователя. 2. Оставайтесь внутри KV FastData, пока вопрос остаётся про индексированные данные «ключ–значение». 3. Используйте эндпоинты «последнего значения» для текущих индексированных представлений, а исторические эндпоинты — только когда пользователю нужны ответы с изменением во времени. -4. Остановитесь, как только индексированных строк уже достаточно для ответа на вопрос о хранилище. +4. Остановитесь, как только индексированных строк уже достаточно для ответа на FastData-вопрос. ## Аутентификация и доступность -- Публичные индексированные чтения хранилища часто работают и без ключа. +- Публичные индексированные чтения FastData часто работают и без ключа. - Если вы стандартизируете один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. - Добавьте `?network=testnet`, чтобы переключить страницу на testnet-бэкенд там, где это поддерживается. - В ответах со списками поле `page_token` отсутствует, когда новых результатов больше нет. @@ -67,7 +67,7 @@ https://kv.test.fastnear.com - пользователю нужна каноническая семантика состояния контракта - индексированное представление хранилища — неподходящая абстракция для вопроса -Тогда расширяйтесь на [Просмотр состояния контракта](https://docs.fastnear.com/ru/rpc/contract/view-state) в [Справочнике RPC](https://docs.fastnear.com/ru/rpc). +Тогда расширяйтесь на [Просмотр состояния контракта](https://docs.fastnear.com/ru/rpc/contract/view-state) в [Справочнике RPC](https://docs.fastnear.com/ru/rpc) или на собственный read-метод контракта. Не предполагайте, что один ключ FastData напрямую соответствует одному raw-ключу из `view_state`. ## Устранение неполадок diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index 5e561fe..4f1f8c0 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -2,60 +2,115 @@ ## Готовое расследование -### Проверить один ключ контракта, а затем пройти по его истории +### Сделать одну testnet-запись FastData и проверить точные индексированные ключи -Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и понять, практичен ли для этого контракта сырой RPC-чек состояния. +Используйте это расследование, когда нужно доказать, какие именно строки FastData породила одна запись, подтвердить точную историю одного из этих ключей, а затем привязать индексированную строку к исходной транзакции. Стратегия - Начните с одного точного ключа, расширяйтесь только до его истории, а затем решите, реалистичен ли для этого контракта сырой RPC state-read. + Сделайте одну управляемую запись FastData, проверьте точные строки ключей, которые она эмитировала, а затем гидратируйте транзакцию, которая их породила. - 01get-latest-key даёт самую новую индексированную запись по точному ключу. - 02get-history-key или history-by-key показывают, как тот же ключ менялся во времени. - 03RPC view_state — это финальный шаг только для контрактов, чьё сырое состояние вообще реально запросить напрямую. + 01near call __fastdata_kv создаёт одну управляемую запись на testnet от вашего собственного аккаунта-предшественника. + 02get-latest-key и get-history-key проверяют точные строки FastData, которые породил этот вызов. + 03latest-by-predecessor с метаданными плюс POST /v0/transactions привязывают эти индексированные строки к исходному вызову. **Цель** -- Объяснить, как этот ключ выглядит в индексе, как он менялся и практичен ли здесь точный RPC-чек состояния. +- Доказать, какие именно строки FastData породила запись, и показать, как проследить эти строки обратно до создавшей их транзакции. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | -| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | -| Более широкий паттерн записей | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Смотрим аккаунт или предшественника, если один ключ — только часть более широкой картины | Помогает понять, менялся ли ключ сам по себе или как часть большего набора записей | -| Контрактно-специфичная точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Используем только на контрактах, у которых сырое состояние действительно можно читать напрямую | Не даёт пообещать raw-state-подтверждение там, где сам контракт этого не позволяет | +| Управляемая запись | CLI `near` | Отправляем один testnet-вызов `__fastdata_kv` с уникальным значением | Даёт запись, которую можно сразу проверить, не полагаясь на чужие старые данные | +| Точная индексированная строка | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Читаем точную строку `value`, а затем точную строку `key`, внутри одного предшественника и контракта | Доказывает точные строки FastData, которые сейчас индексированы для этой записи | +| История точного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) | Забираем историю того же точного ключа `value` | Показывает, менялась ли эта точная строка снова после записи | +| Более широкий паттерн + метаданные | KV FastData [`latest-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) | Список последних строк для того же предшественника с `include_metadata: true` | Возвращает обе эмитированные строки вместе с `tx_hash` и `receipt_id`, которые их породили | +| Гидратация транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем payload из `FunctionCall.args` | Доказывает, какой именно вызов создал индексированные строки FastData | **Что должен включать полезный ответ** -- какой именно ключ и какая область контракта были исследованы -- как выглядит последнее индексированное значение и какие изменения видны в истории -- практичен ли для этого контракта точный raw-state follow-up +- какие именно `current_account_id`, `predecessor_id` и `key` были исследованы +- как выглядит последняя индексированная строка и история этого же точного ключа +- какой `tx_hash` или `receipt_id` породил строку, если важна provenance-цепочка +- остаётся ли вопрос про строки FastData или уже перешёл к контрактно-специфичному состоянию в цепочке -### Shell-сценарий истории точного ключа +### Проверенный testnet shell-сценарий -Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» +Используйте этот сценарий, когда у вас уже настроен testnet-аккаунт в `near` CLI и нужен один воспроизводимый сценарий записи, который можно проверить от начала до конца. **Что вы делаете** -- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. -- Извлекаете точный `key` через `jq`. -- Переиспользуете этот ключ в `POST /v0/history`, чтобы получить историю записей по тому же ключу. +- Пишете одну свежую запись FastData в `kv.gork-agent.testnet`. +- Ждёте, пока KV FastData проиндексирует эту транзакцию. +- Читаете точную строку `value` и точную строку `key`, которые эмитировал контракт. +- Забираете историю точного ключа `value`. +- Расширяетесь до области предшественника с метаданными, чтобы получить индексированный `tx_hash`. +- Гидратируете эту транзакцию и декодируете исходные аргументы вызова `__fastdata_kv`. ```bash -KV_BASE_URL=https://kv.main.fastnear.com -CURRENT_ACCOUNT_ID=social.near -PREDECESSOR_ID=james.near -KEY='graph/follow/sleet.near' - -ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" +KV_BASE_URL=https://kv.test.fastnear.com +TX_BASE_URL=https://tx.test.fastnear.com +CURRENT_ACCOUNT_ID=kv.gork-agent.testnet +SIGNER_ID=YOUR_TESTNET_ACCOUNT +PREDECESSOR_ID="$SIGNER_ID" +FASTDATA_FIELD=verification +FASTDATA_VALUE="verify-$(date -u +%Y%m%dT%H%M%SZ)" + +near call "$CURRENT_ACCOUNT_ID" __fastdata_kv \ + "$(jq -nc --arg key "$FASTDATA_FIELD" --arg value "$FASTDATA_VALUE" '{key: $key, value: $value}')" \ + --accountId "$SIGNER_ID" \ + --networkId testnet \ + --gas 30000000000000 \ + | tee /tmp/kv-fastdata-call.txt + +CLI_TX_HASH="$( + awk '/Transaction Id/{print $3}' /tmp/kv-fastdata-call.txt +)" -EXACT_KEY="$( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | tee /tmp/kv-latest.json \ - | jq -r '.entries[0].key' +ATTEMPTS=0 +until curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ + -H 'content-type: application/json' \ + --data '{"include_metadata": true, "limit": 20}' \ + | tee /tmp/kv-predecessor-latest.json \ + | jq -e --arg tx_hash "$CLI_TX_HASH" ' + .entries + | map(select(.tx_hash == $tx_hash)) + | length > 0 + ' >/dev/null +do + ATTEMPTS=$((ATTEMPTS + 1)) + if [ "$ATTEMPTS" -ge 30 ]; then + echo "Timed out waiting for KV FastData to index $CLI_TX_HASH" >&2 + exit 1 + fi + sleep 2 +done + +INDEXED_TX_HASH="$( + jq -r --arg tx_hash "$CLI_TX_HASH" ' + first(.entries[] | select(.tx_hash == $tx_hash) | .tx_hash) + ' /tmp/kv-predecessor-latest.json )" +test "$CLI_TX_HASH" = "$INDEXED_TX_HASH" \ + && echo "CLI tx hash matches indexed metadata" + +jq --arg tx_hash "$CLI_TX_HASH" '{ + tx_hashes: ([.entries[] | select(.tx_hash == $tx_hash) | .tx_hash] | unique), + receipt_ids: ([.entries[] | select(.tx_hash == $tx_hash) | .receipt_id] | unique), + entries: [ + .entries[] + | select(.tx_hash == $tx_hash) + | { + block_height, + key, + value, + tx_hash, + receipt_id + } + ] + }' /tmp/kv-predecessor-latest.json + jq '{ - latest: ( + latest_value_row: ( .entries[0] | { current_account_id, @@ -65,32 +120,63 @@ jq '{ value } ) -}' /tmp/kv-latest.json +}' <( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" +) -curl -s "$KV_BASE_URL/v0/history" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ + | jq '{ + latest_key_row: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' + +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ | jq '{ page_token, entries: [ .entries[] | { - current_account_id, - predecessor_id, block_height, + key, value } ] }' + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$INDEXED_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + args: ( + .transactions[0].transaction.actions[0].FunctionCall.args + | @base64d + | fromjson + ), + first_receipt_id: .transactions[0].execution_outcome.outcome.receipt_ids[0] + }' ``` **Зачем нужен следующий шаг?** -Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Для этого конкретного ключа в `social.near` именно индексированная история и является правильной конечной точкой: сырой `view_state`-follow-up здесь непрактичен, потому что состояние контракта слишком велико. Если нужен точный chain-state на меньшем контракте, тогда и стоит переходить к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). +Этот контракт эмитирует две строки FastData из одного вызова: строку `key`, в которой лежит логическое имя поля, и строку `value`, в которой лежит само значение. Маршруты точного ключа напрямую доказывают наличие этих строк. Lookup по предшественнику — это мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, породившие эти строки. Гидратация транзакции доказывает, что индексированные строки возникли из одного вызова `__fastdata_kv` с теми же декодированными аргументами. + +Именно здесь проходит важная граница этой поверхности: строки FastData — это индексированный вывод FastData, а не обещание, что сырое RPC `view_state` покажет те же ключи. Поскольку это индексированная поверхность, свежая запись может появиться не мгновенно; дождитесь, пока в индексе появится ваш `tx_hash`, прежде чем считать latest-строки окончательными. Если вопрос пользователя меняется с «какие строки FastData были эмитированы?» на «как выглядит каноническое on-chain-состояние контракта?», переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную вам layout-структуру хранилища. ## Частые задачи -### Посмотреть один точный ключ прямо сейчас +### Посмотреть один точный ключ FastData прямо сейчас **Начните здесь** @@ -102,18 +188,18 @@ curl -s "$KV_BASE_URL/v0/history" \ **Остановитесь, когда** -- Последняя индексированная запись уже отвечает на вопрос о хранилище. +- Последняя индексированная запись уже отвечает на вопрос по FastData. **Переходите дальше, когда** -- Пользователю нужно точное текущее состояние в цепочке на контракте, который подходит для прямого raw-state чтения. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) или к собственному read-методу контракта. +- Пользователя больше не интересуют индексированные строки FastData. Переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только если знаете, какой raw-state layout нужен. -### Превратить один точный ключ в историю изменений +### Превратить один точный ключ FastData в историю изменений **Начните здесь** -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для поиска истории по пути. -- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для path-based истории внутри одного контракта и одного предшественника. +- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) только тогда, когда вы специально хотите искать один и тот же текст ключа по всем контрактам и предшественникам. **Следующая страница при необходимости** @@ -125,18 +211,19 @@ curl -s "$KV_BASE_URL/v0/history" \ **Переходите дальше, когда** -- Теперь нужен точный chain-state, а не только индексированная история. На меньших контрактах переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state); в остальных случаях используйте собственный read-метод контракта. +- Теперь нужно каноническое состояние контракта, а не только индексированная история FastData. Используйте собственный read-метод контракта или [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только если форма raw-state уже известна. ### Проследить записи от одного `predecessor_id` **Начните здесь** - [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. +- [Последнее по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor), когда нужны строки для одного контракта и одного предшественника, при необходимости вместе с метаданными. - [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. **Следующая страница при необходимости** -- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. +- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования, или переходите к [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если главным становится provenance-вопрос. **Остановитесь, когда** @@ -146,6 +233,25 @@ curl -s "$KV_BASE_URL/v0/history" \ - Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. +### Привязать одну строку FastData обратно к транзакции + +**Начните здесь** + +- [Последнее по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) с `include_metadata: true`, чтобы получить `tx_hash` и `receipt_id`. +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы гидратировать исходный вызов и декодировать его аргументы. + +**Следующая страница при необходимости** + +- Переходите к [Receipt by Id](https://docs.fastnear.com/ru/tx/receipt), если следующий вопрос уже не про исходный вызов, а про конкретный downstream receipt. + +**Остановитесь, когда** + +- Уже можно объяснить, какой вызов породил индексированную строку FastData. + +**Переходите дальше, когда** + +- Пользователю нужна каноническая семантика исполнения или точный статус на уровне executor. Тогда переходите к RPC transaction status. + ### Пакетно проверить несколько известных ключей **Начните здесь** @@ -167,9 +273,11 @@ curl -s "$KV_BASE_URL/v0/history" \ ## Частые ошибки - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. +- Путать [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) с [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key). Первый маршрут остаётся внутри одного контракта и предшественника, второй ищет одинаковый текст ключа глобально. - Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. -- Путать индексированную историю с точным текущим состоянием в цепочке. -- Обещать raw `view_state`-доказательство на очень больших контрактах вроде `social.near`. +- Путать индексированные строки FastData с точным каноническим состоянием контракта. +- Предполагать, что ключ FastData можно напрямую запросить через raw RPC `view_state`. +- Предполагать, что свежая запись будет проиндексирована синхронно с включением в блокчейн. - Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. ## Полезные связанные страницы diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index 5e561fe..4f1f8c0 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -2,60 +2,115 @@ ## Готовое расследование -### Проверить один ключ контракта, а затем пройти по его истории +### Сделать одну testnet-запись FastData и проверить точные индексированные ключи -Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и понять, практичен ли для этого контракта сырой RPC-чек состояния. +Используйте это расследование, когда нужно доказать, какие именно строки FastData породила одна запись, подтвердить точную историю одного из этих ключей, а затем привязать индексированную строку к исходной транзакции. Стратегия - Начните с одного точного ключа, расширяйтесь только до его истории, а затем решите, реалистичен ли для этого контракта сырой RPC state-read. + Сделайте одну управляемую запись FastData, проверьте точные строки ключей, которые она эмитировала, а затем гидратируйте транзакцию, которая их породила. - 01get-latest-key даёт самую новую индексированную запись по точному ключу. - 02get-history-key или history-by-key показывают, как тот же ключ менялся во времени. - 03RPC view_state — это финальный шаг только для контрактов, чьё сырое состояние вообще реально запросить напрямую. + 01near call __fastdata_kv создаёт одну управляемую запись на testnet от вашего собственного аккаунта-предшественника. + 02get-latest-key и get-history-key проверяют точные строки FastData, которые породил этот вызов. + 03latest-by-predecessor с метаданными плюс POST /v0/transactions привязывают эти индексированные строки к исходному вызову. **Цель** -- Объяснить, как этот ключ выглядит в индексе, как он менялся и практичен ли здесь точный RPC-чек состояния. +- Доказать, какие именно строки FastData породила запись, и показать, как проследить эти строки обратно до создавшей их транзакции. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | -| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | -| Более широкий паттерн записей | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Смотрим аккаунт или предшественника, если один ключ — только часть более широкой картины | Помогает понять, менялся ли ключ сам по себе или как часть большего набора записей | -| Контрактно-специфичная точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Используем только на контрактах, у которых сырое состояние действительно можно читать напрямую | Не даёт пообещать raw-state-подтверждение там, где сам контракт этого не позволяет | +| Управляемая запись | CLI `near` | Отправляем один testnet-вызов `__fastdata_kv` с уникальным значением | Даёт запись, которую можно сразу проверить, не полагаясь на чужие старые данные | +| Точная индексированная строка | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Читаем точную строку `value`, а затем точную строку `key`, внутри одного предшественника и контракта | Доказывает точные строки FastData, которые сейчас индексированы для этой записи | +| История точного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) | Забираем историю того же точного ключа `value` | Показывает, менялась ли эта точная строка снова после записи | +| Более широкий паттерн + метаданные | KV FastData [`latest-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) | Список последних строк для того же предшественника с `include_metadata: true` | Возвращает обе эмитированные строки вместе с `tx_hash` и `receipt_id`, которые их породили | +| Гидратация транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем payload из `FunctionCall.args` | Доказывает, какой именно вызов создал индексированные строки FastData | **Что должен включать полезный ответ** -- какой именно ключ и какая область контракта были исследованы -- как выглядит последнее индексированное значение и какие изменения видны в истории -- практичен ли для этого контракта точный raw-state follow-up +- какие именно `current_account_id`, `predecessor_id` и `key` были исследованы +- как выглядит последняя индексированная строка и история этого же точного ключа +- какой `tx_hash` или `receipt_id` породил строку, если важна provenance-цепочка +- остаётся ли вопрос про строки FastData или уже перешёл к контрактно-специфичному состоянию в цепочке -### Shell-сценарий истории точного ключа +### Проверенный testnet shell-сценарий -Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» +Используйте этот сценарий, когда у вас уже настроен testnet-аккаунт в `near` CLI и нужен один воспроизводимый сценарий записи, который можно проверить от начала до конца. **Что вы делаете** -- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. -- Извлекаете точный `key` через `jq`. -- Переиспользуете этот ключ в `POST /v0/history`, чтобы получить историю записей по тому же ключу. +- Пишете одну свежую запись FastData в `kv.gork-agent.testnet`. +- Ждёте, пока KV FastData проиндексирует эту транзакцию. +- Читаете точную строку `value` и точную строку `key`, которые эмитировал контракт. +- Забираете историю точного ключа `value`. +- Расширяетесь до области предшественника с метаданными, чтобы получить индексированный `tx_hash`. +- Гидратируете эту транзакцию и декодируете исходные аргументы вызова `__fastdata_kv`. ```bash -KV_BASE_URL=https://kv.main.fastnear.com -CURRENT_ACCOUNT_ID=social.near -PREDECESSOR_ID=james.near -KEY='graph/follow/sleet.near' - -ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" +KV_BASE_URL=https://kv.test.fastnear.com +TX_BASE_URL=https://tx.test.fastnear.com +CURRENT_ACCOUNT_ID=kv.gork-agent.testnet +SIGNER_ID=YOUR_TESTNET_ACCOUNT +PREDECESSOR_ID="$SIGNER_ID" +FASTDATA_FIELD=verification +FASTDATA_VALUE="verify-$(date -u +%Y%m%dT%H%M%SZ)" + +near call "$CURRENT_ACCOUNT_ID" __fastdata_kv \ + "$(jq -nc --arg key "$FASTDATA_FIELD" --arg value "$FASTDATA_VALUE" '{key: $key, value: $value}')" \ + --accountId "$SIGNER_ID" \ + --networkId testnet \ + --gas 30000000000000 \ + | tee /tmp/kv-fastdata-call.txt + +CLI_TX_HASH="$( + awk '/Transaction Id/{print $3}' /tmp/kv-fastdata-call.txt +)" -EXACT_KEY="$( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | tee /tmp/kv-latest.json \ - | jq -r '.entries[0].key' +ATTEMPTS=0 +until curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ + -H 'content-type: application/json' \ + --data '{"include_metadata": true, "limit": 20}' \ + | tee /tmp/kv-predecessor-latest.json \ + | jq -e --arg tx_hash "$CLI_TX_HASH" ' + .entries + | map(select(.tx_hash == $tx_hash)) + | length > 0 + ' >/dev/null +do + ATTEMPTS=$((ATTEMPTS + 1)) + if [ "$ATTEMPTS" -ge 30 ]; then + echo "Timed out waiting for KV FastData to index $CLI_TX_HASH" >&2 + exit 1 + fi + sleep 2 +done + +INDEXED_TX_HASH="$( + jq -r --arg tx_hash "$CLI_TX_HASH" ' + first(.entries[] | select(.tx_hash == $tx_hash) | .tx_hash) + ' /tmp/kv-predecessor-latest.json )" +test "$CLI_TX_HASH" = "$INDEXED_TX_HASH" \ + && echo "CLI tx hash matches indexed metadata" + +jq --arg tx_hash "$CLI_TX_HASH" '{ + tx_hashes: ([.entries[] | select(.tx_hash == $tx_hash) | .tx_hash] | unique), + receipt_ids: ([.entries[] | select(.tx_hash == $tx_hash) | .receipt_id] | unique), + entries: [ + .entries[] + | select(.tx_hash == $tx_hash) + | { + block_height, + key, + value, + tx_hash, + receipt_id + } + ] + }' /tmp/kv-predecessor-latest.json + jq '{ - latest: ( + latest_value_row: ( .entries[0] | { current_account_id, @@ -65,32 +120,63 @@ jq '{ value } ) -}' /tmp/kv-latest.json +}' <( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" +) -curl -s "$KV_BASE_URL/v0/history" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ + | jq '{ + latest_key_row: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' + +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ | jq '{ page_token, entries: [ .entries[] | { - current_account_id, - predecessor_id, block_height, + key, value } ] }' + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$INDEXED_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + args: ( + .transactions[0].transaction.actions[0].FunctionCall.args + | @base64d + | fromjson + ), + first_receipt_id: .transactions[0].execution_outcome.outcome.receipt_ids[0] + }' ``` **Зачем нужен следующий шаг?** -Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Для этого конкретного ключа в `social.near` именно индексированная история и является правильной конечной точкой: сырой `view_state`-follow-up здесь непрактичен, потому что состояние контракта слишком велико. Если нужен точный chain-state на меньшем контракте, тогда и стоит переходить к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). +Этот контракт эмитирует две строки FastData из одного вызова: строку `key`, в которой лежит логическое имя поля, и строку `value`, в которой лежит само значение. Маршруты точного ключа напрямую доказывают наличие этих строк. Lookup по предшественнику — это мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, породившие эти строки. Гидратация транзакции доказывает, что индексированные строки возникли из одного вызова `__fastdata_kv` с теми же декодированными аргументами. + +Именно здесь проходит важная граница этой поверхности: строки FastData — это индексированный вывод FastData, а не обещание, что сырое RPC `view_state` покажет те же ключи. Поскольку это индексированная поверхность, свежая запись может появиться не мгновенно; дождитесь, пока в индексе появится ваш `tx_hash`, прежде чем считать latest-строки окончательными. Если вопрос пользователя меняется с «какие строки FastData были эмитированы?» на «как выглядит каноническое on-chain-состояние контракта?», переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную вам layout-структуру хранилища. ## Частые задачи -### Посмотреть один точный ключ прямо сейчас +### Посмотреть один точный ключ FastData прямо сейчас **Начните здесь** @@ -102,18 +188,18 @@ curl -s "$KV_BASE_URL/v0/history" \ **Остановитесь, когда** -- Последняя индексированная запись уже отвечает на вопрос о хранилище. +- Последняя индексированная запись уже отвечает на вопрос по FastData. **Переходите дальше, когда** -- Пользователю нужно точное текущее состояние в цепочке на контракте, который подходит для прямого raw-state чтения. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) или к собственному read-методу контракта. +- Пользователя больше не интересуют индексированные строки FastData. Переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только если знаете, какой raw-state layout нужен. -### Превратить один точный ключ в историю изменений +### Превратить один точный ключ FastData в историю изменений **Начните здесь** -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для поиска истории по пути. -- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для path-based истории внутри одного контракта и одного предшественника. +- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) только тогда, когда вы специально хотите искать один и тот же текст ключа по всем контрактам и предшественникам. **Следующая страница при необходимости** @@ -125,18 +211,19 @@ curl -s "$KV_BASE_URL/v0/history" \ **Переходите дальше, когда** -- Теперь нужен точный chain-state, а не только индексированная история. На меньших контрактах переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state); в остальных случаях используйте собственный read-метод контракта. +- Теперь нужно каноническое состояние контракта, а не только индексированная история FastData. Используйте собственный read-метод контракта или [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только если форма raw-state уже известна. ### Проследить записи от одного `predecessor_id` **Начните здесь** - [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. +- [Последнее по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor), когда нужны строки для одного контракта и одного предшественника, при необходимости вместе с метаданными. - [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. **Следующая страница при необходимости** -- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. +- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования, или переходите к [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если главным становится provenance-вопрос. **Остановитесь, когда** @@ -146,6 +233,25 @@ curl -s "$KV_BASE_URL/v0/history" \ - Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. +### Привязать одну строку FastData обратно к транзакции + +**Начните здесь** + +- [Последнее по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) с `include_metadata: true`, чтобы получить `tx_hash` и `receipt_id`. +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы гидратировать исходный вызов и декодировать его аргументы. + +**Следующая страница при необходимости** + +- Переходите к [Receipt by Id](https://docs.fastnear.com/ru/tx/receipt), если следующий вопрос уже не про исходный вызов, а про конкретный downstream receipt. + +**Остановитесь, когда** + +- Уже можно объяснить, какой вызов породил индексированную строку FastData. + +**Переходите дальше, когда** + +- Пользователю нужна каноническая семантика исполнения или точный статус на уровне executor. Тогда переходите к RPC transaction status. + ### Пакетно проверить несколько известных ключей **Начните здесь** @@ -167,9 +273,11 @@ curl -s "$KV_BASE_URL/v0/history" \ ## Частые ошибки - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. +- Путать [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) с [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key). Первый маршрут остаётся внутри одного контракта и предшественника, второй ищет одинаковый текст ключа глобально. - Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. -- Путать индексированную историю с точным текущим состоянием в цепочке. -- Обещать raw `view_state`-доказательство на очень больших контрактах вроде `social.near`. +- Путать индексированные строки FastData с точным каноническим состоянием контракта. +- Предполагать, что ключ FastData можно напрямую запросить через raw RPC `view_state`. +- Предполагать, что свежая запись будет проиндексирована синхронно с включением в блокчейн. - Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. ## Полезные связанные страницы diff --git a/static/ru/fastdata/kv/index.md b/static/ru/fastdata/kv/index.md index af02a0e..91089bc 100644 --- a/static/ru/fastdata/kv/index.md +++ b/static/ru/fastdata/kv/index.md @@ -2,7 +2,7 @@ # KV FastData API -KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации хранилища. +KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации FastData. ## Базовые URL @@ -19,7 +19,7 @@ https://kv.test.fastnear.com - нужно последнее индексированное состояние по одному ключу или известному семейству ключей - нужна история изменений по аккаунту, ключу или `predecessor_id` - нужны пакетные поиски по уже известным точным ключам -- идёт отладка хранилища контракта в индексированном виде +- идёт отладка индексированных строк FastData, эмитированных контрактом или предшественником ## Не стартуйте здесь, когда @@ -45,18 +45,18 @@ https://kv.test.fastnear.com ## Нужен сценарий? -Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. +Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории точного ключа, анализа по `predecessor_id` и привязки к транзакции. ## Рабочий цикл по умолчанию 1. Выберите самую узкую область, подходящую под вопрос пользователя. 2. Оставайтесь внутри KV FastData, пока вопрос остаётся про индексированные данные «ключ–значение». 3. Используйте эндпоинты «последнего значения» для текущих индексированных представлений, а исторические эндпоинты — только когда пользователю нужны ответы с изменением во времени. -4. Остановитесь, как только индексированных строк уже достаточно для ответа на вопрос о хранилище. +4. Остановитесь, как только индексированных строк уже достаточно для ответа на FastData-вопрос. ## Аутентификация и доступность -- Публичные индексированные чтения хранилища часто работают и без ключа. +- Публичные индексированные чтения FastData часто работают и без ключа. - Если вы стандартизируете один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. - Добавьте `?network=testnet`, чтобы переключить страницу на testnet-бэкенд там, где это поддерживается. - В ответах со списками поле `page_token` отсутствует, когда новых результатов больше нет. @@ -67,7 +67,7 @@ https://kv.test.fastnear.com - пользователю нужна каноническая семантика состояния контракта - индексированное представление хранилища — неподходящая абстракция для вопроса -Тогда расширяйтесь на [Просмотр состояния контракта](https://docs.fastnear.com/ru/rpc/contract/view-state) в [Справочнике RPC](https://docs.fastnear.com/ru/rpc). +Тогда расширяйтесь на [Просмотр состояния контракта](https://docs.fastnear.com/ru/rpc/contract/view-state) в [Справочнике RPC](https://docs.fastnear.com/ru/rpc) или на собственный read-метод контракта. Не предполагайте, что один ключ FastData напрямую соответствует одному raw-ключу из `view_state`. ## Устранение неполадок diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index f04e8ad..30f0d57 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -13,7 +13,7 @@ - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и понимания, когда точный RPC follow-up по состоянию вообще практичен. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных FastData-ключей, чтения истории точного ключа и привязки индексированных строк к исходной транзакции. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. - [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index c831b5b..59b8c11 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -1523,7 +1523,7 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" \ # KV FastData API -KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации хранилища. +KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации FastData. ## Базовые URL @@ -1540,7 +1540,7 @@ https://kv.test.fastnear.com - нужно последнее индексированное состояние по одному ключу или известному семейству ключей - нужна история изменений по аккаунту, ключу или `predecessor_id` - нужны пакетные поиски по уже известным точным ключам -- идёт отладка хранилища контракта в индексированном виде +- идёт отладка индексированных строк FastData, эмитированных контрактом или предшественником ## Не стартуйте здесь, когда @@ -1566,18 +1566,18 @@ https://kv.test.fastnear.com ## Нужен сценарий? -Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. +Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории точного ключа, анализа по `predecessor_id` и привязки к транзакции. ## Рабочий цикл по умолчанию 1. Выберите самую узкую область, подходящую под вопрос пользователя. 2. Оставайтесь внутри KV FastData, пока вопрос остаётся про индексированные данные «ключ–значение». 3. Используйте эндпоинты «последнего значения» для текущих индексированных представлений, а исторические эндпоинты — только когда пользователю нужны ответы с изменением во времени. -4. Остановитесь, как только индексированных строк уже достаточно для ответа на вопрос о хранилище. +4. Остановитесь, как только индексированных строк уже достаточно для ответа на FastData-вопрос. ## Аутентификация и доступность -- Публичные индексированные чтения хранилища часто работают и без ключа. +- Публичные индексированные чтения FastData часто работают и без ключа. - Если вы стандартизируете один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. - Добавьте `?network=testnet`, чтобы переключить страницу на testnet-бэкенд там, где это поддерживается. - В ответах со списками поле `page_token` отсутствует, когда новых результатов больше нет. @@ -1588,7 +1588,7 @@ https://kv.test.fastnear.com - пользователю нужна каноническая семантика состояния контракта - индексированное представление хранилища — неподходящая абстракция для вопроса -Тогда расширяйтесь на [Просмотр состояния контракта](https://docs.fastnear.com/ru/rpc/contract/view-state) в [Справочнике RPC](https://docs.fastnear.com/ru/rpc). +Тогда расширяйтесь на [Просмотр состояния контракта](https://docs.fastnear.com/ru/rpc/contract/view-state) в [Справочнике RPC](https://docs.fastnear.com/ru/rpc) или на собственный read-метод контракта. Не предполагайте, что один ключ FastData напрямую соответствует одному raw-ключу из `view_state`. ## Устранение неполадок @@ -1611,60 +1611,115 @@ https://kv.test.fastnear.com ## Готовое расследование -### Проверить один ключ контракта, а затем пройти по его истории +### Сделать одну testnet-запись FastData и проверить точные индексированные ключи -Используйте это расследование, когда один ключ хранилища контракта выглядит подозрительно и вы хотите увидеть его последнее индексированное значение, историю записей по тому же ключу и понять, практичен ли для этого контракта сырой RPC-чек состояния. +Используйте это расследование, когда нужно доказать, какие именно строки FastData породила одна запись, подтвердить точную историю одного из этих ключей, а затем привязать индексированную строку к исходной транзакции. Стратегия - Начните с одного точного ключа, расширяйтесь только до его истории, а затем решите, реалистичен ли для этого контракта сырой RPC state-read. + Сделайте одну управляемую запись FastData, проверьте точные строки ключей, которые она эмитировала, а затем гидратируйте транзакцию, которая их породила. - 01get-latest-key даёт самую новую индексированную запись по точному ключу. - 02get-history-key или history-by-key показывают, как тот же ключ менялся во времени. - 03RPC view_state — это финальный шаг только для контрактов, чьё сырое состояние вообще реально запросить напрямую. + 01near call __fastdata_kv создаёт одну управляемую запись на testnet от вашего собственного аккаунта-предшественника. + 02get-latest-key и get-history-key проверяют точные строки FastData, которые породил этот вызов. + 03latest-by-predecessor с метаданными плюс POST /v0/transactions привязывают эти индексированные строки к исходному вызову. **Цель** -- Объяснить, как этот ключ выглядит в индексе, как он менялся и практичен ли здесь точный RPC-чек состояния. +- Доказать, какие именно строки FastData породила запись, и показать, как проследить эти строки обратно до создавшей их транзакции. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Последнее индексированное значение | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Сначала получаем последнюю индексированную запись по точному ключу | Даёт самый быстрый узкий ответ до перехода к истории | -| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-key`](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) | Забираем историю изменений того же ключа во времени | Показывает, стабильно ли текущее значение, насколько оно недавнее и не входит ли в подозрительную последовательность | -| Более широкий паттерн записей | KV FastData [`latest-by-account`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-account) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Смотрим аккаунт или предшественника, если один ключ — только часть более широкой картины | Помогает понять, менялся ли ключ сам по себе или как часть большего набора записей | -| Контрактно-специфичная точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Используем только на контрактах, у которых сырое состояние действительно можно читать напрямую | Не даёт пообещать raw-state-подтверждение там, где сам контракт этого не позволяет | +| Управляемая запись | CLI `near` | Отправляем один testnet-вызов `__fastdata_kv` с уникальным значением | Даёт запись, которую можно сразу проверить, не полагаясь на чужие старые данные | +| Точная индексированная строка | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Читаем точную строку `value`, а затем точную строку `key`, внутри одного предшественника и контракта | Доказывает точные строки FastData, которые сейчас индексированы для этой записи | +| История точного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) | Забираем историю того же точного ключа `value` | Показывает, менялась ли эта точная строка снова после записи | +| Более широкий паттерн + метаданные | KV FastData [`latest-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) | Список последних строк для того же предшественника с `include_metadata: true` | Возвращает обе эмитированные строки вместе с `tx_hash` и `receipt_id`, которые их породили | +| Гидратация транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем payload из `FunctionCall.args` | Доказывает, какой именно вызов создал индексированные строки FastData | **Что должен включать полезный ответ** -- какой именно ключ и какая область контракта были исследованы -- как выглядит последнее индексированное значение и какие изменения видны в истории -- практичен ли для этого контракта точный raw-state follow-up +- какие именно `current_account_id`, `predecessor_id` и `key` были исследованы +- как выглядит последняя индексированная строка и история этого же точного ключа +- какой `tx_hash` или `receipt_id` породил строку, если важна provenance-цепочка +- остаётся ли вопрос про строки FastData или уже перешёл к контрактно-специфичному состоянию в цепочке -### Shell-сценарий истории точного ключа +### Проверенный testnet shell-сценарий -Используйте этот сценарий, когда один полностью определённый ключ уже известен и нужно аккуратно перейти от вопроса «какая последняя индексированная запись?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» +Используйте этот сценарий, когда у вас уже настроен testnet-аккаунт в `near` CLI и нужен один воспроизводимый сценарий записи, который можно проверить от начала до конца. **Что вы делаете** -- Читаете последнюю индексированную запись по точному контракту, predecessor и пути ключа. -- Извлекаете точный `key` через `jq`. -- Переиспользуете этот ключ в `POST /v0/history`, чтобы получить историю записей по тому же ключу. +- Пишете одну свежую запись FastData в `kv.gork-agent.testnet`. +- Ждёте, пока KV FastData проиндексирует эту транзакцию. +- Читаете точную строку `value` и точную строку `key`, которые эмитировал контракт. +- Забираете историю точного ключа `value`. +- Расширяетесь до области предшественника с метаданными, чтобы получить индексированный `tx_hash`. +- Гидратируете эту транзакцию и декодируете исходные аргументы вызова `__fastdata_kv`. ```bash -KV_BASE_URL=https://kv.main.fastnear.com -CURRENT_ACCOUNT_ID=social.near -PREDECESSOR_ID=james.near -KEY='graph/follow/sleet.near' +KV_BASE_URL=https://kv.test.fastnear.com +TX_BASE_URL=https://tx.test.fastnear.com +CURRENT_ACCOUNT_ID=kv.gork-agent.testnet +SIGNER_ID=YOUR_TESTNET_ACCOUNT +PREDECESSOR_ID="$SIGNER_ID" +FASTDATA_FIELD=verification +FASTDATA_VALUE="verify-$(date -u +%Y%m%dT%H%M%SZ)" + +near call "$CURRENT_ACCOUNT_ID" __fastdata_kv \ + "$(jq -nc --arg key "$FASTDATA_FIELD" --arg value "$FASTDATA_VALUE" '{key: $key, value: $value}')" \ + --accountId "$SIGNER_ID" \ + --networkId testnet \ + --gas 30000000000000 \ + | tee /tmp/kv-fastdata-call.txt + +CLI_TX_HASH="$( + awk '/Transaction Id/{print $3}' /tmp/kv-fastdata-call.txt +)" -ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" +ATTEMPTS=0 +until curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ + -H 'content-type: application/json' \ + --data '{"include_metadata": true, "limit": 20}' \ + | tee /tmp/kv-predecessor-latest.json \ + | jq -e --arg tx_hash "$CLI_TX_HASH" ' + .entries + | map(select(.tx_hash == $tx_hash)) + | length > 0 + ' >/dev/null +do + ATTEMPTS=$((ATTEMPTS + 1)) + if [ "$ATTEMPTS" -ge 30 ]; then + echo "Timed out waiting for KV FastData to index $CLI_TX_HASH" >&2 + exit 1 + fi + sleep 2 +done -EXACT_KEY="$( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | tee /tmp/kv-latest.json \ - | jq -r '.entries[0].key' +INDEXED_TX_HASH="$( + jq -r --arg tx_hash "$CLI_TX_HASH" ' + first(.entries[] | select(.tx_hash == $tx_hash) | .tx_hash) + ' /tmp/kv-predecessor-latest.json )" +test "$CLI_TX_HASH" = "$INDEXED_TX_HASH" \ + && echo "CLI tx hash matches indexed metadata" + +jq --arg tx_hash "$CLI_TX_HASH" '{ + tx_hashes: ([.entries[] | select(.tx_hash == $tx_hash) | .tx_hash] | unique), + receipt_ids: ([.entries[] | select(.tx_hash == $tx_hash) | .receipt_id] | unique), + entries: [ + .entries[] + | select(.tx_hash == $tx_hash) + | { + block_height, + key, + value, + tx_hash, + receipt_id + } + ] + }' /tmp/kv-predecessor-latest.json + jq '{ - latest: ( + latest_value_row: ( .entries[0] | { current_account_id, @@ -1674,32 +1729,63 @@ jq '{ value } ) -}' /tmp/kv-latest.json +}' <( + curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" +) -curl -s "$KV_BASE_URL/v0/history" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg key "$EXACT_KEY" '{key: $key, limit: 10}')" \ +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ + | jq '{ + latest_key_row: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' + +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ | jq '{ page_token, entries: [ .entries[] | { - current_account_id, - predecessor_id, block_height, + key, value } ] }' + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$INDEXED_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + args: ( + .transactions[0].transaction.actions[0].FunctionCall.args + | @base64d + | fromjson + ), + first_receipt_id: .transactions[0].execution_outcome.outcome.receipt_ids[0] + }' ``` **Зачем нужен следующий шаг?** -Первый запрос отвечает на вопрос «что у нас есть прямо сейчас?». Повторное использование точного `key` в `POST /v0/history` отвечает на вопрос «как мы к этому пришли?». Для этого конкретного ключа в `social.near` именно индексированная история и является правильной конечной точкой: сырой `view_state`-follow-up здесь непрактичен, потому что состояние контракта слишком велико. Если нужен точный chain-state на меньшем контракте, тогда и стоит переходить к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). +Этот контракт эмитирует две строки FastData из одного вызова: строку `key`, в которой лежит логическое имя поля, и строку `value`, в которой лежит само значение. Маршруты точного ключа напрямую доказывают наличие этих строк. Lookup по предшественнику — это мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, породившие эти строки. Гидратация транзакции доказывает, что индексированные строки возникли из одного вызова `__fastdata_kv` с теми же декодированными аргументами. + +Именно здесь проходит важная граница этой поверхности: строки FastData — это индексированный вывод FastData, а не обещание, что сырое RPC `view_state` покажет те же ключи. Поскольку это индексированная поверхность, свежая запись может появиться не мгновенно; дождитесь, пока в индексе появится ваш `tx_hash`, прежде чем считать latest-строки окончательными. Если вопрос пользователя меняется с «какие строки FastData были эмитированы?» на «как выглядит каноническое on-chain-состояние контракта?», переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную вам layout-структуру хранилища. ## Частые задачи -### Посмотреть один точный ключ прямо сейчас +### Посмотреть один точный ключ FastData прямо сейчас **Начните здесь** @@ -1711,18 +1797,18 @@ curl -s "$KV_BASE_URL/v0/history" \ **Остановитесь, когда** -- Последняя индексированная запись уже отвечает на вопрос о хранилище. +- Последняя индексированная запись уже отвечает на вопрос по FastData. **Переходите дальше, когда** -- Пользователю нужно точное текущее состояние в цепочке на контракте, который подходит для прямого raw-state чтения. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) или к собственному read-методу контракта. +- Пользователя больше не интересуют индексированные строки FastData. Переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только если знаете, какой raw-state layout нужен. -### Превратить один точный ключ в историю изменений +### Превратить один точный ключ FastData в историю изменений **Начните здесь** -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для поиска истории по пути. -- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для path-based истории внутри одного контракта и одного предшественника. +- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) только тогда, когда вы специально хотите искать один и тот же текст ключа по всем контрактам и предшественникам. **Следующая страница при необходимости** @@ -1734,18 +1820,19 @@ curl -s "$KV_BASE_URL/v0/history" \ **Переходите дальше, когда** -- Теперь нужен точный chain-state, а не только индексированная история. На меньших контрактах переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state); в остальных случаях используйте собственный read-метод контракта. +- Теперь нужно каноническое состояние контракта, а не только индексированная история FastData. Используйте собственный read-метод контракта или [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только если форма raw-state уже известна. ### Проследить записи от одного `predecessor_id` **Начните здесь** - [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. +- [Последнее по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor), когда нужны строки для одного контракта и одного предшественника, при необходимости вместе с метаданными. - [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. **Следующая страница при необходимости** -- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. +- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования, или переходите к [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если главным становится provenance-вопрос. **Остановитесь, когда** @@ -1755,6 +1842,25 @@ curl -s "$KV_BASE_URL/v0/history" \ - Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. +### Привязать одну строку FastData обратно к транзакции + +**Начните здесь** + +- [Последнее по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) с `include_metadata: true`, чтобы получить `tx_hash` и `receipt_id`. +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы гидратировать исходный вызов и декодировать его аргументы. + +**Следующая страница при необходимости** + +- Переходите к [Receipt by Id](https://docs.fastnear.com/ru/tx/receipt), если следующий вопрос уже не про исходный вызов, а про конкретный downstream receipt. + +**Остановитесь, когда** + +- Уже можно объяснить, какой вызов породил индексированную строку FastData. + +**Переходите дальше, когда** + +- Пользователю нужна каноническая семантика исполнения или точный статус на уровне executor. Тогда переходите к RPC transaction status. + ### Пакетно проверить несколько известных ключей **Начните здесь** @@ -1776,9 +1882,11 @@ curl -s "$KV_BASE_URL/v0/history" \ ## Частые ошибки - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. +- Путать [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) с [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key). Первый маршрут остаётся внутри одного контракта и предшественника, второй ищет одинаковый текст ключа глобально. - Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. -- Путать индексированную историю с точным текущим состоянием в цепочке. -- Обещать raw `view_state`-доказательство на очень больших контрактах вроде `social.near`. +- Путать индексированные строки FastData с точным каноническим состоянием контракта. +- Предполагать, что ключ FastData можно напрямую запросить через raw RPC `view_state`. +- Предполагать, что свежая запись будет проиндексирована синхронно с включением в блокчейн. - Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. ## Полезные связанные страницы diff --git a/static/ru/llms.txt b/static/ru/llms.txt index a5ac5f9..e09c309 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -16,7 +16,7 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и понимания, когда точный RPC follow-up по состоянию вообще практичен. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных FastData-ключей, чтения истории точного ключа и привязки индексированных строк к исходной транзакции. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. - [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. From 4e13ed42e63d841e09bcc4c6965d2973df840d77 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 22:30:13 -0700 Subject: [PATCH 12/35] docs: clean up example walkthrough details --- docs/api/examples.md | 13 +++++++------ docs/tx/examples.md | 4 ---- .../current/api/examples.md | 13 +++++++------ .../current/tx/examples.md | 4 ---- static/ru/api/examples.md | 13 +++++++------ static/ru/api/examples/index.md | 13 +++++++------ static/ru/llms-full.txt | 17 +++++++---------- static/ru/tx/examples.md | 4 ---- static/ru/tx/examples/index.md | 4 ---- 9 files changed, 35 insertions(+), 50 deletions(-) diff --git a/docs/api/examples.md b/docs/api/examples.md index 3983cfa..1bca905 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -162,7 +162,7 @@ Use this when the user story is “this BOS widget is a real on-chain artifact.

Read the exact widget first, then mint only after the provenance fields are deterministic.

-

01GET /v1/account/.../nft checks whether the receiver already holds archive NFTs from this collection.

+

01GET /v1/account/.../nft checks whether the receiver already shows a holding from this archive collection.

02RPC call_function get on social.near reads the exact widget source and its SocialDB write block.

03Hash the source, mint nft_mint on testnet, then verify the exact provenance fields through nft_token.

@@ -181,7 +181,7 @@ Use this when the user story is “this BOS widget is a real on-chain artifact. **What you're doing** -- Check whether the receiver already holds NFTs from the archive collection. +- Check whether the receiver already shows a holding from the archive collection. - Read one exact BOS widget from `social.near`, including its widget-level SocialDB block. - Hash the widget source and turn that into provenance metadata. - Mint a testnet NFT whose metadata records the author, widget path, SocialDB block, and source hash. @@ -204,25 +204,26 @@ RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Use FastNear API to see whether the receiver already holds NFTs from the archive collection. +1. Use FastNear API to see whether the receiver already shows a holding from the archive collection. ```bash curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ | tee /tmp/provenance-account-nfts.json >/dev/null jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_tokens: [ + existing_archive_collection_entries: [ .tokens[]? | select(.contract_id == $destination_collection_id) | { contract_id, - token_id, last_update_block_height } ] }' /tmp/provenance-account-nfts.json ``` +This is a quick collection-presence check, not an exact token inventory. The exact minted token is verified later through `nft_token`. + 2. Read the exact widget body and widget-level SocialDB block from mainnet. ```bash @@ -365,7 +366,7 @@ jq '{ **Why this next step?** -FastNear API gives you the quick receiver-side check. Mainnet RPC gives you the exact widget body and SocialDB block. The exact `nft_token` read on testnet confirms that minting turned that into a durable NFT record. If you later want to prove which historical transaction wrote the widget, hand off to the NEAR Social proof investigations on [Transactions API examples](/tx/examples). +FastNear API gives you the quick receiver-side collection check. Mainnet RPC gives you the exact widget body and SocialDB block. The exact `nft_token` read on testnet confirms that minting turned that into a durable NFT record. If you later want to prove which historical transaction wrote the widget, hand off to the NEAR Social proof investigations on [Transactions API examples](/tx/examples). ## Common jobs diff --git a/docs/tx/examples.md b/docs/tx/examples.md index c820337..0672f9f 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -1186,10 +1186,6 @@ For this live anchor: #### NEAR Social widget write-proof shell walkthrough -## Settlement Trace - -This is the richest single-trace investigation on the page: one live NEAR Intents settlement, from top-level tx to the receipts and events that explain it. - Use this when you want to turn one widget block anchor into the exact transaction that wrote it. **What you're doing** diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index b982c8f..25b95b7 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -162,7 +162,7 @@ jq -n \

Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы.

-

01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции.

+

01GET /v1/account/.../nft проверяет, появляется ли у получателя уже запись об этой архивной коллекции.

02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB.

03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте точные provenance-поля через nft_token.

@@ -181,7 +181,7 @@ jq -n \ **Что вы делаете** -- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. +- Через FastNear API проверяете, появляется ли у получателя уже запись об архивной коллекции. - Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. - Хешируете исходник виджета и превращаете его в provenance-метаданные. - Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. @@ -204,25 +204,26 @@ RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. +1. Через FastNear API посмотрите, появляется ли у получателя уже запись об архивной коллекции. ```bash curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ | tee /tmp/provenance-account-nfts.json >/dev/null jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_tokens: [ + existing_archive_collection_entries: [ .tokens[]? | select(.contract_id == $destination_collection_id) | { contract_id, - token_id, last_update_block_height } ] }' /tmp/provenance-account-nfts.json ``` +Это быстрый чек наличия коллекции у получателя, а не точный список токенов. Точный выпущенный токен позже подтверждается через `nft_token`. + 2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. ```bash @@ -365,7 +366,7 @@ jq '{ **Зачем нужен следующий шаг?** -FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](/tx/examples). +FastNear API даёт быстрый чек наличия коллекции со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](/tx/examples). ## Частые задачи diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index 1493da3..a94cf58 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -1186,10 +1186,6 @@ NEAR Social даёт семантическую связь. FastNear block recei #### Shell-сценарий доказательства записи виджета в NEAR Social -## Трассировка расчёта - -Это самое насыщенное расследование на странице: один живой расчёт NEAR Intents от верхнеуровневой транзакции до receipts и событий, которые его объясняют. - Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. **Что вы делаете** diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index 49f7e22..5eab573 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -141,7 +141,7 @@ jq -n \ Стратегия Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. - 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. + 01GET /v1/account/.../nft проверяет, появляется ли у получателя уже запись об этой архивной коллекции. 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте точные provenance-поля через nft_token. @@ -158,7 +158,7 @@ jq -n \ **Что вы делаете** -- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. +- Через FastNear API проверяете, появляется ли у получателя уже запись об архивной коллекции. - Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. - Хешируете исходник виджета и превращаете его в provenance-метаданные. - Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. @@ -181,25 +181,26 @@ RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. +1. Через FastNear API посмотрите, появляется ли у получателя уже запись об архивной коллекции. ```bash curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ | tee /tmp/provenance-account-nfts.json >/dev/null jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_tokens: [ + existing_archive_collection_entries: [ .tokens[]? | select(.contract_id == $destination_collection_id) | { contract_id, - token_id, last_update_block_height } ] }' /tmp/provenance-account-nfts.json ``` +Это быстрый чек наличия коллекции у получателя, а не точный список токенов. Точный выпущенный токен позже подтверждается через `nft_token`. + 2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. ```bash @@ -342,7 +343,7 @@ jq '{ **Зачем нужен следующий шаг?** -FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). +FastNear API даёт быстрый чек наличия коллекции со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). ## Частые задачи diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index 49f7e22..5eab573 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -141,7 +141,7 @@ jq -n \ Стратегия Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. - 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. + 01GET /v1/account/.../nft проверяет, появляется ли у получателя уже запись об этой архивной коллекции. 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте точные provenance-поля через nft_token. @@ -158,7 +158,7 @@ jq -n \ **Что вы делаете** -- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. +- Через FastNear API проверяете, появляется ли у получателя уже запись об архивной коллекции. - Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. - Хешируете исходник виджета и превращаете его в provenance-метаданные. - Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. @@ -181,25 +181,26 @@ RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. +1. Через FastNear API посмотрите, появляется ли у получателя уже запись об архивной коллекции. ```bash curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ | tee /tmp/provenance-account-nfts.json >/dev/null jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_tokens: [ + existing_archive_collection_entries: [ .tokens[]? | select(.contract_id == $destination_collection_id) | { contract_id, - token_id, last_update_block_height } ] }' /tmp/provenance-account-nfts.json ``` +Это быстрый чек наличия коллекции у получателя, а не точный список токенов. Точный выпущенный токен позже подтверждается через `nft_token`. + 2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. ```bash @@ -342,7 +343,7 @@ jq '{ **Зачем нужен следующий шаг?** -FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). +FastNear API даёт быстрый чек наличия коллекции со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). ## Частые задачи diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 59b8c11..9204f75 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -1135,7 +1135,7 @@ jq -n \ Стратегия Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. - 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. + 01GET /v1/account/.../nft проверяет, появляется ли у получателя уже запись об этой архивной коллекции. 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте точные provenance-поля через nft_token. @@ -1152,7 +1152,7 @@ jq -n \ **Что вы делаете** -- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. +- Через FastNear API проверяете, появляется ли у получателя уже запись об архивной коллекции. - Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. - Хешируете исходник виджета и превращаете его в provenance-метаданные. - Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. @@ -1175,25 +1175,26 @@ RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. +1. Через FastNear API посмотрите, появляется ли у получателя уже запись об архивной коллекции. ```bash curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ | tee /tmp/provenance-account-nfts.json >/dev/null jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_tokens: [ + existing_archive_collection_entries: [ .tokens[]? | select(.contract_id == $destination_collection_id) | { contract_id, - token_id, last_update_block_height } ] }' /tmp/provenance-account-nfts.json ``` +Это быстрый чек наличия коллекции у получателя, а не точный список токенов. Точный выпущенный токен позже подтверждается через `nft_token`. + 2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. ```bash @@ -1336,7 +1337,7 @@ jq '{ **Зачем нужен следующий шаг?** -FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). +FastNear API даёт быстрый чек наличия коллекции со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). ## Частые задачи @@ -6109,10 +6110,6 @@ NEAR Social даёт семантическую связь. FastNear block recei #### Shell-сценарий доказательства записи виджета в NEAR Social -## Трассировка расчёта - -Это самое насыщенное расследование на странице: один живой расчёт NEAR Intents от верхнеуровневой транзакции до receipts и событий, которые его объясняют. - Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. **Что вы делаете** diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index 0b60ca0..9caf20e 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -1138,10 +1138,6 @@ NEAR Social даёт семантическую связь. FastNear block recei #### Shell-сценарий доказательства записи виджета в NEAR Social -## Трассировка расчёта - -Это самое насыщенное расследование на странице: один живой расчёт NEAR Intents от верхнеуровневой транзакции до receipts и событий, которые его объясняют. - Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. **Что вы делаете** diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index 0b60ca0..9caf20e 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -1138,10 +1138,6 @@ NEAR Social даёт семантическую связь. FastNear block recei #### Shell-сценарий доказательства записи виджета в NEAR Social -## Трассировка расчёта - -Это самое насыщенное расследование на странице: один живой расчёт NEAR Intents от верхнеуровневой транзакции до receipts и событий, которые его объясняют. - Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. **Что вы делаете** From 13da05802c444dce2eb51b5298217355e02a7809 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 22:31:05 -0700 Subject: [PATCH 13/35] docs: trim outlayer example to chain-visible flow --- docs/tx/outlayer.mdx | 322 ++++++------------ .../current/tx/outlayer.mdx | 320 ++++++----------- static/ru/guides/llms.txt | 2 +- static/ru/llms-full.txt | 304 ++++++----------- static/ru/llms.txt | 2 +- static/ru/tx/examples/outlayer.md | 312 ++++++----------- static/ru/tx/examples/outlayer/index.md | 312 ++++++----------- 7 files changed, 505 insertions(+), 1069 deletions(-) diff --git a/docs/tx/outlayer.mdx b/docs/tx/outlayer.mdx index a22022e..5d38070 100644 --- a/docs/tx/outlayer.mdx +++ b/docs/tx/outlayer.mdx @@ -1,8 +1,8 @@ --- sidebar_label: OutLayer slug: /tx/examples/outlayer -title: "OutLayer: Trace one request from caller to callback" -description: "Use Transactions API and RPC to trace a real OutLayer request from the initial FunctionCall through the worker transaction and into the callback and refund phase." +title: "OutLayer: Trace request and worker resolution" +description: "Use Transactions API to pair one caller-side OutLayer request with one later worker-side resolution and inspect the finish receipts." displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -10,265 +10,151 @@ keywords: - OutLayer - FastNear - NEAR - - RPC - Transactions API - - callback - - promise - receipt + - callback --- import Link from '@site/src/components/LocalizedLink'; -{/* FASTNEAR_AI_DISCOVERY: This case study shows how to use FastNear RPC and Transactions API to trace a live OutLayer execution in NEAR-native terms. It separates the visible request/worker/callback flow that FastNear can trace now from the documented under-the-hood yield/resume and CKD/MPC trust path. */} +{/* FASTNEAR_AI_DISCOVERY: This example stays on observable transaction and receipt evidence. It shows how to pair one caller-side OutLayer request with one later worker-side resolution and inspect the finish receipts. It does not try to prove OutLayer's internal TEE, yield/resume, or CKD/MPC architecture from public chain data alone. */} -# OutLayer: Trace one request from caller to callback +# OutLayer: Trace request and worker resolution -Use this when the question is: “I can see OutLayer on-chain. Which transaction opened the work, which later transaction came from the worker, and where did callback, charging, and refund behavior show up?” +Use this when the question is: “Which transaction opened the OutLayer request, which later transaction came from the worker, and where did the finish receipts show callback, charging, or refund behavior?” -This is the advanced async case study in the Transactions examples family. Keep the NEAR frame first: one caller-side `FunctionCall`, one later worker-side transaction, and receipt-level follow-up only when you need to inspect the finish. +Treat this as a transaction-history problem first: + +- one caller-side `request_execution` +- one later worker-side `submit_execution_output_and_resolve` or `resolve_execution` +- receipt-level follow-up only when the finish path matters
Strategy -

Find the caller tx and the worker tx first, then use receipts only when the finish path becomes the real question.

+

Find the two hashes first, hydrate them second, and inspect worker receipts only when you need the finish path.

-

01POST /v0/account is the fastest way to discover the caller-side and worker-side hashes that belong to the same story.

-

02POST /v0/transactions hydrates both hashes and shows the readable request, worker resolution, and early logs.

-

03Only after that do you inspect receipt-level callback, charging, and refund behavior or fall back to exact RPC identity checks.

+

01POST /v0/account on outlayer.* is the quickest hash-discovery surface.

+

02POST /v0/transactions turns the caller hash and the worker hash into readable signer, method, and log evidence.

+

03Inspect the worker transaction’s receipts only when the real question is about callback, charging, or refund behavior.

-Useful references: - -- Account History -- Transactions by Hash -- View Account -- [OutLayer NEAR Integration](https://outlayer.fastnear.com/docs/near-integration) -- [OutLayer Secrets / CKD](https://outlayer.fastnear.com/docs/secrets) - -## The short version - -If you run into OutLayer on-chain, the practical questions are usually: +**Goal** -- which transaction created the async work item? -- which later transaction came from the worker? -- where did callback, charging, and refund behavior show up? +- Recover one caller transaction, one worker transaction, and the finish receipts that belong to the same OutLayer request. -That is not a current-state question. It is an execution-history question. +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Hash discovery | Transactions API [`POST /v0/account`](/tx/account) | Pull recent transaction hashes for `outlayer.near` | Gives the fastest contract-local search surface when you do not already know the pair of hashes | +| Transaction hydration | Transactions API [`POST /v0/transactions`](/tx/transactions) | Hydrate the caller-side and worker-side hashes together | Turns the raw hashes into signer, receiver, action, and log evidence | +| Finish-path inspection | Transactions API [`POST /v0/transactions`](/tx/transactions) | Reuse the worker hash and inspect its receipt list | Shows where callback, charging, and refund behavior materialized | +| Optional identity check | RPC [`view_account`](/rpc/account/view-account) | Use RPC only if the next question is about contract identity rather than transaction history | Keeps current-state identity checks separate from history tracing | -The useful FastNear move is to pair one caller-side `request_execution` transaction with one worker-side resolution transaction, and then drop to receipt inspection only for the finish. - -```mermaid -sequenceDiagram - autonumber - participant Caller as "Caller" - participant Outlayer as "outlayer.*" - participant Worker as "worker.outlayer.*" - participant Near as "NEAR runtime" - - Caller->>Outlayer: FunctionCall request_execution - Outlayer-->>Near: establish async work + accounting context - Worker->>Outlayer: resolve_execution or submit_execution_output_and_resolve - Outlayer-->>Near: completion logs, callback path, charging, refunds -``` - -Everything above is visible today with FastNear and RPC. - -## 1. Hydrate one request transaction and one worker resolution - -If you want the whole shape immediately, start with a known pair of hashes and hydrate both. +## Verified shell walkthrough This pair worked on April 18, 2026: -- `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` — caller-side `request_execution` -- `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` — worker-side `submit_execution_output_and_resolve` +- caller-side request: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` +- worker-side resolution: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` -```bash title="Hydrate a request hash and a worker-resolution hash" -curl -sS https://tx.main.fastnear.com/v0/transactions \ - -H 'content-type: application/json' \ - --data '{ - "tx_hashes":[ - "AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4", - "AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs" - ] - }' | jq '.transactions[] | { - hash: .transaction.hash, - signer: .transaction.signer_id, - receiver: .transaction.receiver_id, - actions: [.transaction.actions[] | keys[0]], - logs: (.receipts[0].execution_outcome.outcome.logs[:2]) - }' -``` - -In the sampled output: - -- the request hash came from `solarflux.near` to `outlayer.near` -- the logs showed a resolved project: `zavodil.near/near-email` -- the worker hash came from `worker.outlayer.near` to `outlayer.near` -- the worker logs said `Stored pending output` and `Resolving execution ... (combined flow)` - -That is already the visible loop: the original `FunctionCall` established the async work item, the worker came back later as a separate signer, and the contract resolved the result on-chain. +### 1. Hydrate the caller transaction and the worker transaction together -If you only copy one command from this page, copy this one. +```bash +TX_BASE_URL=https://tx.main.fastnear.com +REQUEST_TX_HASH=AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4 +WORKER_TX_HASH=AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs -## 2. Find the two hashes yourself - -If you do not already have a pair of hashes, switch to Transactions API: Account History. - -```bash title="Recent mainnet activity for outlayer.near" -curl -sS https://tx.main.fastnear.com/v0/account \ +curl -sS "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ - --data '{"account_id":"outlayer.near","desc":true}' \ - | jq '{txs_count, first: .account_txs[0]}' -``` - -On April 18, 2026 this surface reported more than 5,000 traced transactions for `outlayer.near`, and the newest sampled hash was: - -```text -AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs -``` - -That hash was not the original user request. It was already a worker-side follow-up. - -That is why account history is the right first search surface: you are not trying to summarize the contract, you are trying to find two concrete transactions in one execution story. - -```mermaid -flowchart TD - A["Transactions API: /v0/account for outlayer.*"] --> B["Find recent transaction hashes"] - B --> C["Caller-side request_execution hash"] - B --> D["Worker-side resolution hash"] - C --> E["Hydrate both with /v0/transactions"] - D --> E - E --> F["Inspect logs, request_id, project/source, callback receipts, charging, refunds"] + --data "$(jq -nc --arg request_tx_hash "$REQUEST_TX_HASH" --arg worker_tx_hash "$WORKER_TX_HASH" '{ + tx_hashes: [$request_tx_hash, $worker_tx_hash] + }')" \ + | tee /tmp/outlayer-pair.json >/dev/null + +jq '{ + transactions: [ + .transactions[] + | { + hash: .transaction.hash, + signer_id: .transaction.signer_id, + receiver_id: .transaction.receiver_id, + actions: [.transaction.actions[] | keys[0]], + first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) + } + ] +}' /tmp/outlayer-pair.json ``` -## 3. Inspect the callback and refund phase - -If you want to go past “a worker called back,” inspect the receipt list on the hydrated worker transaction. - -```bash title="Show receipt-level follow-up for the worker resolution" -curl -sS https://tx.main.fastnear.com/v0/transactions \ - -H 'content-type: application/json' \ - --data '{"tx_hashes":["AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs"]}' \ - | jq '.transactions[0] | { - hash: .transaction.hash, - receipts: [ - .receipts[] | { - predecessor: .receipt.predecessor_id, - receiver: .receiver_id, - actions: [.receipt.receipt.Action.actions[] | keys[0]], - logs: .execution_outcome.outcome.logs - } - ] - }' +What this establishes: + +- the request transaction came from `solarflux.near` to `outlayer.near` +- the request logs included the resolved project `zavodil.near/near-email` +- the worker transaction came later from `worker.outlayer.near` to `outlayer.near` +- the worker logs included `Stored pending output` and `Resolving execution ...` + +That is the core observable loop in NEAR terms: caller-side request first, worker-side resolution later. + +### 2. Inspect the worker receipts only when the finish path matters + +```bash +jq --arg worker_tx_hash "$WORKER_TX_HASH" ' + .transactions[] + | select(.transaction.hash == $worker_tx_hash) + | { + worker_tx_hash: .transaction.hash, + receipts: [ + .receipts[] + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + actions: [.receipt.receipt.Action.actions[] | keys[0]], + logs: .execution_outcome.outcome.logs + } + ] + } +' /tmp/outlayer-pair.json ``` -What to look for: +Look for: -- `FunctionCall:on_execution_response` +- `FunctionCall` receipts that continue the finish path - charging logs such as `[[yNEAR charged: "..."]]` -- completion events such as `execution_completed` -- follow-up `Transfer` receipts - -This is where `receipt` becomes the right abstraction: not at the beginning of the tutorial, but when you are debugging the actual finish path. - -## 4. Confirm the contract identity if you need it +- follow-up `Transfer` receipts that suggest refund or settlement movement -Use raw RPC when you want exact account identity and code-hash checks. This is the identity step, not the history step. +This is the right point to care about receipts. Do not start here if the real question is still “which two transactions belong to the same OutLayer request?” -```bash title="Mainnet: view_account for outlayer.near" -curl -sS https://rpc.mainnet.fastnear.com \ - -H 'content-type: application/json' \ - --data '{ - "jsonrpc":"2.0", - "id":"1", - "method":"query", - "params":{ - "request_type":"view_account", - "finality":"final", - "account_id":"outlayer.near" - } - }' | jq '.result | {amount, locked, code_hash, storage_usage}' -``` +### 3. If you do not already know the two hashes, discover them first -```bash title="Testnet: view_account for outlayer.testnet" -curl -sS https://rpc.testnet.fastnear.com \ +```bash +curl -sS "$TX_BASE_URL/v0/account" \ -H 'content-type: application/json' \ - --data '{ - "jsonrpc":"2.0", - "id":"1", - "method":"query", - "params":{ - "request_type":"view_account", - "finality":"final", - "account_id":"outlayer.testnet" - } - }' | jq '.result | {amount, locked, code_hash, storage_usage}' -``` - -As of April 18, 2026, both contracts returned the same code hash: - -```text -94uKcoDB3QbEpxDj1xsw9CQwu9bAY1PoVPr2BZYRRv4K + --data '{"account_id":"outlayer.near","desc":true,"limit":10}' \ + | jq '{ + txs_count, + recent_hashes: [.account_txs[:10][] | .transaction_hash] + }' ``` -That is a strong hint that the same contract binary is deployed on both networks. +Use this as a discovery surface only. For this example, `/v0/account` gives you candidate hashes, and `/v0/transactions` is the surface that turns those hashes into readable evidence. -## 5. What is happening under the hood? +## Scope boundary -The visible story above is what a NEAR builder needs first. The deeper story explains why this flow is interesting. +This page intentionally stays on public chain evidence that FastNear and RPC can show directly: -### Observable now +- caller-side request transaction +- later worker-side resolution transaction +- finish-path receipts and logs -FastNear and RPC can already show you: +It does **not** try to prove OutLayer’s internal TEE execution model, same-account `yield/resume` usage, or CKD/MPC trust path from public transaction traces alone. Treat those as separate architecture questions and read them in the OutLayer docs, not as claims established by this trace. -- the caller-side `request_execution` -- the worker-side `resolve_execution` or `submit_execution_output_and_resolve` -- the finish receipts where callback, charging, and refund behavior materialize +## Related pages -Your own integration is still ordinary NEAR async composition: call `outlayer.*`, then handle your callback. - -### Documented under the hood - -The OutLayer docs describe a deeper internal model: `outlayer.*` uses NEAR yield/resume semantics as its own internal async boundary, off-chain work runs inside TEE workers, and protected secrets use a separate keystore trust path backed by DAO-gated CKD requests to the NEAR MPC signer. - -The important precision for NEAR readers is that yield/resume is not being presented here as something your caller contract directly writes. NEAR's yield/resume primitives are same-account primitives, so if that mechanism is used here, the yielded and resumed actor is `outlayer.*`, not the original caller contract. For the raw runtime model, see Advanced Features. - -The Secrets / CKD docs describe that keystore path as two-level: the keystore gets a derivation key through a DAO-gated MPC path, then uses that cached derivation capability for protected secrets during app executions. That is a trust-path explanation, not a claim that every ordinary OutLayer execution makes a fresh DAO -> MPC round trip. - -The live public gateway account for that keystore / DAO path is still unresolved in our current public chain evidence, so keep that part in the documented-under-the-hood bucket rather than the observable-now bucket. - -```mermaid -flowchart TB - subgraph Observable["Observable now with FastNear / RPC"] - Caller["User or calling contract"] -->|FunctionCall request_execution| Entry["outlayer.*"] - Entry -->|later worker activity| Worker["worker.outlayer.*"] - Worker -->|resolve_execution or submit_execution_output_and_resolve| Entry - Entry -->|on_execution_response, charging, refund transfers| Finish["Callback / finish receipts"] - end - - subgraph Internal["Documented under the hood"] - Yield["outlayer.* internal yield point"] --> TEE["TEE worker executes WASM"] - TEE -->|resume with output| Yield - TEE --> Keystore["TEE keystore for protected secrets"] - Keystore -->|documented DAO-gated request_key| DAO["DAO gateway (documented)"] - DAO -->|documented CKD request| MPC["v1.signer / v1.signer-prod.testnet"] - MPC -->|derivation key for keystore| Keystore - end - - Entry -. same logical execution .-> Yield - - classDef observable fill:#e7f1ff,stroke:#2563eb,color:#0f172a; - classDef internal fill:#fff4e5,stroke:#b45309,color:#0f172a; - class Caller,Entry,Worker,Finish observable; - class Yield,TEE,Keystore,DAO,MPC internal; -``` - -## Read deeper - -- Transactions API for account history, receipts, and transaction hydration -- Advanced Features for NEAR yield/resume semantics -- Async Model for promise and callback vocabulary -- [OutLayer NEAR Integration](https://outlayer.fastnear.com/docs/near-integration) for the documented contract-facing interface -- [OutLayer Secrets / CKD](https://outlayer.fastnear.com/docs/secrets) for the documented keystore, DAO, and MPC trust path +- Transactions API: Account History +- Transactions API: Transactions by Hash +- Transactions API: Receipt by ID +- RPC: View Account +- [OutLayer NEAR Integration](https://outlayer.fastnear.com/docs/near-integration) +- [OutLayer Secrets / CKD](https://outlayer.fastnear.com/docs/secrets) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx index f623bca..2015dfb 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx @@ -1,8 +1,8 @@ --- sidebar_label: OutLayer slug: /tx/examples/outlayer -title: "OutLayer: как проследить один запрос от вызова до callback" -description: "Используйте Transactions API и RPC, чтобы проследить реальный OutLayer-запрос от исходного FunctionCall через транзакцию воркера до callback- и refund-фазы." +title: "OutLayer: трассировка запроса и разрешения воркером" +description: "Используйте Transactions API, чтобы связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать завершающие receipts." displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -10,265 +10,151 @@ keywords: - OutLayer - FastNear - NEAR - - RPC - Transactions API - - callback - - promise - receipt + - callback --- import Link from '@site/src/components/LocalizedLink'; -{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как использовать FastNear RPC и Transactions API, чтобы разбирать живое исполнение OutLayer в терминах NEAR. Он отделяет видимый request/worker/callback-поток, который уже можно трассировать через FastNear, от задокументированного внутреннего пути yield/resume и CKD/MPC. */} +{/* FASTNEAR_AI_DISCOVERY: Этот пример остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} -# OutLayer: как проследить один запрос от вызова до callback +# OutLayer: трассировка запроса и разрешения воркером -Используйте этот разбор, когда вопрос звучит так: «я вижу OutLayer в цепочке. Какая транзакция открыла работу, какая более поздняя транзакция пришла от воркера и где проявились callback, списание и возврат средств?» +Используйте этот сценарий, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» -Это продвинутый разбор асинхронного исполнения в семействе Transactions examples. Держите NEAR-рамку первой: один `FunctionCall` со стороны вызывающего, одна более поздняя транзакция со стороны воркера, и квитанции только тогда, когда действительно нужно разбирать фазу завершения. +Сначала смотрите на это как на задачу по истории транзакций: + +- один caller-side `request_execution` +- одна более поздняя worker-side транзакция `submit_execution_output_and_resolve` или `resolve_execution` +- переход к receipts только тогда, когда уже важен путь завершения
Стратегия -

Сначала найдите caller-транзакцию и worker-транзакцию, а receipts подключайте только тогда, когда настоящим вопросом становится finish-путь.

+

Сначала найдите два хеша, затем раскройте их, а к worker-receipts переходите только тогда, когда нужен finish-путь.

-

01POST /v0/account — самый быстрый способ найти caller-side и worker-side хеши из одной и той же истории.

-

02POST /v0/transactions раскрывает оба хеша и показывает читаемые request, worker-resolution и ранние логи.

-

03Только после этого имеет смысл разбирать callback, списание и refund на уровне receipts или уходить в точные RPC-проверки идентичности.

+

01POST /v0/account по outlayer.* — самый быстрый surface для поиска хешей.

+

02POST /v0/transactions превращает caller-хеш и worker-хеш в читаемые signer, method и log-доказательства.

+

03Разбирайте receipts worker-транзакции только тогда, когда реальный вопрос уже касается callback, списания или возврата средств.

-Полезные ссылки: - -- История аккаунта -- Транзакции по хешу -- Просмотр аккаунта -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) - -## Короткая версия - -Если вы видите активность OutLayer в цепочке, практические вопросы обычно такие: +**Цель** -- какая транзакция создала асинхронную единицу работы? -- какая более поздняя транзакция пришла от воркера? -- где именно проявились callback, списание и возврат средств? +- Восстановить одну caller-side транзакцию, одну worker-side транзакцию и завершающие receipts, которые относятся к одному и тому же запросу OutLayer. -Это не вопрос о текущем состоянии. Это вопрос об истории исполнения. +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Поиск хешей | Transactions API [`POST /v0/account`](/tx/account) | Забираем недавние хеши транзакций для `outlayer.near` | Даёт самый быстрый локальный по контракту surface, когда пара хешей ещё не известна | +| Раскрытие транзакций | Transactions API [`POST /v0/transactions`](/tx/transactions) | Раскрываем caller-side и worker-side хеши вместе | Превращает сырые хеши в signer-, receiver-, action- и log-доказательства | +| Разбор finish-пути | Transactions API [`POST /v0/transactions`](/tx/transactions) | Переиспользуем worker-хеш и читаем список его receipts | Показывает, где материализовались callback, списание и возврат средств | +| Необязательная проверка идентичности | RPC [`view_account`](/rpc/account/view-account) | Идём в RPC только если следующий вопрос уже про идентичность контракта, а не про историю транзакций | Держит проверку текущего состояния отдельно от трассировки истории | -Полезный ход через FastNear — связать одну транзакцию `request_execution` со стороны вызывающего с одной транзакцией разрешения со стороны воркера, а к receipt переходить только на этапе завершения. - -```mermaid -sequenceDiagram - autonumber - participant Caller as "Вызывающая сторона" - participant Outlayer as "outlayer.*" - participant Worker as "worker.outlayer.*" - participant Near as "исполняющая среда NEAR" - - Caller->>Outlayer: FunctionCall request_execution - Outlayer-->>Near: создаёт асинхронную работу и контекст учёта - Worker->>Outlayer: resolve_execution или submit_execution_output_and_resolve - Outlayer-->>Near: логи завершения, путь callback, списание, возвраты -``` - -Всё это уже видно сегодня через FastNear и RPC. - -## 1. Раскройте одну транзакцию запроса и одно разрешение воркера - -Если хотите сразу увидеть всю форму потока, начните с уже известной пары хешей и раскройте оба. +## Проверенный shell-сценарий Эта пара работала 18 апреля 2026 года: -- `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` — `request_execution` со стороны вызывающего -- `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` — `submit_execution_output_and_resolve` со стороны воркера +- caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` +- worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` -```bash title="Раскройте хеш запроса и хеш разрешения воркера" -curl -sS https://tx.main.fastnear.com/v0/transactions \ - -H 'content-type: application/json' \ - --data '{ - "tx_hashes":[ - "AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4", - "AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs" - ] - }' | jq '.transactions[] | { - hash: .transaction.hash, - signer: .transaction.signer_id, - receiver: .transaction.receiver_id, - actions: [.transaction.actions[] | keys[0]], - logs: (.receipts[0].execution_outcome.outcome.logs[:2]) - }' -``` - -В этом выборочном выводе: - -- хеш запроса шёл от `solarflux.near` к `outlayer.near` -- в логах фигурировал разрешённый проект: `zavodil.near/near-email` -- хеш воркера шёл от `worker.outlayer.near` к `outlayer.near` -- в логах воркера было `Stored pending output` и `Resolving execution ... (combined flow)` - -Этого уже достаточно для видимой истории в терминах NEAR: исходный `FunctionCall` создал асинхронную единицу работы, позже воркер вернулся как отдельный подписант, а контракт разрешил результат в цепочке. +### 1. Сразу раскройте caller-транзакцию и worker-транзакцию вместе -Если копировать с этой страницы только одну команду, то именно эту. +```bash +TX_BASE_URL=https://tx.main.fastnear.com +REQUEST_TX_HASH=AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4 +WORKER_TX_HASH=AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs -## 2. Найдите два нужных хеша сами - -Если пары хешей у вас ещё нет, переключитесь на Transactions API: история аккаунта. - -```bash title="Недавняя mainnet-активность для outlayer.near" -curl -sS https://tx.main.fastnear.com/v0/account \ +curl -sS "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ - --data '{"account_id":"outlayer.near","desc":true}' \ - | jq '{txs_count, first: .account_txs[0]}' -``` - -18 апреля 2026 года эта поверхность показывала более 5 000 трассированных транзакций для `outlayer.near`, а самый свежий выборочный хеш был таким: - -```text -AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs -``` - -Этот хеш не был исходным пользовательским запросом. Это уже было последующее действие со стороны воркера. - -Именно поэтому история аккаунта — правильный первый поиск: здесь задача не в том, чтобы описать контракт целиком, а в том, чтобы найти две конкретные транзакции в одной истории исполнения. - -```mermaid -flowchart TD - A["Transactions API: /v0/account для outlayer.*"] --> B["Найдите недавние хеши транзакций"] - B --> C["Хеш request_execution со стороны вызывающего"] - B --> D["Хеш разрешения со стороны воркера"] - C --> E["Раскройте оба через /v0/transactions"] - D --> E - E --> F["Проверьте логи, request_id, project/source, callback-receipt, списание и возвраты"] + --data "$(jq -nc --arg request_tx_hash "$REQUEST_TX_HASH" --arg worker_tx_hash "$WORKER_TX_HASH" '{ + tx_hashes: [$request_tx_hash, $worker_tx_hash] + }')" \ + | tee /tmp/outlayer-pair.json >/dev/null + +jq '{ + transactions: [ + .transactions[] + | { + hash: .transaction.hash, + signer_id: .transaction.signer_id, + receiver_id: .transaction.receiver_id, + actions: [.transaction.actions[] | keys[0]], + first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) + } + ] +}' /tmp/outlayer-pair.json ``` -## 3. Разберите фазу callback и возврата средств - -Если нужно пройти дальше, чем просто «воркер вернул результат», посмотрите список receipt у раскрытой воркерской транзакции. - -```bash title="Показать последующие действия на уровне receipt для разрешения воркером" -curl -sS https://tx.main.fastnear.com/v0/transactions \ - -H 'content-type: application/json' \ - --data '{"tx_hashes":["AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs"]}' \ - | jq '.transactions[0] | { - hash: .transaction.hash, - receipts: [ - .receipts[] | { - predecessor: .receipt.predecessor_id, - receiver: .receiver_id, - actions: [.receipt.receipt.Action.actions[] | keys[0]], - logs: .execution_outcome.outcome.logs - } - ] - }' +Что это доказывает: + +- запросная транзакция шла от `solarflux.near` к `outlayer.near` +- в логах запроса фигурировал проект `zavodil.near/near-email` +- worker-транзакция позже пришла от `worker.outlayer.near` к `outlayer.near` +- в логах воркера были `Stored pending output` и `Resolving execution ...` + +Это и есть главный наблюдаемый цикл в терминах NEAR: сначала caller-side запрос, затем более позднее worker-side разрешение. + +### 2. Читайте worker-receipts только тогда, когда уже важен finish-путь + +```bash +jq --arg worker_tx_hash "$WORKER_TX_HASH" ' + .transactions[] + | select(.transaction.hash == $worker_tx_hash) + | { + worker_tx_hash: .transaction.hash, + receipts: [ + .receipts[] + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + actions: [.receipt.receipt.Action.actions[] | keys[0]], + logs: .execution_outcome.outcome.logs + } + ] + } +' /tmp/outlayer-pair.json ``` На что смотреть: -- `FunctionCall:on_execution_response` +- `FunctionCall`-receipts, которые продолжают finish-путь - логи списания вроде `[[yNEAR charged: "..."]]` -- события завершения вроде `execution_completed` -- последующие receipt `Transfer` - -Здесь понятие receipt как раз становится правильной абстракцией: не в начале урока, а тогда, когда уже отлаживается реальный путь завершения. - -## 4. Подтвердите контракт, если нужна точная проверка +- последующие `Transfer`-receipts, которые похожи на refund или settlement-движение -Если нужна точная проверка аккаунта и `code_hash`, используйте сырой RPC. Это шаг для проверки идентичности, а не для восстановления истории исполнения. +Именно здесь receipts становятся правильной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции относятся к одному запросу OutLayer?» -```bash title="Mainnet: view_account для outlayer.near" -curl -sS https://rpc.mainnet.fastnear.com \ - -H 'content-type: application/json' \ - --data '{ - "jsonrpc":"2.0", - "id":"1", - "method":"query", - "params":{ - "request_type":"view_account", - "finality":"final", - "account_id":"outlayer.near" - } - }' | jq '.result | {amount, locked, code_hash, storage_usage}' -``` +### 3. Если двух хешей у вас ещё нет, сначала найдите их -```bash title="Testnet: view_account для outlayer.testnet" -curl -sS https://rpc.testnet.fastnear.com \ +```bash +curl -sS "$TX_BASE_URL/v0/account" \ -H 'content-type: application/json' \ - --data '{ - "jsonrpc":"2.0", - "id":"1", - "method":"query", - "params":{ - "request_type":"view_account", - "finality":"final", - "account_id":"outlayer.testnet" - } - }' | jq '.result | {amount, locked, code_hash, storage_usage}' -``` - -По состоянию на 18 апреля 2026 года оба контракта возвращали один и тот же `code_hash`: - -```text -94uKcoDB3QbEpxDj1xsw9CQwu9bAY1PoVPr2BZYRRv4K + --data '{"account_id":"outlayer.near","desc":true,"limit":10}' \ + | jq '{ + txs_count, + recent_hashes: [.account_txs[:10][] | .transaction_hash] + }' ``` -Это сильный сигнал, что на обеих сетях развёрнут один и тот же бинарник контракта. +Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это уже surface, который превращает их в читаемое доказательство. -## 5. Что происходит внутри? +## Граница сценария -Видимая история выше — это то, что NEAR-разработчику нужно сначала. Более глубокий слой объясняет, почему этот поток вообще интересен. +Этот сценарий намеренно остаётся в рамках публичных chain-данных, которые FastNear и RPC умеют показывать напрямую: -### Что можно наблюдать уже сейчас +- caller-side транзакция запроса +- более поздняя worker-side транзакция разрешения +- finish-receipts и логи -Через FastNear и RPC уже видно: +Он **не** пытается доказывать внутреннюю TEE-модель OutLayer, использование same-account `yield/resume` или путь доверия через CKD/MPC только по публичной трассе транзакций. Это отдельные архитектурные вопросы, и читать их нужно в документации OutLayer, а не считать доказанными этим trace-сценарием. -- вызывающую транзакцию `request_execution` -- воркерскую `resolve_execution` или `submit_execution_output_and_resolve` -- завершающие receipt, где материализуются callback, списание и возврат средств +## Полезные связанные страницы -Интеграция вашего контракта при этом остаётся обычной асинхронной композицией в NEAR: вы вызываете `outlayer.*`, а потом обрабатываете свой callback. - -### Что задокументировано как внутренний механизм - -Документация OutLayer описывает более глубокий внутренний слой: `outlayer.*` использует семантику NEAR `yield/resume` как свою внутреннюю асинхронную границу, внешняя работа выполняется в TEE-воркерах, а защищённые секреты проходят через отдельный путь доверия, где TEE-keystore получает DAO-gated CKD через MPC signer. - -Для NEAR-разработчика здесь важна точность: мы не говорим, что ваш вызывающий контракт сам пишет `promise_yield_create`. Примитивы `yield/resume` в NEAR работают только в рамках одного и того же аккаунта, поэтому если этот механизм используется здесь, то yielding и resuming делает `outlayer.*`, а не исходный вызывающий контракт. Для сырой модели выполнения смотрите Продвинутые возможности. - -Документация Secrets / CKD описывает этот путь keystore как двухуровневый: сначала keystore получает derivation key через DAO-gated путь к MPC, а затем использует уже полученную derivation capability для защищённых секретов во время исполнений приложения. Это объяснение доверительной модели, а не утверждение, что каждое обычное исполнение OutLayer делает новый DAO -> MPC round trip. - -Публичный gateway-аккаунт для этого пути keystore / DAO в наших текущих публичных chain-данных всё ещё не подтверждён, поэтому эту часть надо держать в корзине «задокументировано внутри», а не в корзине «уже наблюдается сейчас». - -```mermaid -flowchart TB - subgraph Observable["Что уже можно наблюдать через FastNear / RPC"] - Caller["Пользователь или вызывающий контракт"] -->|FunctionCall request_execution| Entry["outlayer.*"] - Entry -->|поздняя активность воркера| Worker["worker.outlayer.*"] - Worker -->|resolve_execution или submit_execution_output_and_resolve| Entry - Entry -->|on_execution_response, списание, refund-переводы| Finish["Callback / finish receipt"] - end - - subgraph Internal["Что задокументировано как внутренний слой"] - Yield["внутренняя yield-точка в outlayer.*"] --> TEE["TEE-воркер исполняет WASM"] - TEE -->|resume с результатом| Yield - TEE --> Keystore["TEE-keystore для защищённых секретов"] - Keystore -->|задокументированный DAO-gated request_key| DAO["DAO gateway (задокументирован)"] - DAO -->|задокументированный CKD request| MPC["v1.signer / v1.signer-prod.testnet"] - MPC -->|derivation key для keystore| Keystore - end - - Entry -. та же логическая история исполнения .-> Yield - - classDef observable fill:#e7f1ff,stroke:#2563eb,color:#0f172a; - classDef internal fill:#fff4e5,stroke:#b45309,color:#0f172a; - class Caller,Entry,Worker,Finish observable; - class Yield,TEE,Keystore,DAO,MPC internal; -``` - -## Куда читать дальше - -- Transactions API для истории аккаунта, receipt и раскрытия транзакций -- Продвинутые возможности для семантики `yield/resume` в NEAR -- Асинхронная модель для лексики promise и callback -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) для задокументированного контрактного интерфейса -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) для задокументированного пути keystore, DAO и MPC +- Transactions API: история аккаунта +- Transactions API: транзакции по хешу +- Transactions API: receipt по ID +- RPC: view_account +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index 30f0d57..5b0bd4d 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -36,7 +36,7 @@ - [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. - [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования и подробные разборы для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents. - [Berry Club: как восстанавливать исторические доски](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Используйте Transactions API, RPC get_lines и проигрывание draw-вызовов, чтобы восстанавливать доски Berry Club по историческим эпохам. -- [OutLayer: как проследить один запрос от вызова до callback](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API и RPC, чтобы проследить реальный OutLayer-запрос от исходного FunctionCall через транзакцию воркера до callback- и refund-фазы. +- [OutLayer: трассировка запроса и разрешения воркером](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать завершающие receipts. ## Снапшоты diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 9204f75..e4bf361 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -6771,261 +6771,149 @@ for (const drawTx of drawTransactionsOldestFirst) { --- -## OutLayer: как проследить один запрос от вызова до callback +## OutLayer: трассировка запроса и разрешения воркером - HTML-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer - Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer.md **Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) -{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как использовать FastNear RPC и Transactions API, чтобы разбирать живое исполнение OutLayer в терминах NEAR. Он отделяет видимый request/worker/callback-поток, который уже можно трассировать через FastNear, от задокументированного внутреннего пути yield/resume и CKD/MPC. */} +{/* FASTNEAR_AI_DISCOVERY: Этот пример остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} -# OutLayer: как проследить один запрос от вызова до callback +# OutLayer: трассировка запроса и разрешения воркером -Используйте этот разбор, когда вопрос звучит так: «я вижу OutLayer в цепочке. Какая транзакция открыла работу, какая более поздняя транзакция пришла от воркера и где проявились callback, списание и возврат средств?» +Используйте этот сценарий, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» -Это продвинутый разбор асинхронного исполнения в семействе Transactions examples. Держите NEAR-рамку первой: один `FunctionCall` со стороны вызывающего, одна более поздняя транзакция со стороны воркера, и квитанции только тогда, когда действительно нужно разбирать фазу завершения. +Сначала смотрите на это как на задачу по истории транзакций: - Стратегия - Сначала найдите caller-транзакцию и worker-транзакцию, а receipts подключайте только тогда, когда настоящим вопросом становится finish-путь. - - 01POST /v0/account — самый быстрый способ найти caller-side и worker-side хеши из одной и той же истории. - 02POST /v0/transactions раскрывает оба хеша и показывает читаемые request, worker-resolution и ранние логи. - 03Только после этого имеет смысл разбирать callback, списание и refund на уровне receipts или уходить в точные RPC-проверки идентичности. - -Полезные ссылки: - -- [История аккаунта](https://docs.fastnear.com/ru/tx/account) -- [Транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) -- [Просмотр аккаунта](https://docs.fastnear.com/ru/rpc/account/view-account) -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) - -## Короткая версия - -Если вы видите активность OutLayer в цепочке, практические вопросы обычно такие: - -- какая транзакция создала асинхронную единицу работы? -- какая более поздняя транзакция пришла от воркера? -- где именно проявились callback, списание и возврат средств? - -Это не вопрос о текущем состоянии. Это вопрос об истории исполнения. +- один caller-side `request_execution` +- одна более поздняя worker-side транзакция `submit_execution_output_and_resolve` или `resolve_execution` +- переход к receipts только тогда, когда уже важен путь завершения -Полезный ход через FastNear — связать одну транзакцию `request_execution` со стороны вызывающего с одной транзакцией разрешения со стороны воркера, а к receipt переходить только на этапе завершения. + Стратегия + Сначала найдите два хеша, затем раскройте их, а к worker-receipts переходите только тогда, когда нужен finish-путь. -```mermaid -sequenceDiagram - autonumber - participant Caller as "Вызывающая сторона" - participant Outlayer as "outlayer.*" - participant Worker as "worker.outlayer.*" - participant Near as "исполняющая среда NEAR" + 01POST /v0/account по outlayer.* — самый быстрый surface для поиска хешей. + 02POST /v0/transactions превращает caller-хеш и worker-хеш в читаемые signer, method и log-доказательства. + 03Разбирайте receipts worker-транзакции только тогда, когда реальный вопрос уже касается callback, списания или возврата средств. - Caller->>Outlayer: FunctionCall request_execution - Outlayer-->>Near: создаёт асинхронную работу и контекст учёта - Worker->>Outlayer: resolve_execution или submit_execution_output_and_resolve - Outlayer-->>Near: логи завершения, путь callback, списание, возвраты -``` +**Цель** -Всё это уже видно сегодня через FastNear и RPC. +- Восстановить одну caller-side транзакцию, одну worker-side транзакцию и завершающие receipts, которые относятся к одному и тому же запросу OutLayer. -## 1. Раскройте одну транзакцию запроса и одно разрешение воркера +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Поиск хешей | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Забираем недавние хеши транзакций для `outlayer.near` | Даёт самый быстрый локальный по контракту surface, когда пара хешей ещё не известна | +| Раскрытие транзакций | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Раскрываем caller-side и worker-side хеши вместе | Превращает сырые хеши в signer-, receiver-, action- и log-доказательства | +| Разбор finish-пути | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем worker-хеш и читаем список его receipts | Показывает, где материализовались callback, списание и возврат средств | +| Необязательная проверка идентичности | RPC [`view_account`](https://docs.fastnear.com/ru/rpc/account/view-account) | Идём в RPC только если следующий вопрос уже про идентичность контракта, а не про историю транзакций | Держит проверку текущего состояния отдельно от трассировки истории | -Если хотите сразу увидеть всю форму потока, начните с уже известной пары хешей и раскройте оба. +## Проверенный shell-сценарий Эта пара работала 18 апреля 2026 года: -- `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` — `request_execution` со стороны вызывающего -- `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` — `submit_execution_output_and_resolve` со стороны воркера +- caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` +- worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` -```bash title="Раскройте хеш запроса и хеш разрешения воркера" -curl -sS https://tx.main.fastnear.com/v0/transactions \ - -H 'content-type: application/json' \ - --data '{ - "tx_hashes":[ - "AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4", - "AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs" - ] - }' | jq '.transactions[] | { - hash: .transaction.hash, - signer: .transaction.signer_id, - receiver: .transaction.receiver_id, - actions: [.transaction.actions[] | keys[0]], - logs: (.receipts[0].execution_outcome.outcome.logs[:2]) - }' -``` - -В этом выборочном выводе: - -- хеш запроса шёл от `solarflux.near` к `outlayer.near` -- в логах фигурировал разрешённый проект: `zavodil.near/near-email` -- хеш воркера шёл от `worker.outlayer.near` к `outlayer.near` -- в логах воркера было `Stored pending output` и `Resolving execution ... (combined flow)` - -Этого уже достаточно для видимой истории в терминах NEAR: исходный `FunctionCall` создал асинхронную единицу работы, позже воркер вернулся как отдельный подписант, а контракт разрешил результат в цепочке. +### 1. Сразу раскройте caller-транзакцию и worker-транзакцию вместе -Если копировать с этой страницы только одну команду, то именно эту. - -## 2. Найдите два нужных хеша сами - -Если пары хешей у вас ещё нет, переключитесь на [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account). +```bash +TX_BASE_URL=https://tx.main.fastnear.com +REQUEST_TX_HASH=AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4 +WORKER_TX_HASH=AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs -```bash title="Недавняя mainnet-активность для outlayer.near" -curl -sS https://tx.main.fastnear.com/v0/account \ +curl -sS "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ - --data '{"account_id":"outlayer.near","desc":true}' \ - | jq '{txs_count, first: .account_txs[0]}' -``` - -18 апреля 2026 года эта поверхность показывала более 5 000 трассированных транзакций для `outlayer.near`, а самый свежий выборочный хеш был таким: + --data "$(jq -nc --arg request_tx_hash "$REQUEST_TX_HASH" --arg worker_tx_hash "$WORKER_TX_HASH" '{ + tx_hashes: [$request_tx_hash, $worker_tx_hash] + }')" \ + | tee /tmp/outlayer-pair.json >/dev/null -```text -AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs +jq '{ + transactions: [ + .transactions[] + | { + hash: .transaction.hash, + signer_id: .transaction.signer_id, + receiver_id: .transaction.receiver_id, + actions: [.transaction.actions[] | keys[0]], + first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) + } + ] +}' /tmp/outlayer-pair.json ``` -Этот хеш не был исходным пользовательским запросом. Это уже было последующее действие со стороны воркера. +Что это доказывает: -Именно поэтому история аккаунта — правильный первый поиск: здесь задача не в том, чтобы описать контракт целиком, а в том, чтобы найти две конкретные транзакции в одной истории исполнения. +- запросная транзакция шла от `solarflux.near` к `outlayer.near` +- в логах запроса фигурировал проект `zavodil.near/near-email` +- worker-транзакция позже пришла от `worker.outlayer.near` к `outlayer.near` +- в логах воркера были `Stored pending output` и `Resolving execution ...` -```mermaid -flowchart TD - A["Transactions API: /v0/account для outlayer.*"] --> B["Найдите недавние хеши транзакций"] - B --> C["Хеш request_execution со стороны вызывающего"] - B --> D["Хеш разрешения со стороны воркера"] - C --> E["Раскройте оба через /v0/transactions"] - D --> E - E --> F["Проверьте логи, request_id, project/source, callback-receipt, списание и возвраты"] -``` - -## 3. Разберите фазу callback и возврата средств +Это и есть главный наблюдаемый цикл в терминах NEAR: сначала caller-side запрос, затем более позднее worker-side разрешение. -Если нужно пройти дальше, чем просто «воркер вернул результат», посмотрите список receipt у раскрытой воркерской транзакции. +### 2. Читайте worker-receipts только тогда, когда уже важен finish-путь -```bash title="Показать последующие действия на уровне receipt для разрешения воркером" -curl -sS https://tx.main.fastnear.com/v0/transactions \ - -H 'content-type: application/json' \ - --data '{"tx_hashes":["AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs"]}' \ - | jq '.transactions[0] | { - hash: .transaction.hash, - receipts: [ - .receipts[] | { - predecessor: .receipt.predecessor_id, - receiver: .receiver_id, - actions: [.receipt.receipt.Action.actions[] | keys[0]], - logs: .execution_outcome.outcome.logs - } - ] - }' +```bash +jq --arg worker_tx_hash "$WORKER_TX_HASH" ' + .transactions[] + | select(.transaction.hash == $worker_tx_hash) + | { + worker_tx_hash: .transaction.hash, + receipts: [ + .receipts[] + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + actions: [.receipt.receipt.Action.actions[] | keys[0]], + logs: .execution_outcome.outcome.logs + } + ] + } +' /tmp/outlayer-pair.json ``` На что смотреть: -- `FunctionCall:on_execution_response` +- `FunctionCall`-receipts, которые продолжают finish-путь - логи списания вроде `[[yNEAR charged: "..."]]` -- события завершения вроде `execution_completed` -- последующие receipt `Transfer` - -Здесь понятие receipt как раз становится правильной абстракцией: не в начале урока, а тогда, когда уже отлаживается реальный путь завершения. +- последующие `Transfer`-receipts, которые похожи на refund или settlement-движение -## 4. Подтвердите контракт, если нужна точная проверка +Именно здесь receipts становятся правильной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции относятся к одному запросу OutLayer?» -Если нужна точная проверка аккаунта и `code_hash`, используйте сырой RPC. Это шаг для проверки идентичности, а не для восстановления истории исполнения. - -```bash title="Mainnet: view_account для outlayer.near" -curl -sS https://rpc.mainnet.fastnear.com \ - -H 'content-type: application/json' \ - --data '{ - "jsonrpc":"2.0", - "id":"1", - "method":"query", - "params":{ - "request_type":"view_account", - "finality":"final", - "account_id":"outlayer.near" - } - }' | jq '.result | {amount, locked, code_hash, storage_usage}' -``` +### 3. Если двух хешей у вас ещё нет, сначала найдите их -```bash title="Testnet: view_account для outlayer.testnet" -curl -sS https://rpc.testnet.fastnear.com \ +```bash +curl -sS "$TX_BASE_URL/v0/account" \ -H 'content-type: application/json' \ - --data '{ - "jsonrpc":"2.0", - "id":"1", - "method":"query", - "params":{ - "request_type":"view_account", - "finality":"final", - "account_id":"outlayer.testnet" - } - }' | jq '.result | {amount, locked, code_hash, storage_usage}' -``` - -По состоянию на 18 апреля 2026 года оба контракта возвращали один и тот же `code_hash`: - -```text -94uKcoDB3QbEpxDj1xsw9CQwu9bAY1PoVPr2BZYRRv4K + --data '{"account_id":"outlayer.near","desc":true,"limit":10}' \ + | jq '{ + txs_count, + recent_hashes: [.account_txs[:10][] | .transaction_hash] + }' ``` -Это сильный сигнал, что на обеих сетях развёрнут один и тот же бинарник контракта. - -## 5. Что происходит внутри? - -Видимая история выше — это то, что NEAR-разработчику нужно сначала. Более глубокий слой объясняет, почему этот поток вообще интересен. +Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это уже surface, который превращает их в читаемое доказательство. -### Что можно наблюдать уже сейчас +## Граница сценария -Через FastNear и RPC уже видно: +Этот сценарий намеренно остаётся в рамках публичных chain-данных, которые FastNear и RPC умеют показывать напрямую: -- вызывающую транзакцию `request_execution` -- воркерскую `resolve_execution` или `submit_execution_output_and_resolve` -- завершающие receipt, где материализуются callback, списание и возврат средств +- caller-side транзакция запроса +- более поздняя worker-side транзакция разрешения +- finish-receipts и логи -Интеграция вашего контракта при этом остаётся обычной асинхронной композицией в NEAR: вы вызываете `outlayer.*`, а потом обрабатываете свой callback. +Он **не** пытается доказывать внутреннюю TEE-модель OutLayer, использование same-account `yield/resume` или путь доверия через CKD/MPC только по публичной трассе транзакций. Это отдельные архитектурные вопросы, и читать их нужно в документации OutLayer, а не считать доказанными этим trace-сценарием. -### Что задокументировано как внутренний механизм - -Документация OutLayer описывает более глубокий внутренний слой: `outlayer.*` использует семантику NEAR `yield/resume` как свою внутреннюю асинхронную границу, внешняя работа выполняется в TEE-воркерах, а защищённые секреты проходят через отдельный путь доверия, где TEE-keystore получает DAO-gated CKD через MPC signer. - -Для NEAR-разработчика здесь важна точность: мы не говорим, что ваш вызывающий контракт сам пишет `promise_yield_create`. Примитивы `yield/resume` в NEAR работают только в рамках одного и того же аккаунта, поэтому если этот механизм используется здесь, то yielding и resuming делает `outlayer.*`, а не исходный вызывающий контракт. Для сырой модели выполнения смотрите [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features). - -Документация Secrets / CKD описывает этот путь keystore как двухуровневый: сначала keystore получает derivation key через DAO-gated путь к MPC, а затем использует уже полученную derivation capability для защищённых секретов во время исполнений приложения. Это объяснение доверительной модели, а не утверждение, что каждое обычное исполнение OutLayer делает новый DAO -> MPC round trip. - -Публичный gateway-аккаунт для этого пути keystore / DAO в наших текущих публичных chain-данных всё ещё не подтверждён, поэтому эту часть надо держать в корзине «задокументировано внутри», а не в корзине «уже наблюдается сейчас». +## Полезные связанные страницы -```mermaid -flowchart TB - subgraph Observable["Что уже можно наблюдать через FastNear / RPC"] - Caller["Пользователь или вызывающий контракт"] -->|FunctionCall request_execution| Entry["outlayer.*"] - Entry -->|поздняя активность воркера| Worker["worker.outlayer.*"] - Worker -->|resolve_execution или submit_execution_output_and_resolve| Entry - Entry -->|on_execution_response, списание, refund-переводы| Finish["Callback / finish receipt"] - end - - subgraph Internal["Что задокументировано как внутренний слой"] - Yield["внутренняя yield-точка в outlayer.*"] --> TEE["TEE-воркер исполняет WASM"] - TEE -->|resume с результатом| Yield - TEE --> Keystore["TEE-keystore для защищённых секретов"] - Keystore -->|задокументированный DAO-gated request_key| DAO["DAO gateway (задокументирован)"] - DAO -->|задокументированный CKD request| MPC["v1.signer / v1.signer-prod.testnet"] - MPC -->|derivation key для keystore| Keystore - end - - Entry -. та же логическая история исполнения .-> Yield - - classDef observable fill:#e7f1ff,stroke:#2563eb,color:#0f172a; - classDef internal fill:#fff4e5,stroke:#b45309,color:#0f172a; - class Caller,Entry,Worker,Finish observable; - class Yield,TEE,Keystore,DAO,MPC internal; -``` - -## Куда читать дальше - -- [Transactions API](https://docs.fastnear.com/ru/tx) для истории аккаунта, receipt и раскрытия транзакций -- [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features) для семантики `yield/resume` в NEAR -- [Асинхронная модель](https://docs.fastnear.com/ru/transaction-flow/async-model) для лексики promise и callback -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) для задокументированного контрактного интерфейса -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) для задокументированного пути keystore, DAO и MPC +- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +- [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) +- [RPC: view_account](https://docs.fastnear.com/ru/rpc/account/view-account) +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) --- diff --git a/static/ru/llms.txt b/static/ru/llms.txt index e09c309..0502649 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -39,7 +39,7 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. - [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования и подробные разборы для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents. - [Berry Club: как восстанавливать исторические доски](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Используйте Transactions API, RPC get_lines и проигрывание draw-вызовов, чтобы восстанавливать доски Berry Club по историческим эпохам. -- [OutLayer: как проследить один запрос от вызова до callback](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API и RPC, чтобы проследить реальный OutLayer-запрос от исходного FunctionCall через транзакцию воркера до callback- и refund-фазы. +- [OutLayer: трассировка запроса и разрешения воркером](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать завершающие receipts. ## Снапшоты diff --git a/static/ru/tx/examples/outlayer.md b/static/ru/tx/examples/outlayer.md index 804f0a3..4c7ea07 100644 --- a/static/ru/tx/examples/outlayer.md +++ b/static/ru/tx/examples/outlayer.md @@ -1,250 +1,138 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) -{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как использовать FastNear RPC и Transactions API, чтобы разбирать живое исполнение OutLayer в терминах NEAR. Он отделяет видимый request/worker/callback-поток, который уже можно трассировать через FastNear, от задокументированного внутреннего пути yield/resume и CKD/MPC. */} +{/* FASTNEAR_AI_DISCOVERY: Этот пример остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} -# OutLayer: как проследить один запрос от вызова до callback +# OutLayer: трассировка запроса и разрешения воркером -Используйте этот разбор, когда вопрос звучит так: «я вижу OutLayer в цепочке. Какая транзакция открыла работу, какая более поздняя транзакция пришла от воркера и где проявились callback, списание и возврат средств?» +Используйте этот сценарий, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» -Это продвинутый разбор асинхронного исполнения в семействе Transactions examples. Держите NEAR-рамку первой: один `FunctionCall` со стороны вызывающего, одна более поздняя транзакция со стороны воркера, и квитанции только тогда, когда действительно нужно разбирать фазу завершения. +Сначала смотрите на это как на задачу по истории транзакций: - Стратегия - Сначала найдите caller-транзакцию и worker-транзакцию, а receipts подключайте только тогда, когда настоящим вопросом становится finish-путь. - - 01POST /v0/account — самый быстрый способ найти caller-side и worker-side хеши из одной и той же истории. - 02POST /v0/transactions раскрывает оба хеша и показывает читаемые request, worker-resolution и ранние логи. - 03Только после этого имеет смысл разбирать callback, списание и refund на уровне receipts или уходить в точные RPC-проверки идентичности. - -Полезные ссылки: - -- [История аккаунта](https://docs.fastnear.com/ru/tx/account) -- [Транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) -- [Просмотр аккаунта](https://docs.fastnear.com/ru/rpc/account/view-account) -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) - -## Короткая версия +- один caller-side `request_execution` +- одна более поздняя worker-side транзакция `submit_execution_output_and_resolve` или `resolve_execution` +- переход к receipts только тогда, когда уже важен путь завершения -Если вы видите активность OutLayer в цепочке, практические вопросы обычно такие: - -- какая транзакция создала асинхронную единицу работы? -- какая более поздняя транзакция пришла от воркера? -- где именно проявились callback, списание и возврат средств? - -Это не вопрос о текущем состоянии. Это вопрос об истории исполнения. + Стратегия + Сначала найдите два хеша, затем раскройте их, а к worker-receipts переходите только тогда, когда нужен finish-путь. -Полезный ход через FastNear — связать одну транзакцию `request_execution` со стороны вызывающего с одной транзакцией разрешения со стороны воркера, а к receipt переходить только на этапе завершения. + 01POST /v0/account по outlayer.* — самый быстрый surface для поиска хешей. + 02POST /v0/transactions превращает caller-хеш и worker-хеш в читаемые signer, method и log-доказательства. + 03Разбирайте receipts worker-транзакции только тогда, когда реальный вопрос уже касается callback, списания или возврата средств. -```mermaid -sequenceDiagram - autonumber - participant Caller as "Вызывающая сторона" - participant Outlayer as "outlayer.*" - participant Worker as "worker.outlayer.*" - participant Near as "исполняющая среда NEAR" +**Цель** - Caller->>Outlayer: FunctionCall request_execution - Outlayer-->>Near: создаёт асинхронную работу и контекст учёта - Worker->>Outlayer: resolve_execution или submit_execution_output_and_resolve - Outlayer-->>Near: логи завершения, путь callback, списание, возвраты -``` +- Восстановить одну caller-side транзакцию, одну worker-side транзакцию и завершающие receipts, которые относятся к одному и тому же запросу OutLayer. -Всё это уже видно сегодня через FastNear и RPC. +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Поиск хешей | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Забираем недавние хеши транзакций для `outlayer.near` | Даёт самый быстрый локальный по контракту surface, когда пара хешей ещё не известна | +| Раскрытие транзакций | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Раскрываем caller-side и worker-side хеши вместе | Превращает сырые хеши в signer-, receiver-, action- и log-доказательства | +| Разбор finish-пути | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем worker-хеш и читаем список его receipts | Показывает, где материализовались callback, списание и возврат средств | +| Необязательная проверка идентичности | RPC [`view_account`](https://docs.fastnear.com/ru/rpc/account/view-account) | Идём в RPC только если следующий вопрос уже про идентичность контракта, а не про историю транзакций | Держит проверку текущего состояния отдельно от трассировки истории | -## 1. Раскройте одну транзакцию запроса и одно разрешение воркера - -Если хотите сразу увидеть всю форму потока, начните с уже известной пары хешей и раскройте оба. +## Проверенный shell-сценарий Эта пара работала 18 апреля 2026 года: -- `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` — `request_execution` со стороны вызывающего -- `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` — `submit_execution_output_and_resolve` со стороны воркера - -```bash title="Раскройте хеш запроса и хеш разрешения воркера" -curl -sS https://tx.main.fastnear.com/v0/transactions \ - -H 'content-type: application/json' \ - --data '{ - "tx_hashes":[ - "AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4", - "AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs" - ] - }' | jq '.transactions[] | { - hash: .transaction.hash, - signer: .transaction.signer_id, - receiver: .transaction.receiver_id, - actions: [.transaction.actions[] | keys[0]], - logs: (.receipts[0].execution_outcome.outcome.logs[:2]) - }' -``` - -В этом выборочном выводе: - -- хеш запроса шёл от `solarflux.near` к `outlayer.near` -- в логах фигурировал разрешённый проект: `zavodil.near/near-email` -- хеш воркера шёл от `worker.outlayer.near` к `outlayer.near` -- в логах воркера было `Stored pending output` и `Resolving execution ... (combined flow)` - -Этого уже достаточно для видимой истории в терминах NEAR: исходный `FunctionCall` создал асинхронную единицу работы, позже воркер вернулся как отдельный подписант, а контракт разрешил результат в цепочке. +- caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` +- worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` -Если копировать с этой страницы только одну команду, то именно эту. +### 1. Сразу раскройте caller-транзакцию и worker-транзакцию вместе -## 2. Найдите два нужных хеша сами +```bash +TX_BASE_URL=https://tx.main.fastnear.com +REQUEST_TX_HASH=AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4 +WORKER_TX_HASH=AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs -Если пары хешей у вас ещё нет, переключитесь на [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account). - -```bash title="Недавняя mainnet-активность для outlayer.near" -curl -sS https://tx.main.fastnear.com/v0/account \ +curl -sS "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ - --data '{"account_id":"outlayer.near","desc":true}' \ - | jq '{txs_count, first: .account_txs[0]}' -``` - -18 апреля 2026 года эта поверхность показывала более 5 000 трассированных транзакций для `outlayer.near`, а самый свежий выборочный хеш был таким: - -```text -AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs -``` - -Этот хеш не был исходным пользовательским запросом. Это уже было последующее действие со стороны воркера. - -Именно поэтому история аккаунта — правильный первый поиск: здесь задача не в том, чтобы описать контракт целиком, а в том, чтобы найти две конкретные транзакции в одной истории исполнения. - -```mermaid -flowchart TD - A["Transactions API: /v0/account для outlayer.*"] --> B["Найдите недавние хеши транзакций"] - B --> C["Хеш request_execution со стороны вызывающего"] - B --> D["Хеш разрешения со стороны воркера"] - C --> E["Раскройте оба через /v0/transactions"] - D --> E - E --> F["Проверьте логи, request_id, project/source, callback-receipt, списание и возвраты"] + --data "$(jq -nc --arg request_tx_hash "$REQUEST_TX_HASH" --arg worker_tx_hash "$WORKER_TX_HASH" '{ + tx_hashes: [$request_tx_hash, $worker_tx_hash] + }')" \ + | tee /tmp/outlayer-pair.json >/dev/null + +jq '{ + transactions: [ + .transactions[] + | { + hash: .transaction.hash, + signer_id: .transaction.signer_id, + receiver_id: .transaction.receiver_id, + actions: [.transaction.actions[] | keys[0]], + first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) + } + ] +}' /tmp/outlayer-pair.json ``` -## 3. Разберите фазу callback и возврата средств - -Если нужно пройти дальше, чем просто «воркер вернул результат», посмотрите список receipt у раскрытой воркерской транзакции. - -```bash title="Показать последующие действия на уровне receipt для разрешения воркером" -curl -sS https://tx.main.fastnear.com/v0/transactions \ - -H 'content-type: application/json' \ - --data '{"tx_hashes":["AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs"]}' \ - | jq '.transactions[0] | { - hash: .transaction.hash, - receipts: [ - .receipts[] | { - predecessor: .receipt.predecessor_id, - receiver: .receiver_id, - actions: [.receipt.receipt.Action.actions[] | keys[0]], - logs: .execution_outcome.outcome.logs - } - ] - }' +Что это доказывает: + +- запросная транзакция шла от `solarflux.near` к `outlayer.near` +- в логах запроса фигурировал проект `zavodil.near/near-email` +- worker-транзакция позже пришла от `worker.outlayer.near` к `outlayer.near` +- в логах воркера были `Stored pending output` и `Resolving execution ...` + +Это и есть главный наблюдаемый цикл в терминах NEAR: сначала caller-side запрос, затем более позднее worker-side разрешение. + +### 2. Читайте worker-receipts только тогда, когда уже важен finish-путь + +```bash +jq --arg worker_tx_hash "$WORKER_TX_HASH" ' + .transactions[] + | select(.transaction.hash == $worker_tx_hash) + | { + worker_tx_hash: .transaction.hash, + receipts: [ + .receipts[] + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + actions: [.receipt.receipt.Action.actions[] | keys[0]], + logs: .execution_outcome.outcome.logs + } + ] + } +' /tmp/outlayer-pair.json ``` На что смотреть: -- `FunctionCall:on_execution_response` +- `FunctionCall`-receipts, которые продолжают finish-путь - логи списания вроде `[[yNEAR charged: "..."]]` -- события завершения вроде `execution_completed` -- последующие receipt `Transfer` +- последующие `Transfer`-receipts, которые похожи на refund или settlement-движение -Здесь понятие receipt как раз становится правильной абстракцией: не в начале урока, а тогда, когда уже отлаживается реальный путь завершения. +Именно здесь receipts становятся правильной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции относятся к одному запросу OutLayer?» -## 4. Подтвердите контракт, если нужна точная проверка - -Если нужна точная проверка аккаунта и `code_hash`, используйте сырой RPC. Это шаг для проверки идентичности, а не для восстановления истории исполнения. - -```bash title="Mainnet: view_account для outlayer.near" -curl -sS https://rpc.mainnet.fastnear.com \ - -H 'content-type: application/json' \ - --data '{ - "jsonrpc":"2.0", - "id":"1", - "method":"query", - "params":{ - "request_type":"view_account", - "finality":"final", - "account_id":"outlayer.near" - } - }' | jq '.result | {amount, locked, code_hash, storage_usage}' -``` +### 3. Если двух хешей у вас ещё нет, сначала найдите их -```bash title="Testnet: view_account для outlayer.testnet" -curl -sS https://rpc.testnet.fastnear.com \ +```bash +curl -sS "$TX_BASE_URL/v0/account" \ -H 'content-type: application/json' \ - --data '{ - "jsonrpc":"2.0", - "id":"1", - "method":"query", - "params":{ - "request_type":"view_account", - "finality":"final", - "account_id":"outlayer.testnet" - } - }' | jq '.result | {amount, locked, code_hash, storage_usage}' + --data '{"account_id":"outlayer.near","desc":true,"limit":10}' \ + | jq '{ + txs_count, + recent_hashes: [.account_txs[:10][] | .transaction_hash] + }' ``` -По состоянию на 18 апреля 2026 года оба контракта возвращали один и тот же `code_hash`: - -```text -94uKcoDB3QbEpxDj1xsw9CQwu9bAY1PoVPr2BZYRRv4K -``` - -Это сильный сигнал, что на обеих сетях развёрнут один и тот же бинарник контракта. - -## 5. Что происходит внутри? +Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это уже surface, который превращает их в читаемое доказательство. -Видимая история выше — это то, что NEAR-разработчику нужно сначала. Более глубокий слой объясняет, почему этот поток вообще интересен. +## Граница сценария -### Что можно наблюдать уже сейчас +Этот сценарий намеренно остаётся в рамках публичных chain-данных, которые FastNear и RPC умеют показывать напрямую: -Через FastNear и RPC уже видно: +- caller-side транзакция запроса +- более поздняя worker-side транзакция разрешения +- finish-receipts и логи -- вызывающую транзакцию `request_execution` -- воркерскую `resolve_execution` или `submit_execution_output_and_resolve` -- завершающие receipt, где материализуются callback, списание и возврат средств +Он **не** пытается доказывать внутреннюю TEE-модель OutLayer, использование same-account `yield/resume` или путь доверия через CKD/MPC только по публичной трассе транзакций. Это отдельные архитектурные вопросы, и читать их нужно в документации OutLayer, а не считать доказанными этим trace-сценарием. -Интеграция вашего контракта при этом остаётся обычной асинхронной композицией в NEAR: вы вызываете `outlayer.*`, а потом обрабатываете свой callback. +## Полезные связанные страницы -### Что задокументировано как внутренний механизм - -Документация OutLayer описывает более глубокий внутренний слой: `outlayer.*` использует семантику NEAR `yield/resume` как свою внутреннюю асинхронную границу, внешняя работа выполняется в TEE-воркерах, а защищённые секреты проходят через отдельный путь доверия, где TEE-keystore получает DAO-gated CKD через MPC signer. - -Для NEAR-разработчика здесь важна точность: мы не говорим, что ваш вызывающий контракт сам пишет `promise_yield_create`. Примитивы `yield/resume` в NEAR работают только в рамках одного и того же аккаунта, поэтому если этот механизм используется здесь, то yielding и resuming делает `outlayer.*`, а не исходный вызывающий контракт. Для сырой модели выполнения смотрите [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features). - -Документация Secrets / CKD описывает этот путь keystore как двухуровневый: сначала keystore получает derivation key через DAO-gated путь к MPC, а затем использует уже полученную derivation capability для защищённых секретов во время исполнений приложения. Это объяснение доверительной модели, а не утверждение, что каждое обычное исполнение OutLayer делает новый DAO -> MPC round trip. - -Публичный gateway-аккаунт для этого пути keystore / DAO в наших текущих публичных chain-данных всё ещё не подтверждён, поэтому эту часть надо держать в корзине «задокументировано внутри», а не в корзине «уже наблюдается сейчас». - -```mermaid -flowchart TB - subgraph Observable["Что уже можно наблюдать через FastNear / RPC"] - Caller["Пользователь или вызывающий контракт"] -->|FunctionCall request_execution| Entry["outlayer.*"] - Entry -->|поздняя активность воркера| Worker["worker.outlayer.*"] - Worker -->|resolve_execution или submit_execution_output_and_resolve| Entry - Entry -->|on_execution_response, списание, refund-переводы| Finish["Callback / finish receipt"] - end - - subgraph Internal["Что задокументировано как внутренний слой"] - Yield["внутренняя yield-точка в outlayer.*"] --> TEE["TEE-воркер исполняет WASM"] - TEE -->|resume с результатом| Yield - TEE --> Keystore["TEE-keystore для защищённых секретов"] - Keystore -->|задокументированный DAO-gated request_key| DAO["DAO gateway (задокументирован)"] - DAO -->|задокументированный CKD request| MPC["v1.signer / v1.signer-prod.testnet"] - MPC -->|derivation key для keystore| Keystore - end - - Entry -. та же логическая история исполнения .-> Yield - - classDef observable fill:#e7f1ff,stroke:#2563eb,color:#0f172a; - classDef internal fill:#fff4e5,stroke:#b45309,color:#0f172a; - class Caller,Entry,Worker,Finish observable; - class Yield,TEE,Keystore,DAO,MPC internal; -``` - -## Куда читать дальше - -- [Transactions API](https://docs.fastnear.com/ru/tx) для истории аккаунта, receipt и раскрытия транзакций -- [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features) для семантики `yield/resume` в NEAR -- [Асинхронная модель](https://docs.fastnear.com/ru/transaction-flow/async-model) для лексики promise и callback -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) для задокументированного контрактного интерфейса -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) для задокументированного пути keystore, DAO и MPC +- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +- [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) +- [RPC: view_account](https://docs.fastnear.com/ru/rpc/account/view-account) +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) diff --git a/static/ru/tx/examples/outlayer/index.md b/static/ru/tx/examples/outlayer/index.md index 804f0a3..4c7ea07 100644 --- a/static/ru/tx/examples/outlayer/index.md +++ b/static/ru/tx/examples/outlayer/index.md @@ -1,250 +1,138 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) -{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как использовать FastNear RPC и Transactions API, чтобы разбирать живое исполнение OutLayer в терминах NEAR. Он отделяет видимый request/worker/callback-поток, который уже можно трассировать через FastNear, от задокументированного внутреннего пути yield/resume и CKD/MPC. */} +{/* FASTNEAR_AI_DISCOVERY: Этот пример остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} -# OutLayer: как проследить один запрос от вызова до callback +# OutLayer: трассировка запроса и разрешения воркером -Используйте этот разбор, когда вопрос звучит так: «я вижу OutLayer в цепочке. Какая транзакция открыла работу, какая более поздняя транзакция пришла от воркера и где проявились callback, списание и возврат средств?» +Используйте этот сценарий, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» -Это продвинутый разбор асинхронного исполнения в семействе Transactions examples. Держите NEAR-рамку первой: один `FunctionCall` со стороны вызывающего, одна более поздняя транзакция со стороны воркера, и квитанции только тогда, когда действительно нужно разбирать фазу завершения. +Сначала смотрите на это как на задачу по истории транзакций: - Стратегия - Сначала найдите caller-транзакцию и worker-транзакцию, а receipts подключайте только тогда, когда настоящим вопросом становится finish-путь. - - 01POST /v0/account — самый быстрый способ найти caller-side и worker-side хеши из одной и той же истории. - 02POST /v0/transactions раскрывает оба хеша и показывает читаемые request, worker-resolution и ранние логи. - 03Только после этого имеет смысл разбирать callback, списание и refund на уровне receipts или уходить в точные RPC-проверки идентичности. - -Полезные ссылки: - -- [История аккаунта](https://docs.fastnear.com/ru/tx/account) -- [Транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) -- [Просмотр аккаунта](https://docs.fastnear.com/ru/rpc/account/view-account) -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) - -## Короткая версия +- один caller-side `request_execution` +- одна более поздняя worker-side транзакция `submit_execution_output_and_resolve` или `resolve_execution` +- переход к receipts только тогда, когда уже важен путь завершения -Если вы видите активность OutLayer в цепочке, практические вопросы обычно такие: - -- какая транзакция создала асинхронную единицу работы? -- какая более поздняя транзакция пришла от воркера? -- где именно проявились callback, списание и возврат средств? - -Это не вопрос о текущем состоянии. Это вопрос об истории исполнения. + Стратегия + Сначала найдите два хеша, затем раскройте их, а к worker-receipts переходите только тогда, когда нужен finish-путь. -Полезный ход через FastNear — связать одну транзакцию `request_execution` со стороны вызывающего с одной транзакцией разрешения со стороны воркера, а к receipt переходить только на этапе завершения. + 01POST /v0/account по outlayer.* — самый быстрый surface для поиска хешей. + 02POST /v0/transactions превращает caller-хеш и worker-хеш в читаемые signer, method и log-доказательства. + 03Разбирайте receipts worker-транзакции только тогда, когда реальный вопрос уже касается callback, списания или возврата средств. -```mermaid -sequenceDiagram - autonumber - participant Caller as "Вызывающая сторона" - participant Outlayer as "outlayer.*" - participant Worker as "worker.outlayer.*" - participant Near as "исполняющая среда NEAR" +**Цель** - Caller->>Outlayer: FunctionCall request_execution - Outlayer-->>Near: создаёт асинхронную работу и контекст учёта - Worker->>Outlayer: resolve_execution или submit_execution_output_and_resolve - Outlayer-->>Near: логи завершения, путь callback, списание, возвраты -``` +- Восстановить одну caller-side транзакцию, одну worker-side транзакцию и завершающие receipts, которые относятся к одному и тому же запросу OutLayer. -Всё это уже видно сегодня через FastNear и RPC. +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Поиск хешей | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Забираем недавние хеши транзакций для `outlayer.near` | Даёт самый быстрый локальный по контракту surface, когда пара хешей ещё не известна | +| Раскрытие транзакций | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Раскрываем caller-side и worker-side хеши вместе | Превращает сырые хеши в signer-, receiver-, action- и log-доказательства | +| Разбор finish-пути | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем worker-хеш и читаем список его receipts | Показывает, где материализовались callback, списание и возврат средств | +| Необязательная проверка идентичности | RPC [`view_account`](https://docs.fastnear.com/ru/rpc/account/view-account) | Идём в RPC только если следующий вопрос уже про идентичность контракта, а не про историю транзакций | Держит проверку текущего состояния отдельно от трассировки истории | -## 1. Раскройте одну транзакцию запроса и одно разрешение воркера - -Если хотите сразу увидеть всю форму потока, начните с уже известной пары хешей и раскройте оба. +## Проверенный shell-сценарий Эта пара работала 18 апреля 2026 года: -- `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` — `request_execution` со стороны вызывающего -- `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` — `submit_execution_output_and_resolve` со стороны воркера - -```bash title="Раскройте хеш запроса и хеш разрешения воркера" -curl -sS https://tx.main.fastnear.com/v0/transactions \ - -H 'content-type: application/json' \ - --data '{ - "tx_hashes":[ - "AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4", - "AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs" - ] - }' | jq '.transactions[] | { - hash: .transaction.hash, - signer: .transaction.signer_id, - receiver: .transaction.receiver_id, - actions: [.transaction.actions[] | keys[0]], - logs: (.receipts[0].execution_outcome.outcome.logs[:2]) - }' -``` - -В этом выборочном выводе: - -- хеш запроса шёл от `solarflux.near` к `outlayer.near` -- в логах фигурировал разрешённый проект: `zavodil.near/near-email` -- хеш воркера шёл от `worker.outlayer.near` к `outlayer.near` -- в логах воркера было `Stored pending output` и `Resolving execution ... (combined flow)` - -Этого уже достаточно для видимой истории в терминах NEAR: исходный `FunctionCall` создал асинхронную единицу работы, позже воркер вернулся как отдельный подписант, а контракт разрешил результат в цепочке. +- caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` +- worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` -Если копировать с этой страницы только одну команду, то именно эту. +### 1. Сразу раскройте caller-транзакцию и worker-транзакцию вместе -## 2. Найдите два нужных хеша сами +```bash +TX_BASE_URL=https://tx.main.fastnear.com +REQUEST_TX_HASH=AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4 +WORKER_TX_HASH=AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs -Если пары хешей у вас ещё нет, переключитесь на [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account). - -```bash title="Недавняя mainnet-активность для outlayer.near" -curl -sS https://tx.main.fastnear.com/v0/account \ +curl -sS "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ - --data '{"account_id":"outlayer.near","desc":true}' \ - | jq '{txs_count, first: .account_txs[0]}' -``` - -18 апреля 2026 года эта поверхность показывала более 5 000 трассированных транзакций для `outlayer.near`, а самый свежий выборочный хеш был таким: - -```text -AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs -``` - -Этот хеш не был исходным пользовательским запросом. Это уже было последующее действие со стороны воркера. - -Именно поэтому история аккаунта — правильный первый поиск: здесь задача не в том, чтобы описать контракт целиком, а в том, чтобы найти две конкретные транзакции в одной истории исполнения. - -```mermaid -flowchart TD - A["Transactions API: /v0/account для outlayer.*"] --> B["Найдите недавние хеши транзакций"] - B --> C["Хеш request_execution со стороны вызывающего"] - B --> D["Хеш разрешения со стороны воркера"] - C --> E["Раскройте оба через /v0/transactions"] - D --> E - E --> F["Проверьте логи, request_id, project/source, callback-receipt, списание и возвраты"] + --data "$(jq -nc --arg request_tx_hash "$REQUEST_TX_HASH" --arg worker_tx_hash "$WORKER_TX_HASH" '{ + tx_hashes: [$request_tx_hash, $worker_tx_hash] + }')" \ + | tee /tmp/outlayer-pair.json >/dev/null + +jq '{ + transactions: [ + .transactions[] + | { + hash: .transaction.hash, + signer_id: .transaction.signer_id, + receiver_id: .transaction.receiver_id, + actions: [.transaction.actions[] | keys[0]], + first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) + } + ] +}' /tmp/outlayer-pair.json ``` -## 3. Разберите фазу callback и возврата средств - -Если нужно пройти дальше, чем просто «воркер вернул результат», посмотрите список receipt у раскрытой воркерской транзакции. - -```bash title="Показать последующие действия на уровне receipt для разрешения воркером" -curl -sS https://tx.main.fastnear.com/v0/transactions \ - -H 'content-type: application/json' \ - --data '{"tx_hashes":["AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs"]}' \ - | jq '.transactions[0] | { - hash: .transaction.hash, - receipts: [ - .receipts[] | { - predecessor: .receipt.predecessor_id, - receiver: .receiver_id, - actions: [.receipt.receipt.Action.actions[] | keys[0]], - logs: .execution_outcome.outcome.logs - } - ] - }' +Что это доказывает: + +- запросная транзакция шла от `solarflux.near` к `outlayer.near` +- в логах запроса фигурировал проект `zavodil.near/near-email` +- worker-транзакция позже пришла от `worker.outlayer.near` к `outlayer.near` +- в логах воркера были `Stored pending output` и `Resolving execution ...` + +Это и есть главный наблюдаемый цикл в терминах NEAR: сначала caller-side запрос, затем более позднее worker-side разрешение. + +### 2. Читайте worker-receipts только тогда, когда уже важен finish-путь + +```bash +jq --arg worker_tx_hash "$WORKER_TX_HASH" ' + .transactions[] + | select(.transaction.hash == $worker_tx_hash) + | { + worker_tx_hash: .transaction.hash, + receipts: [ + .receipts[] + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + actions: [.receipt.receipt.Action.actions[] | keys[0]], + logs: .execution_outcome.outcome.logs + } + ] + } +' /tmp/outlayer-pair.json ``` На что смотреть: -- `FunctionCall:on_execution_response` +- `FunctionCall`-receipts, которые продолжают finish-путь - логи списания вроде `[[yNEAR charged: "..."]]` -- события завершения вроде `execution_completed` -- последующие receipt `Transfer` +- последующие `Transfer`-receipts, которые похожи на refund или settlement-движение -Здесь понятие receipt как раз становится правильной абстракцией: не в начале урока, а тогда, когда уже отлаживается реальный путь завершения. +Именно здесь receipts становятся правильной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции относятся к одному запросу OutLayer?» -## 4. Подтвердите контракт, если нужна точная проверка - -Если нужна точная проверка аккаунта и `code_hash`, используйте сырой RPC. Это шаг для проверки идентичности, а не для восстановления истории исполнения. - -```bash title="Mainnet: view_account для outlayer.near" -curl -sS https://rpc.mainnet.fastnear.com \ - -H 'content-type: application/json' \ - --data '{ - "jsonrpc":"2.0", - "id":"1", - "method":"query", - "params":{ - "request_type":"view_account", - "finality":"final", - "account_id":"outlayer.near" - } - }' | jq '.result | {amount, locked, code_hash, storage_usage}' -``` +### 3. Если двух хешей у вас ещё нет, сначала найдите их -```bash title="Testnet: view_account для outlayer.testnet" -curl -sS https://rpc.testnet.fastnear.com \ +```bash +curl -sS "$TX_BASE_URL/v0/account" \ -H 'content-type: application/json' \ - --data '{ - "jsonrpc":"2.0", - "id":"1", - "method":"query", - "params":{ - "request_type":"view_account", - "finality":"final", - "account_id":"outlayer.testnet" - } - }' | jq '.result | {amount, locked, code_hash, storage_usage}' + --data '{"account_id":"outlayer.near","desc":true,"limit":10}' \ + | jq '{ + txs_count, + recent_hashes: [.account_txs[:10][] | .transaction_hash] + }' ``` -По состоянию на 18 апреля 2026 года оба контракта возвращали один и тот же `code_hash`: - -```text -94uKcoDB3QbEpxDj1xsw9CQwu9bAY1PoVPr2BZYRRv4K -``` - -Это сильный сигнал, что на обеих сетях развёрнут один и тот же бинарник контракта. - -## 5. Что происходит внутри? +Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это уже surface, который превращает их в читаемое доказательство. -Видимая история выше — это то, что NEAR-разработчику нужно сначала. Более глубокий слой объясняет, почему этот поток вообще интересен. +## Граница сценария -### Что можно наблюдать уже сейчас +Этот сценарий намеренно остаётся в рамках публичных chain-данных, которые FastNear и RPC умеют показывать напрямую: -Через FastNear и RPC уже видно: +- caller-side транзакция запроса +- более поздняя worker-side транзакция разрешения +- finish-receipts и логи -- вызывающую транзакцию `request_execution` -- воркерскую `resolve_execution` или `submit_execution_output_and_resolve` -- завершающие receipt, где материализуются callback, списание и возврат средств +Он **не** пытается доказывать внутреннюю TEE-модель OutLayer, использование same-account `yield/resume` или путь доверия через CKD/MPC только по публичной трассе транзакций. Это отдельные архитектурные вопросы, и читать их нужно в документации OutLayer, а не считать доказанными этим trace-сценарием. -Интеграция вашего контракта при этом остаётся обычной асинхронной композицией в NEAR: вы вызываете `outlayer.*`, а потом обрабатываете свой callback. +## Полезные связанные страницы -### Что задокументировано как внутренний механизм - -Документация OutLayer описывает более глубокий внутренний слой: `outlayer.*` использует семантику NEAR `yield/resume` как свою внутреннюю асинхронную границу, внешняя работа выполняется в TEE-воркерах, а защищённые секреты проходят через отдельный путь доверия, где TEE-keystore получает DAO-gated CKD через MPC signer. - -Для NEAR-разработчика здесь важна точность: мы не говорим, что ваш вызывающий контракт сам пишет `promise_yield_create`. Примитивы `yield/resume` в NEAR работают только в рамках одного и того же аккаунта, поэтому если этот механизм используется здесь, то yielding и resuming делает `outlayer.*`, а не исходный вызывающий контракт. Для сырой модели выполнения смотрите [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features). - -Документация Secrets / CKD описывает этот путь keystore как двухуровневый: сначала keystore получает derivation key через DAO-gated путь к MPC, а затем использует уже полученную derivation capability для защищённых секретов во время исполнений приложения. Это объяснение доверительной модели, а не утверждение, что каждое обычное исполнение OutLayer делает новый DAO -> MPC round trip. - -Публичный gateway-аккаунт для этого пути keystore / DAO в наших текущих публичных chain-данных всё ещё не подтверждён, поэтому эту часть надо держать в корзине «задокументировано внутри», а не в корзине «уже наблюдается сейчас». - -```mermaid -flowchart TB - subgraph Observable["Что уже можно наблюдать через FastNear / RPC"] - Caller["Пользователь или вызывающий контракт"] -->|FunctionCall request_execution| Entry["outlayer.*"] - Entry -->|поздняя активность воркера| Worker["worker.outlayer.*"] - Worker -->|resolve_execution или submit_execution_output_and_resolve| Entry - Entry -->|on_execution_response, списание, refund-переводы| Finish["Callback / finish receipt"] - end - - subgraph Internal["Что задокументировано как внутренний слой"] - Yield["внутренняя yield-точка в outlayer.*"] --> TEE["TEE-воркер исполняет WASM"] - TEE -->|resume с результатом| Yield - TEE --> Keystore["TEE-keystore для защищённых секретов"] - Keystore -->|задокументированный DAO-gated request_key| DAO["DAO gateway (задокументирован)"] - DAO -->|задокументированный CKD request| MPC["v1.signer / v1.signer-prod.testnet"] - MPC -->|derivation key для keystore| Keystore - end - - Entry -. та же логическая история исполнения .-> Yield - - classDef observable fill:#e7f1ff,stroke:#2563eb,color:#0f172a; - classDef internal fill:#fff4e5,stroke:#b45309,color:#0f172a; - class Caller,Entry,Worker,Finish observable; - class Yield,TEE,Keystore,DAO,MPC internal; -``` - -## Куда читать дальше - -- [Transactions API](https://docs.fastnear.com/ru/tx) для истории аккаунта, receipt и раскрытия транзакций -- [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features) для семантики `yield/resume` в NEAR -- [Асинхронная модель](https://docs.fastnear.com/ru/transaction-flow/async-model) для лексики promise и callback -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) для задокументированного контрактного интерфейса -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) для задокументированного пути keystore, DAO и MPC +- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +- [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) +- [RPC: view_account](https://docs.fastnear.com/ru/rpc/account/view-account) +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) From 25bdd0112507c2a0c937b94eacc2346d64d59d47 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 22:38:52 -0700 Subject: [PATCH 14/35] docs: add quick-start examples to landing pages --- docs/api/examples.md | 31 ++- docs/fastdata/kv/examples.md | 27 +++ docs/neardata/examples.md | 18 ++ docs/rpc/examples.md | 32 ++- docs/snapshots/examples.mdx | 13 ++ docs/transfers/examples.md | 41 ++++ docs/tx/examples.md | 29 +++ .../current/api/examples.md | 31 ++- .../current/fastdata/kv/examples.md | 27 +++ .../current/neardata/examples.md | 18 ++ .../current/rpc/examples.md | 32 ++- .../current/snapshots/examples.mdx | 13 ++ .../current/transfers/examples.md | 41 ++++ .../current/tx/examples.md | 29 +++ static/ru/api/examples.md | 31 ++- static/ru/api/examples/index.md | 31 ++- static/ru/fastdata/kv/examples.md | 27 +++ static/ru/fastdata/kv/examples/index.md | 27 +++ static/ru/llms-full.txt | 191 +++++++++++++++++- static/ru/neardata/examples.md | 18 ++ static/ru/neardata/examples/index.md | 18 ++ static/ru/rpc/examples.md | 32 ++- static/ru/rpc/examples/index.md | 32 ++- static/ru/snapshots/examples.md | 13 ++ static/ru/snapshots/examples/index.md | 13 ++ static/ru/transfers/examples.md | 41 ++++ static/ru/transfers/examples/index.md | 41 ++++ static/ru/tx/examples.md | 29 +++ static/ru/tx/examples/index.md | 29 +++ 29 files changed, 940 insertions(+), 15 deletions(-) diff --git a/docs/api/examples.md b/docs/api/examples.md index 1bca905..caba272 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -8,9 +8,36 @@ page_actions: - markdown --- -## Worked walkthroughs +## Quick start + +Start with one identity lookup and one broad account read. + +```bash +API_BASE_URL=https://api.fastnear.com +PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' + +ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" + +ACCOUNT_ID="$( + curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ + | jq -r '.account_ids[0]' +)" -Read this page as a short ladder: first resolve who the account is, then classify the wallet shape, then use one richer provenance flow when you want to turn a live BOS artifact into a minted record. +echo "$ACCOUNT_ID" + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +``` + +This is the shortest path to “which account is this key?” and “what does that wallet look like right now?” + +## Worked walkthroughs ### Resolve a public key, then fetch the account snapshot diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index 2275c7d..ab7f85b 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -8,6 +8,33 @@ page_actions: - markdown --- +## Quick start + +If you already know the contract, predecessor, and exact row name, read that exact row first. + +```bash +KV_BASE_URL=https://kv.test.fastnear.com +CURRENT_ACCOUNT_ID=kv.gork-agent.testnet +PREDECESSOR_ID=YOUR_TESTNET_ACCOUNT +FASTDATA_ROW=value + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_ROW" \ + | jq '{ + latest_row: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' +``` + +This is the shortest FastData read on the page. The full walkthrough below adds a controlled write, exact-key history, and transaction provenance. + ## Worked investigation ### Write one testnet FastData row, then verify the exact indexed keys diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index 9b1db5f..cb587ab 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -8,6 +8,24 @@ page_actions: - markdown --- +## Quick start + +Start with the two helper routes that tell you what changed right now. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print "optimistic:", $2}' \ + | tr -d '\r' + +curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print "final:", $2}' \ + | tr -d '\r' +``` + +This gives you the current optimistic and final redirect targets before you fetch full block documents. + ## Worked investigation ### Catch a new block early, then confirm it after finality diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index 136d9bc..45df884 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -10,7 +10,37 @@ page_actions: # RPC Examples -Use this page when you already know the answer lives in RPC and you want the shortest path to it. The goal is not to memorize every method. It is to start with the right RPC read or write, stop as soon as the response answers the question, and only switch to a higher-level API when that would save time. +Use this page when you want one exact RPC answer fast. Start with one read, then move to transactions or raw state only when the simpler check stops being enough. + +## Quick start + +If you just landed here, start with one exact account read. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + finality: "final", + account_id: $account_id + } + }')" \ + | jq '.result | { + amount, + locked, + code_hash, + storage_usage + }' +``` + +This is the smallest reliable RPC example on the page: one request, one exact answer, no receipt tree. ## Transaction Submission and Tracking diff --git a/docs/snapshots/examples.mdx b/docs/snapshots/examples.mdx index 4ef654a..78b31c1 100644 --- a/docs/snapshots/examples.mdx +++ b/docs/snapshots/examples.mdx @@ -8,6 +8,19 @@ page_actions: - markdown --- +## Quick start + +If the job is simply “bring a mainnet RPC node back fast,” start with one runnable command. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash +``` + +This is the shortest recovery path on the page. The rest only matters when you need standard RPC or archival hot/cold recovery. + ## Worked investigation ### Choose and execute the right mainnet recovery path diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index 068449e..d5fe8cc 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -8,6 +8,47 @@ page_actions: - markdown --- +## Quick start + +Start with one tight outgoing window and print the rows before you chase receipts. + +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +FROM_TIMESTAMP_MS=1711929600000 +TO_TIMESTAMP_MS=1712016000000 + +curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | jq '{ + resume_token, + transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + asset_id, + amount, + other_account_id, + block_height + } + ] + }' +``` + +This is the shortest way to answer “did funds move here, and which receipt should I chase next?” + ## Worked walkthrough ### Find one suspicious transfer, then chase its receipt diff --git a/docs/tx/examples.md b/docs/tx/examples.md index 0672f9f..9dc159e 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -8,6 +8,35 @@ page_actions: - markdown --- +## Quick start + +Start with one tx hash and ask for the smallest readable answer first. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) + }' +``` + +This is the shortest investigation on the page. Only move to RPC or receipt IDs if this output is not enough. + If you want the longer case-study version of the same surface, jump to [Berry Club](/tx/examples/berry-club) for historical board reconstruction or [OutLayer](/tx/examples/outlayer) for worker and callback tracing. ## Start Here diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index 25b95b7..9fd0a4b 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -8,9 +8,36 @@ page_actions: - markdown --- -## Готовые сценарии +## Быстрый старт + +Начните с одного поиска по публичному ключу и одного широкого чтения аккаунта. + +```bash +API_BASE_URL=https://api.fastnear.com +PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' + +ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" + +ACCOUNT_ID="$( + curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ + | jq -r '.account_ids[0]' +)" -Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. +echo "$ACCOUNT_ID" + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +``` + +Это самый короткий путь к вопросам «какой это аккаунт?» и «что сейчас видно по этому кошельку?» + +## Готовые сценарии ### Определить аккаунт по публичному ключу, а затем получить сводку по нему diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index 7ffb910..c0a8286 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -8,6 +8,33 @@ page_actions: - markdown --- +## Быстрый старт + +Если контракт, predecessor и точное имя строки уже известны, сначала прочитайте именно эту строку. + +```bash +KV_BASE_URL=https://kv.test.fastnear.com +CURRENT_ACCOUNT_ID=kv.gork-agent.testnet +PREDECESSOR_ID=YOUR_TESTNET_ACCOUNT +FASTDATA_ROW=value + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_ROW" \ + | jq '{ + latest_row: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' +``` + +Это самое короткое чтение FastData на странице. Полный walkthrough ниже добавляет управляемую запись, историю точного ключа и привязку к транзакции. + ## Готовое расследование ### Сделать одну testnet-запись FastData и проверить точные индексированные ключи diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index e13fb89..ae0c4a0 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -8,6 +8,24 @@ page_actions: - markdown --- +## Быстрый старт + +Начните с двух helper-маршрутов, которые показывают, что изменилось прямо сейчас. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print "optimistic:", $2}' \ + | tr -d '\r' + +curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print "final:", $2}' \ + | tr -d '\r' +``` + +Это даёт текущие optimistic и final redirect target до того, как вы запрашиваете полные документы блоков. + ## Готовое расследование ### Поймать новый блок как можно раньше, а затем подтвердить его после finality diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index d9798dd..ead737a 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -10,7 +10,37 @@ page_actions: # Примеры RPC -Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. +Используйте эту страницу, когда нужен быстрый точный ответ через RPC. Начните с одного чтения, а к транзакциям и сырому состоянию переходите только если простого запроса уже недостаточно. + +## Быстрый старт + +Если вы только открыли эту страницу, начните с одного точного чтения аккаунта. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + finality: "final", + account_id: $account_id + } + }')" \ + | jq '.result | { + amount, + locked, + code_hash, + storage_usage + }' +``` + +Это самый маленький надёжный RPC-пример на странице: один запрос, один точный ответ, без дерева receipts. ## Отправка и отслеживание транзакции diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx index d364d1a..8c35b98 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx @@ -8,6 +8,19 @@ page_actions: - markdown --- +## Быстрый старт + +Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash +``` + +Это самый короткий путь восстановления на странице. Всё остальное нужно только если вам нужен standard RPC или архивный hot/cold-сценарий. + ## Готовое расследование ### Выбрать и выполнить правильный сценарий восстановления mainnet diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md index a72576f..c26496c 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -8,6 +8,47 @@ page_actions: - markdown --- +## Быстрый старт + +Начните с узкого окна исходящих переводов и сначала выведите строки, а уже потом переходите к receipts. + +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +FROM_TIMESTAMP_MS=1711929600000 +TO_TIMESTAMP_MS=1712016000000 + +curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | jq '{ + resume_token, + transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + asset_id, + amount, + other_account_id, + block_height + } + ] + }' +``` + +Это самый короткий путь к вопросу «были ли здесь движения средств и какой receipt брать следующим?» + ## Готовый сценарий ### Найти один подозрительный перевод, а затем пройти по его receipt diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index a94cf58..ff11430 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -8,6 +8,35 @@ page_actions: - markdown --- +## Быстрый старт + +Начните с одного tx hash и сначала получите самый короткий читаемый ответ. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) + }' +``` + +Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. + Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](/tx/examples/outlayer) для трассировки воркера и callback-цепочки. ## С чего начать diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index 5eab573..8708265 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -1,8 +1,35 @@ **Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) -## Готовые сценарии +## Быстрый старт + +Начните с одного поиска по публичному ключу и одного широкого чтения аккаунта. + +```bash +API_BASE_URL=https://api.fastnear.com +PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' + +ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" + +ACCOUNT_ID="$( + curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ + | jq -r '.account_ids[0]' +)" -Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. +echo "$ACCOUNT_ID" + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +``` + +Это самый короткий путь к вопросам «какой это аккаунт?» и «что сейчас видно по этому кошельку?» + +## Готовые сценарии ### Определить аккаунт по публичному ключу, а затем получить сводку по нему diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index 5eab573..8708265 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -1,8 +1,35 @@ **Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) -## Готовые сценарии +## Быстрый старт + +Начните с одного поиска по публичному ключу и одного широкого чтения аккаунта. + +```bash +API_BASE_URL=https://api.fastnear.com +PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' + +ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" + +ACCOUNT_ID="$( + curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ + | jq -r '.account_ids[0]' +)" -Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. +echo "$ACCOUNT_ID" + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +``` + +Это самый короткий путь к вопросам «какой это аккаунт?» и «что сейчас видно по этому кошельку?» + +## Готовые сценарии ### Определить аккаунт по публичному ключу, а затем получить сводку по нему diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index 4f1f8c0..8d0122c 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -1,5 +1,32 @@ **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) +## Быстрый старт + +Если контракт, predecessor и точное имя строки уже известны, сначала прочитайте именно эту строку. + +```bash +KV_BASE_URL=https://kv.test.fastnear.com +CURRENT_ACCOUNT_ID=kv.gork-agent.testnet +PREDECESSOR_ID=YOUR_TESTNET_ACCOUNT +FASTDATA_ROW=value + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_ROW" \ + | jq '{ + latest_row: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' +``` + +Это самое короткое чтение FastData на странице. Полный walkthrough ниже добавляет управляемую запись, историю точного ключа и привязку к транзакции. + ## Готовое расследование ### Сделать одну testnet-запись FastData и проверить точные индексированные ключи diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index 4f1f8c0..8d0122c 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -1,5 +1,32 @@ **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) +## Быстрый старт + +Если контракт, predecessor и точное имя строки уже известны, сначала прочитайте именно эту строку. + +```bash +KV_BASE_URL=https://kv.test.fastnear.com +CURRENT_ACCOUNT_ID=kv.gork-agent.testnet +PREDECESSOR_ID=YOUR_TESTNET_ACCOUNT +FASTDATA_ROW=value + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_ROW" \ + | jq '{ + latest_row: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' +``` + +Это самое короткое чтение FastData на странице. Полный walkthrough ниже добавляет управляемую запись, историю точного ключа и привязку к транзакции. + ## Готовое расследование ### Сделать одну testnet-запись FastData и проверить точные индексированные ключи diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index e4bf361..46fe899 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -994,9 +994,36 @@ https://test.api.fastnear.com **Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) -## Готовые сценарии +## Быстрый старт + +Начните с одного поиска по публичному ключу и одного широкого чтения аккаунта. -Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. +```bash +API_BASE_URL=https://api.fastnear.com +PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' + +ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" + +ACCOUNT_ID="$( + curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ + | jq -r '.account_ids[0]' +)" + +echo "$ACCOUNT_ID" + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +``` + +Это самый короткий путь к вопросам «какой это аккаунт?» и «что сейчас видно по этому кошельку?» + +## Готовые сценарии ### Определить аккаунт по публичному ключу, а затем получить сводку по нему @@ -1610,6 +1637,33 @@ https://kv.test.fastnear.com **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) +## Быстрый старт + +Если контракт, predecessor и точное имя строки уже известны, сначала прочитайте именно эту строку. + +```bash +KV_BASE_URL=https://kv.test.fastnear.com +CURRENT_ACCOUNT_ID=kv.gork-agent.testnet +PREDECESSOR_ID=YOUR_TESTNET_ACCOUNT +FASTDATA_ROW=value + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_ROW" \ + | jq '{ + latest_row: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' +``` + +Это самое короткое чтение FastData на странице. Полный walkthrough ниже добавляет управляемую запись, историю точного ключа и привязку к транзакции. + ## Готовое расследование ### Сделать одну testnet-запись FastData и проверить точные индексированные ключи @@ -2233,6 +2287,24 @@ https://testnet.neardata.xyz **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) +## Быстрый старт + +Начните с двух helper-маршрутов, которые показывают, что изменилось прямо сейчас. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print "optimistic:", $2}' \ + | tr -d '\r' + +curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print "final:", $2}' \ + | tr -d '\r' +``` + +Это даёт текущие optimistic и final redirect target до того, как вы запрашиваете полные документы блоков. + ## Готовое расследование ### Поймать новый блок как можно раньше, а затем подтвердить его после finality @@ -2588,7 +2660,37 @@ https://archival-rpc.testnet.fastnear.com # Примеры RPC -Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. +Используйте эту страницу, когда нужен быстрый точный ответ через RPC. Начните с одного чтения, а к транзакциям и сырому состоянию переходите только если простого запроса уже недостаточно. + +## Быстрый старт + +Если вы только открыли эту страницу, начните с одного точного чтения аккаунта. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + finality: "final", + account_id: $account_id + } + }')" \ + | jq '.result | { + amount, + locked, + code_hash, + storage_usage + }' +``` + +Это самый маленький надёжный RPC-пример на странице: один запрос, один точный ответ, без дерева receipts. ## Отправка и отслеживание транзакции @@ -4262,6 +4364,19 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash **Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) +## Быстрый старт + +Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash +``` + +Это самый короткий путь восстановления на странице. Всё остальное нужно только если вам нужен standard RPC или архивный hot/cold-сценарий. + ## Готовое расследование ### Выбрать и выполнить правильный сценарий восстановления mainnet @@ -4748,6 +4863,47 @@ https://transfers.main.fastnear.com **Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) +## Быстрый старт + +Начните с узкого окна исходящих переводов и сначала выведите строки, а уже потом переходите к receipts. + +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +FROM_TIMESTAMP_MS=1711929600000 +TO_TIMESTAMP_MS=1712016000000 + +curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | jq '{ + resume_token, + transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + asset_id, + amount, + other_account_id, + block_height + } + ] + }' +``` + +Это самый короткий путь к вопросу «были ли здесь движения средств и какой receipt брать следующим?» + ## Готовый сценарий ### Найти один подозрительный перевод, а затем пройти по его receipt @@ -4972,6 +5128,35 @@ https://tx.test.fastnear.com **Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) +## Быстрый старт + +Начните с одного tx hash и сначала получите самый короткий читаемый ответ. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) + }' +``` + +Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. + Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. ## С чего начать diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index 1b710d3..e44bd50 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -1,5 +1,23 @@ **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) +## Быстрый старт + +Начните с двух helper-маршрутов, которые показывают, что изменилось прямо сейчас. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print "optimistic:", $2}' \ + | tr -d '\r' + +curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print "final:", $2}' \ + | tr -d '\r' +``` + +Это даёт текущие optimistic и final redirect target до того, как вы запрашиваете полные документы блоков. + ## Готовое расследование ### Поймать новый блок как можно раньше, а затем подтвердить его после finality diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index 1b710d3..e44bd50 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -1,5 +1,23 @@ **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) +## Быстрый старт + +Начните с двух helper-маршрутов, которые показывают, что изменилось прямо сейчас. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print "optimistic:", $2}' \ + | tr -d '\r' + +curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print "final:", $2}' \ + | tr -d '\r' +``` + +Это даёт текущие optimistic и final redirect target до того, как вы запрашиваете полные документы блоков. + ## Готовое расследование ### Поймать новый блок как можно раньше, а затем подтвердить его после finality diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index 52456e4..6438b2b 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -2,7 +2,37 @@ # Примеры RPC -Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. +Используйте эту страницу, когда нужен быстрый точный ответ через RPC. Начните с одного чтения, а к транзакциям и сырому состоянию переходите только если простого запроса уже недостаточно. + +## Быстрый старт + +Если вы только открыли эту страницу, начните с одного точного чтения аккаунта. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + finality: "final", + account_id: $account_id + } + }')" \ + | jq '.result | { + amount, + locked, + code_hash, + storage_usage + }' +``` + +Это самый маленький надёжный RPC-пример на странице: один запрос, один точный ответ, без дерева receipts. ## Отправка и отслеживание транзакции diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index 52456e4..6438b2b 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -2,7 +2,37 @@ # Примеры RPC -Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. +Используйте эту страницу, когда нужен быстрый точный ответ через RPC. Начните с одного чтения, а к транзакциям и сырому состоянию переходите только если простого запроса уже недостаточно. + +## Быстрый старт + +Если вы только открыли эту страницу, начните с одного точного чтения аккаунта. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + finality: "final", + account_id: $account_id + } + }')" \ + | jq '.result | { + amount, + locked, + code_hash, + storage_usage + }' +``` + +Это самый маленький надёжный RPC-пример на странице: один запрос, один точный ответ, без дерева receipts. ## Отправка и отслеживание транзакции diff --git a/static/ru/snapshots/examples.md b/static/ru/snapshots/examples.md index 650c732..1e39417 100644 --- a/static/ru/snapshots/examples.md +++ b/static/ru/snapshots/examples.md @@ -1,5 +1,18 @@ **Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) +## Быстрый старт + +Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash +``` + +Это самый короткий путь восстановления на странице. Всё остальное нужно только если вам нужен standard RPC или архивный hot/cold-сценарий. + ## Готовое расследование ### Выбрать и выполнить правильный сценарий восстановления mainnet diff --git a/static/ru/snapshots/examples/index.md b/static/ru/snapshots/examples/index.md index 650c732..1e39417 100644 --- a/static/ru/snapshots/examples/index.md +++ b/static/ru/snapshots/examples/index.md @@ -1,5 +1,18 @@ **Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) +## Быстрый старт + +Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash +``` + +Это самый короткий путь восстановления на странице. Всё остальное нужно только если вам нужен standard RPC или архивный hot/cold-сценарий. + ## Готовое расследование ### Выбрать и выполнить правильный сценарий восстановления mainnet diff --git a/static/ru/transfers/examples.md b/static/ru/transfers/examples.md index b898351..ba49ebf 100644 --- a/static/ru/transfers/examples.md +++ b/static/ru/transfers/examples.md @@ -1,5 +1,46 @@ **Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) +## Быстрый старт + +Начните с узкого окна исходящих переводов и сначала выведите строки, а уже потом переходите к receipts. + +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +FROM_TIMESTAMP_MS=1711929600000 +TO_TIMESTAMP_MS=1712016000000 + +curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | jq '{ + resume_token, + transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + asset_id, + amount, + other_account_id, + block_height + } + ] + }' +``` + +Это самый короткий путь к вопросу «были ли здесь движения средств и какой receipt брать следующим?» + ## Готовый сценарий ### Найти один подозрительный перевод, а затем пройти по его receipt diff --git a/static/ru/transfers/examples/index.md b/static/ru/transfers/examples/index.md index b898351..ba49ebf 100644 --- a/static/ru/transfers/examples/index.md +++ b/static/ru/transfers/examples/index.md @@ -1,5 +1,46 @@ **Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) +## Быстрый старт + +Начните с узкого окна исходящих переводов и сначала выведите строки, а уже потом переходите к receipts. + +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +FROM_TIMESTAMP_MS=1711929600000 +TO_TIMESTAMP_MS=1712016000000 + +curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | jq '{ + resume_token, + transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + asset_id, + amount, + other_account_id, + block_height + } + ] + }' +``` + +Это самый короткий путь к вопросу «были ли здесь движения средств и какой receipt брать следующим?» + ## Готовый сценарий ### Найти один подозрительный перевод, а затем пройти по его receipt diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index 9caf20e..412cfab 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -1,5 +1,34 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) +## Быстрый старт + +Начните с одного tx hash и сначала получите самый короткий читаемый ответ. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) + }' +``` + +Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. + Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. ## С чего начать diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index 9caf20e..412cfab 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -1,5 +1,34 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) +## Быстрый старт + +Начните с одного tx hash и сначала получите самый короткий читаемый ответ. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) + }' +``` + +Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. + Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. ## С чего начать From 19a70ad102ec1918e0b02a7930955a7e91f92481 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sun, 19 Apr 2026 09:40:06 -0700 Subject: [PATCH 15/35] docs: simplify examples around common developer jobs --- docs/api/examples.md | 395 +- docs/fastdata/kv/examples.md | 313 +- docs/neardata/examples.md | 318 +- docs/rpc/examples.md | 1403 +----- docs/snapshots/examples.mdx | 75 +- docs/transaction-flow/advanced-features.mdx | 49 +- docs/transaction-flow/finality.mdx | 11 +- docs/transaction-flow/foundations.mdx | 34 +- docs/transaction-flow/gas-economics.mdx | 39 +- docs/transaction-flow/index.mdx | 13 +- docs/transaction-flow/infrastructure.mdx | 11 +- docs/transaction-flow/network-blocks.mdx | 69 +- docs/transaction-flow/runtime-execution.mdx | 15 +- docs/transfers/examples.md | 151 +- docs/tx/berry-club.mdx | 253 +- docs/tx/examples.md | 1167 ++--- docs/tx/outlayer.mdx | 57 +- docs/tx/socialdb-proofs.mdx | 198 + .../current/api/examples.md | 395 +- .../current/fastdata/kv/examples.md | 313 +- .../current/neardata/examples.md | 318 +- .../current/rpc/examples.md | 1401 +----- .../current/snapshots/examples.mdx | 75 +- .../current/transfers/examples.md | 151 +- .../current/tx/berry-club.mdx | 253 +- .../current/tx/examples.md | 1167 ++--- .../current/tx/outlayer.mdx | 59 +- .../current/tx/socialdb-proofs.mdx | 198 + scripts/generate-ai-surfaces.js | 7 + src/components/BerryClubLiveBoard/index.js | 225 + .../BerryClubLiveBoard/styles.module.css | 106 + static/ru/api/examples.md | 393 +- static/ru/api/examples/index.md | 393 +- static/ru/fastdata/kv/examples.md | 312 +- static/ru/fastdata/kv/examples/index.md | 312 +- static/ru/guides/llms.txt | 13 +- static/ru/llms-full.txt | 4360 +++++------------ static/ru/llms.txt | 13 +- static/ru/neardata/examples.md | 317 +- static/ru/neardata/examples/index.md | 317 +- static/ru/rpc/examples.md | 1379 +----- static/ru/rpc/examples/index.md | 1379 +----- static/ru/snapshots/examples.md | 76 +- static/ru/snapshots/examples/index.md | 76 +- static/ru/structured-data/site-graph.json | 13 + static/ru/transfers/examples.md | 152 +- static/ru/transfers/examples/index.md | 152 +- static/ru/tx/examples.md | 1141 ++--- static/ru/tx/examples/berry-club.md | 213 +- static/ru/tx/examples/berry-club/index.md | 213 +- static/ru/tx/examples/index.md | 1141 ++--- static/ru/tx/examples/outlayer.md | 48 +- static/ru/tx/examples/outlayer/index.md | 48 +- static/ru/tx/socialdb-proofs.md | 191 + static/ru/tx/socialdb-proofs/index.md | 191 + 55 files changed, 6452 insertions(+), 15630 deletions(-) create mode 100644 docs/tx/socialdb-proofs.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx create mode 100644 src/components/BerryClubLiveBoard/index.js create mode 100644 src/components/BerryClubLiveBoard/styles.module.css create mode 100644 static/ru/tx/socialdb-proofs.md create mode 100644 static/ru/tx/socialdb-proofs/index.md diff --git a/docs/api/examples.md b/docs/api/examples.md index caba272..0a42e04 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /api/examples title: API Examples -description: Plain-language workflows for using FastNear API docs for account lookups, holdings checks, NFT gating, and staking classification. +description: Plain-language workflows for using FastNear API docs for account lookups, asset inventory, and staking classification. displayed_sidebar: fastnearApiSidebar page_actions: - markdown @@ -91,19 +91,19 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ The public-key lookup tells you which account you are dealing with. The full account snapshot is the natural next read when you want balances, NFTs, staking, and pools in one response. If the key maps to multiple accounts instead of one, move to [V1 Public Key Lookup All](/api/v1/public-key-all) or loop through each returned `account_id`. -### Am I locked or liquid? +### Does this account have direct staking right now? -Use this when the user story is “show me whether this wallet is exposed through direct staking pools, liquid staking tokens, or both.” +Use this when the user story is simple: “tell me whether this account has visible direct staking pools right now, and show me which pools they are.”
Strategy -

Compare staking positions and FT balances before you interpret the wallet.

+

Read the staking endpoint once, then turn the visible pool list into a yes/no answer.

-

01GET /v1/account/.../staking finds direct pool exposure.

-

02GET /v1/account/.../ft finds liquid staking tokens that sit beside or instead of direct pools.

-

03jq turns those two indexed reads into direct_only, liquid_only, or mixed.

+

01GET /v1/account/.../staking returns the account’s visible direct staking positions.

+

02jq turns that response into has_direct_staking_now, pool_count, and pool_ids.

+

03If pools is empty, the answer from this surface is simply “no visible direct staking right now.”

@@ -114,350 +114,159 @@ Use this when the user story is “show me whether this wallet is exposed throug **Official references** - [Validator staking](https://docs.near.org/concepts/basics/staking) -- [Using liquid staking](https://docs.near.org/primitives/liquid-staking) - -This example is intentionally observational. It classifies what FastNear can see from staking positions and FT balances today. It does not prove every possible synthetic or off-platform staking exposure. **What you're doing** - Read indexed direct staking positions from the account staking endpoint. -- Read indexed FT balances from the account FT endpoint. -- Classify the account into `direct_only`, `liquid_only`, `mixed`, or `no_visible_staking_position`. -- Print the direct pool list and the liquid staking token list that informed the classification. +- Print a small yes/no summary plus the visible pool IDs. +- Stop there unless the next question becomes pool-specific unstake or withdraw timing. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +ACCOUNT_ID=mike.near ``` 1. Fetch the direct staking view. ```bash curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` + | tee /tmp/account-staking.json >/dev/null -2. Fetch fungible token balances so you can detect liquid staking positions. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null +jq '{ + account_id, + has_direct_staking_now: ((.pools // []) | length > 0), + pool_count: ((.pools // []) | length), + pool_ids: ((.pools // []) | map(.pool_id)) +}' /tmp/account-staking.json ``` -3. Classify the account from those two indexed views. - -```bash -jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ - --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens - | { - classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) - }' -``` +At the time of writing, `mike.near` returned visible direct staking pools here. If `pool_ids` comes back empty for your target account, this endpoint is answering “no visible direct staking right now.” **Why this next step?** -If the classification is `direct_only`, the next operational question is usually about unstake and withdraw timing. If it is `liquid_only`, the next question is usually about redeeming or swapping the liquid token. If it is `mixed`, you should treat those as two separate exit paths rather than assuming one flow covers both. +This keeps the question narrow and operational. If the answer is `true`, the next practical step is usually pool-specific unstake or withdraw work. If the answer is `false`, do not infer liquid staking from this example alone; this example is only about direct pool positions. -### Archive a BOS widget version as a provenance NFT +### What FT balances and NFT collections does this account show right now? -Use this when the user story is “this BOS widget is a real on-chain artifact. Mint an NFT that records exactly which version I archived.” +Use this when a wallet view, support tool, or agent already has an `account_id` and needs a fast indexed holdings summary: FT balances plus the NFT collections this account currently shows holdings from.
Strategy -

Read the exact widget first, then mint only after the provenance fields are deterministic.

+

Read FT balances first, read NFT collections second, then combine them into one compact indexed inventory.

-

01GET /v1/account/.../nft checks whether the receiver already shows a holding from this archive collection.

-

02RPC call_function get on social.near reads the exact widget source and its SocialDB write block.

-

03Hash the source, mint nft_mint on testnet, then verify the exact provenance fields through nft_token.

+

01GET /v1/account/.../ft gives the wallet’s indexed FT balances.

+

02GET /v1/account/.../nft gives the NFT collections the wallet currently shows holdings from.

+

03jq turns those two indexed reads into one wallet-friendly inventory.

-**Networks** - -- mainnet for reading the widget from `social.near` -- testnet for safely minting the provenance NFT on `nft.examples.testnet` - -**Official references** - -- [Pre-deployed NFT contract](https://docs.near.org/tutorials/nfts/js/predeployed-contract) -- [NEP-171 NFT standard](https://docs.near.org/primitives/nft/standard) -- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) - **What you're doing** -- Check whether the receiver already shows a holding from the archive collection. -- Read one exact BOS widget from `social.near`, including its widget-level SocialDB block. -- Hash the widget source and turn that into provenance metadata. -- Mint a testnet NFT whose metadata records the author, widget path, SocialDB block, and source hash. -- Verify that the minted token still carries those provenance fields. +- Read the account’s fungible token balances. +- Read the account’s NFT collection-level holdings. +- Print one short indexed inventory that a wallet or support flow could reuse. -Pinned source widget: +This example does not answer native balance, staking, pools, exact NFT token IDs, or metadata. -- author account: `mob.near` -- widget path: `mob.near/widget/Profile` -- widget-level SocialDB block: `86494825` +The NFT endpoint here is collection-level. Treat it as “which NFT contracts does this account currently hold from?” rather than a full per-token crawl. ```bash -API_BASE_URL=https://test.api.fastnear.com -MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com -TESTNET_RPC_URL=https://rpc.testnet.fastnear.com -AUTHOR_ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -DESTINATION_COLLECTION_ID=nft.examples.testnet -RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID + +# Sample live value observed on April 19, 2026: +# ACCOUNT_ID=mike.near ``` -1. Use FastNear API to see whether the receiver already shows a holding from the archive collection. +1. Read fungible token balances for the account. ```bash -curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ - | tee /tmp/provenance-account-nfts.json >/dev/null - -jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_collection_entries: [ - .tokens[]? - | select(.contract_id == $destination_collection_id) - | { +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null + +jq '{ + account_id, + ft_contracts: ( + .tokens + | map(select((.balance // "0") != "0") | { contract_id, + balance, last_update_block_height - } - ] -}' /tmp/provenance-account-nfts.json + }) + | .[:10] + ) +}' /tmp/account-ft.json ``` -This is a quick collection-presence check, not an exact token inventory. The exact minted token is verified later through `nft_token`. - -2. Read the exact widget body and widget-level SocialDB block from mainnet. +2. Read NFT collection holdings for the same account. ```bash -WIDGET_ARGS_BASE64="$( - jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($author_account_id + "/widget/" + $widget_name)], - options: {with_block_height: true} - }' | base64 | tr -d '\n' -)" +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | tee /tmp/account-nft.json >/dev/null -curl -s "$MAINNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget.json >/dev/null - -jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - widget_path: ($author_account_id + "/widget/" + $widget_name), - socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], - source_preview: ( - .[$author_account_id].widget[$widget_name][""] - | split("\n")[0:8] +jq '{ + account_id, + nft_collections: ( + (.tokens // []) + | map({ + contract_id, + last_update_block_height + }) + | unique_by(.contract_id) + | .[:10] ) -}' /tmp/bos-widget.json +}' /tmp/account-nft.json ``` -3. Hash the widget source and build deterministic provenance metadata. +3. Turn those two reads into one compact inventory. ```bash -jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][""] -' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx - -WIDGET_BLOCK_HEIGHT="$( - jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][":block"] - ' /tmp/bos-widget.json -)" - -SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" -SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" -TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" - -PROVENANCE_METADATA_JSON="$( - jq -nc \ - --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" \ - --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ - --arg block_height "$WIDGET_BLOCK_HEIGHT" \ - --arg source_sha256 "$SOURCE_SHA256" '{ - title: ("BOS widget archive: " + $widget_path), - description: ("Archived from social.near on mainnet at block " + $block_height), - copies: 1, - extra: ({ - author_account_id: $author_account_id, - widget_name: $widget_name, - widget_path: $widget_path, - source_contract_id: "social.near", - source_network: "mainnet", - socialdb_block_height: ($block_height | tonumber), - source_sha256: $source_sha256 - } | @json) +jq -n \ + --slurpfile ft /tmp/account-ft.json \ + --slurpfile nft /tmp/account-nft.json ' + ($ft[0].tokens // []) as $ft_tokens + | ($nft[0].tokens // []) as $nft_tokens + | { + account_id: ($ft[0].account_id // $nft[0].account_id), + ft_contract_count: ( + $ft_tokens + | map(select((.balance // "0") != "0")) + | length + ), + nft_collection_count: ( + $nft_tokens + | map(.contract_id) + | unique + | length + ), + ft_contracts: ( + $ft_tokens + | map(select((.balance // "0") != "0") | { + contract_id, + balance, + last_update_block_height + }) + | .[:10] + ), + nft_collections: ( + $nft_tokens + | map({ + contract_id, + last_update_block_height + }) + | unique_by(.contract_id) + | .[:10] + ) }' -)" - -printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' -``` - -4. Mint the provenance NFT on testnet. - -```bash -near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ - --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --argjson metadata "$PROVENANCE_METADATA_JSON" '{ - token_id: $token_id, - receiver_id: $receiver_id, - metadata: $metadata - }')" \ - --accountId "$SIGNER_ACCOUNT_ID" \ - --deposit 0.1 \ - --networkId testnet ``` -5. Verify through the exact `nft_token` read that the minted NFT carries the provenance fields you expect. - -Poll a few times instead of assuming failure if the token does not appear immediately after the mint transaction returns. - -```bash -NFT_TOKEN_ARGS_BASE64="$( - jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ - | base64 | tr -d '\n' -)" - -for attempt in 1 2 3 4 5; do - curl -s "$TESTNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_token", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget-provenance-token.json >/dev/null - - if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then - break - fi - - sleep 1 -done - -jq '{ - token_id, - owner_id, - title: .metadata.title, - provenance: (.metadata.extra | fromjson) -}' /tmp/bos-widget-provenance-token.json -``` +For `mike.near` on April 19, 2026, these reads returned dozens of FT contracts and NFT collections. That is enough for the common wallet question: “which FT balances and NFT collections does this account currently show?” **Why this next step?** -FastNear API gives you the quick receiver-side collection check. Mainnet RPC gives you the exact widget body and SocialDB block. The exact `nft_token` read on testnet confirms that minting turned that into a durable NFT record. If you later want to prove which historical transaction wrote the widget, hand off to the NEAR Social proof investigations on [Transactions API examples](/tx/examples). - -## Common jobs - -### What does this account actually hold right now? - -**Start here** - -- [V1 Full Account View](/api/v1/account-full) when you want the fastest readable answer to “what is in this account right now?” - -**Next page if needed** - -- [V1 Account FT](/api/v1/account-ft), [V1 Account NFT](/api/v1/account-nft), or [V1 Account Staking](/api/v1/account-staking) if the broad summary is useful but you now want to stay on just one asset family. -- [Transactions API account history](/tx/account) if the next question becomes “how did this account get here?” instead of “what does it hold?” - -**Stop when** - -- The summary already answers the holdings question in one response. - -**Switch when** - -- The user asks for exact account state, access-key semantics, or protocol-native fields. Move to [RPC Reference](/rpc). -- The user asks for activity or execution history rather than current holdings. Move to [Transactions API](/tx). - -### Resolve a public key to one or more accounts - -**Start here** - -- [V1 Public Key Lookup](/api/v1/public-key) when you want the primary account match. -- [V1 Public Key Lookup All](/api/v1/public-key-all) when you need the full set of associated accounts. - -**Next page if needed** - -- [V1 Full Account View](/api/v1/account-full) after resolution if the user immediately wants balances or holdings for the returned accounts. - -**Stop when** - -- You have identified the account or accounts that belong to the key. - -**Switch when** - -- The user starts asking about exact access-key permissions, nonces, or current key state. Move to [View Access Key](/rpc/account/view-access-key) or [View Access Key List](/rpc/account/view-access-key-list). -- The user wants recent activity for the resolved accounts rather than just identity resolution. Move to [Transactions API](/tx). - -### Does this account hold FTs, NFTs, or staking positions? - -**Start here** - -- [V1 Account FT](/api/v1/account-ft) when the question is just about fungible-token balances. -- [V1 Account NFT](/api/v1/account-nft) when the question is specifically about NFT holdings. -- [V1 Account Staking](/api/v1/account-staking) when the question is really about staking positions, not the whole wallet picture. - -**Next page if needed** - -- [V1 Full Account View](/api/v1/account-full) if the user later wants the whole account picture after starting from one asset family. -- [Transactions API account history](/tx/account) if the user stops asking “what does this account hold?” and starts asking how it got there. - -**Stop when** - -- The asset-specific endpoint already answers the holdings question without making you rebuild the whole account picture. - -**Switch when** - -- The indexed view is not enough and the user needs the exact on-chain answer. Move to [RPC Reference](/rpc). -- The question becomes historical or execution-oriented instead of “what does this account hold now?” Move to [Transactions API](/tx). +Use [`GET /v1/account/{account_id}/full`](/api/v1/account-full) when the next question also needs staking, pools, or native account state. Use contract-specific reads only when the question becomes “which exact NFT token IDs or metadata do I own?” ## Common mistakes diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index ab7f85b..c067a95 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: KV FastData Examples -description: Plain-language workflows for verifying exact FastData keys, following exact-key history, and bridging indexed rows back to the originating transaction. +description: Plain-language workflows for reading exact FastData rows, checking exact-key history, and tracing one indexed row back to its originating transaction. displayed_sidebar: kvFastDataSidebar page_actions: - markdown @@ -10,18 +10,24 @@ page_actions: ## Quick start -If you already know the contract, predecessor, and exact row name, read that exact row first. +If you already know the exact FastData keys you care about, read them directly. ```bash KV_BASE_URL=https://kv.test.fastnear.com CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=YOUR_TESTNET_ACCOUNT -FASTDATA_ROW=value +PREDECESSOR_ID=kv.gork-agent.testnet -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_ROW" \ +curl -s "$KV_BASE_URL/v0/multi" \ + -H 'content-type: application/json' \ + --data '{ + "keys": [ + "kv.gork-agent.testnet/kv.gork-agent.testnet/key", + "kv.gork-agent.testnet/kv.gork-agent.testnet/value" + ] + }' \ | jq '{ - latest_row: ( - .entries[0] + entries: [ + .entries[] | { current_account_id, predecessor_id, @@ -29,148 +35,107 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_RO key, value } - ) + ] }' ``` -This is the shortest FastData read on the page. The full walkthrough below adds a controlled write, exact-key history, and transaction provenance. +This is the shortest useful FastData read on the page: one request, two exact rows. ## Worked investigation -### Write one testnet FastData row, then verify the exact indexed keys +### Read one indexed setting, then trace it back to the write -Use this investigation when you want to prove exactly which FastData rows one write produced, confirm the exact keyed history for one of those rows, and then bridge the indexed row back to the originating transaction. +Use this investigation when you already know the contract and predecessor, and the question is: “what is the current indexed setting value, did it change before, and which transaction created it?”
Strategy -

Create one controlled FastData write, verify the exact key rows it emitted, then hydrate the transaction that produced them.

+

Read the exact setting rows first, widen to predecessor metadata only when provenance matters, and use Transactions API only for the final proof.

-

01near call __fastdata_kv creates one controlled testnet write from your own predecessor account.

-

02get-latest-key and get-history-key verify the exact FastData rows that call emitted.

-

03latest-by-predecessor with metadata plus POST /v0/transactions bridges those indexed rows back to the originating call.

+

01multi or get-latest-key reads the exact indexed setting rows.

+

02get-history-key shows whether the indexed setting changed again later.

+

03latest-by-predecessor with metadata plus POST /v0/transactions proves which write created those indexed rows.

**Goal** -- Prove which exact FastData rows a write produced and show how to trace those rows back to the transaction that created them. +- Read one stable indexed setting from a minimal public testnet contract, confirm the exact-key history for one row, and recover the transaction that created both rows. | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | -| Controlled write | `near` CLI | Submit one testnet `__fastdata_kv` call with a unique value | Gives you a row you can verify immediately instead of relying on someone else's old data | -| Exact indexed row | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Read the exact `value` row, then the exact `key` row, under one predecessor and contract | Proves the precise FastData rows currently indexed for this write | -| Exact keyed history | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) | Pull the exact-key history for the same `value` row | Shows whether that exact row changed again after the write | -| Broader write pattern + metadata | KV FastData [`latest-by-predecessor`](/fastdata/kv/latest-by-predecessor) | List the latest rows for the same predecessor with `include_metadata: true` | Recovers both emitted rows plus the `tx_hash` and `receipt_id` that produced them | -| Transaction hydration | Transactions API [`POST /v0/transactions`](/tx/transactions) | Hydrate the recovered `tx_hash` and decode the `FunctionCall.args` payload | Proves which method call created the indexed FastData rows | +| Exact setting read | KV FastData [`multi`](/fastdata/kv/multi) | Read the known `key` and `value` rows in one request | This is the narrowest useful read when the exact indexed setting rows are already known | +| Exact row read | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Re-read one exact row by path | Useful when the question is about one row, not the whole setting pair | +| Exact keyed history | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) | Check the change history for the exact `value` row | Shows whether that indexed setting changed across multiple writes | +| Optional provenance bridge | KV FastData [`latest-by-predecessor`](/fastdata/kv/latest-by-predecessor) | Recover `tx_hash` and `receipt_id` for the indexed rows only when provenance matters | This is the optional bridge from indexed rows back to one write | +| Optional transaction hydration | Transactions API [`POST /v0/transactions`](/tx/transactions) | Hydrate the recovered `tx_hash` and decode the original args only when you need that proof | Final optional proof that one write created the indexed setting rows | **What a useful answer should include** -- the exact `current_account_id`, `predecessor_id`, and `key` investigated -- the latest indexed row and the exact-key history for that same row -- the `tx_hash` or `receipt_id` that produced the row, if provenance matters -- whether the question is still about FastData rows or has widened into contract-specific on-chain state +- the exact `current_account_id`, `predecessor_id`, and indexed setting rows investigated +- the latest indexed rows and the exact-key history for one of them +- the `tx_hash` or `receipt_id` that created those rows, only if provenance matters +- whether the question is still about indexed FastData rows or has widened into canonical contract state + +### Verified read-only testnet shell walkthrough -### Verified testnet shell walkthrough +Use this when you want a fully read-only example against the stable sample data in `kv.gork-agent.testnet`. -Use this when you have a testnet account configured in `near` CLI and want one reproducible write that you can verify end to end. +This minimal contract behaves like a tiny settings store: one write emits two indexed rows, `key` and `value`. The sample setting currently reads as `test=hello`, which is simple enough to teach the FastData shape without pretending it is a richer application object. +This sample contract indexes its own writes, so `CURRENT_ACCOUNT_ID` and `PREDECESSOR_ID` are intentionally the same in this walkthrough. **What you're doing** -- Write one fresh FastData entry to `kv.gork-agent.testnet`. -- Wait for KV FastData to index that transaction. -- Read the exact `value` row and the exact `key` row that the contract emitted. -- Pull the exact-key history for the `value` row. -- Widen to predecessor scope with metadata so you recover the indexed `tx_hash`. -- Hydrate that transaction and decode the original `__fastdata_kv` call args. +- Read the exact indexed setting rows together. +- Re-read the same rows individually so the exact-key route shape is clear. +- Pull the exact-key history for the setting `value` row. +- Stop there unless provenance matters. ```bash KV_BASE_URL=https://kv.test.fastnear.com TX_BASE_URL=https://tx.test.fastnear.com CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -SIGNER_ID=YOUR_TESTNET_ACCOUNT -PREDECESSOR_ID="$SIGNER_ID" -FASTDATA_FIELD=verification -FASTDATA_VALUE="verify-$(date -u +%Y%m%dT%H%M%SZ)" - -near call "$CURRENT_ACCOUNT_ID" __fastdata_kv \ - "$(jq -nc --arg key "$FASTDATA_FIELD" --arg value "$FASTDATA_VALUE" '{key: $key, value: $value}')" \ - --accountId "$SIGNER_ID" \ - --networkId testnet \ - --gas 30000000000000 \ - | tee /tmp/kv-fastdata-call.txt - -CLI_TX_HASH="$( - awk '/Transaction Id/{print $3}' /tmp/kv-fastdata-call.txt -)" +PREDECESSOR_ID=kv.gork-agent.testnet -ATTEMPTS=0 -until curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ +curl -s "$KV_BASE_URL/v0/multi" \ -H 'content-type: application/json' \ - --data '{"include_metadata": true, "limit": 20}' \ - | tee /tmp/kv-predecessor-latest.json \ - | jq -e --arg tx_hash "$CLI_TX_HASH" ' - .entries - | map(select(.tx_hash == $tx_hash)) - | length > 0 - ' >/dev/null -do - ATTEMPTS=$((ATTEMPTS + 1)) - if [ "$ATTEMPTS" -ge 30 ]; then - echo "Timed out waiting for KV FastData to index $CLI_TX_HASH" >&2 - exit 1 - fi - sleep 2 -done - -INDEXED_TX_HASH="$( - jq -r --arg tx_hash "$CLI_TX_HASH" ' - first(.entries[] | select(.tx_hash == $tx_hash) | .tx_hash) - ' /tmp/kv-predecessor-latest.json -)" - -test "$CLI_TX_HASH" = "$INDEXED_TX_HASH" \ - && echo "CLI tx hash matches indexed metadata" - -jq --arg tx_hash "$CLI_TX_HASH" '{ - tx_hashes: ([.entries[] | select(.tx_hash == $tx_hash) | .tx_hash] | unique), - receipt_ids: ([.entries[] | select(.tx_hash == $tx_hash) | .receipt_id] | unique), + --data '{ + "keys": [ + "kv.gork-agent.testnet/kv.gork-agent.testnet/key", + "kv.gork-agent.testnet/kv.gork-agent.testnet/value" + ] + }' \ + | jq '{ entries: [ .entries[] - | select(.tx_hash == $tx_hash) | { + current_account_id, + predecessor_id, block_height, key, - value, - tx_hash, - receipt_id + value } ] - }' /tmp/kv-predecessor-latest.json - -jq '{ - latest_value_row: ( - .entries[0] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ) -}' <( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" -) + }' curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ | jq '{ latest_key_row: ( .entries[0] | { - current_account_id, - predecessor_id, + block_height, + key, + value + } + ) + }' + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ + | jq '{ + latest_value_row: ( + .entries[0] + | { block_height, key, value @@ -190,6 +155,39 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ } ] }' +``` + +That is the whole main read path: exact rows, exact latest reads, and exact-key history for the same indexed setting. + +### Optional provenance extension + +Only use this follow-up when the next question is “which write created these rows?” + +```bash + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ + -H 'content-type: application/json' \ + --data '{"include_metadata": true, "limit": 10}' \ + | tee /tmp/kv-predecessor-latest.json >/dev/null + +jq '{ + entries: [ + .entries[] + | { + block_height, + key, + value, + tx_hash, + receipt_id + } + ] +}' /tmp/kv-predecessor-latest.json + +INDEXED_TX_HASH="$( + jq -r ' + first(.entries[] | select(.key == "value") | .tx_hash) + ' /tmp/kv-predecessor-latest.json +)" curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ @@ -204,126 +202,29 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | @base64d | fromjson ), - first_receipt_id: .transactions[0].execution_outcome.outcome.receipt_ids[0] + receipt_ids: .transactions[0].execution_outcome.outcome.receipt_ids }' ``` **Why this next step?** -This contract emits two FastData rows from one call: a `key` row that contains the logical field name you passed in, and a `value` row that contains the actual value. The exact-key routes prove those rows directly. The predecessor-scoped lookup is the bridge back to provenance, because it gives you the `tx_hash` and `receipt_id` that created those rows. Hydrating that transaction proves the indexed rows came from one `__fastdata_kv` call with the same decoded args. - -That is also the important boundary for this surface: FastData rows are indexed FastData output, not a guarantee that raw RPC `view_state` will expose the same keys. Because this is an indexed surface, a fresh write may take a short moment to appear; wait until the indexed `tx_hash` shows up before treating the latest rows as final. If the user's question changes from “which FastData rows were emitted?” to “what does this contract's canonical on-chain state look like?”, move to the contract's own read method or [View State](/rpc/contract/view-state) only when you independently know the storage layout you are asking for. - -## Common jobs - -### Look up one exact FastData key right now - -**Start here** - -- [GET Latest by Exact Key](/fastdata/kv/get-latest-key) when one fully qualified key is already known. - -**Next page if needed** - -- [GET History by Exact Key](/fastdata/kv/get-history-key) if the question becomes “how did this key change?” - -**Stop when** - -- The latest indexed row already answers the FastData question. - -**Switch when** - -- The user is no longer asking about indexed FastData rows. Move to the contract's own read method or [View State](/rpc/contract/view-state) only if you know the raw-state layout you need. - -### Turn one exact FastData key into a change history - -**Start here** - -- [GET History by Exact Key](/fastdata/kv/get-history-key) for path-based history under one contract and one predecessor. -- [History by Key](/fastdata/kv/history-by-key) only when you intentionally want the same key string across all contracts and predecessors. - -**Next page if needed** - -- Revisit [GET Latest by Exact Key](/fastdata/kv/get-latest-key) if you want the current indexed value alongside the history. - -**Stop when** - -- You can explain how the key changed over time. - -**Switch when** - -- The user now needs canonical contract state, not just indexed FastData history. Use the contract's own read method or [View State](/rpc/contract/view-state) only when the raw-state shape is known. - -### Trace writes from one predecessor - -**Start here** - -- [All by Predecessor](/fastdata/kv/all-by-predecessor) for latest rows across contracts touched by one predecessor. -- [Latest by Predecessor](/fastdata/kv/latest-by-predecessor) when you need the rows for one contract and one predecessor, optionally with metadata. -- [History by Predecessor](/fastdata/kv/history-by-predecessor) when you need the write history over time. - -**Next page if needed** - -- Narrow to an exact key if one row becomes the real focus, or widen to [Transactions by Hash](/tx/transactions) if provenance becomes the real question. - -**Stop when** - -- You can answer what this predecessor changed and where. - -**Switch when** - -- The user stops asking about indexed writes and starts asking about the current chain state. - -### Bridge one FastData row back to its transaction - -**Start here** - -- [Latest by Predecessor](/fastdata/kv/latest-by-predecessor) with `include_metadata: true` to recover `tx_hash` and `receipt_id`. -- [Transactions by Hash](/tx/transactions) to hydrate the originating call and decode its args. - -**Next page if needed** - -- Move to [Receipt by Id](/tx/receipt) if the next question is about one downstream receipt rather than the original call. - -**Stop when** - -- You can explain which call produced the indexed FastData row. - -**Switch when** - -- The user needs canonical execution semantics or exact executor-level status. Move to RPC transaction status. - -### Batch-check several known keys - -**Start here** - -- [Multi Lookup](/fastdata/kv/multi) when you already know a fixed set of fully qualified keys. - -**Next page if needed** - -- Move one interesting key to [GET History by Exact Key](/fastdata/kv/get-history-key) if the batch result raises a historical question. - -**Stop when** - -- The batch response already answers which of the keys matter. +This sample contract emits two indexed rows from one write: `key=test` and `value=hello`. Treat that as one indexed setting. The exact-key routes prove those rows directly. The predecessor-scoped lookup with metadata is the optional bridge back to provenance, because it gives you the `tx_hash` and `receipt_id` that created those rows. Hydrating that transaction proves those indexed rows came from one `__fastdata_kv` call with decoded args `{ "key": "test", "value": "hello" }`. -**Switch when** +That is also the important boundary for this surface: KV FastData answers questions about indexed FastData rows. If the question changes to canonical contract state, move to the contract's own read method or [View State](/rpc/contract/view-state) only when you independently know the storage layout you want. -- You no longer have a fixed key list and need to inspect the contract or predecessor more broadly. ## Common mistakes -- Starting with broad account or predecessor scans when an exact key is already known. -- Mixing up [GET History by Exact Key](/fastdata/kv/get-history-key) with [History by Key](/fastdata/kv/history-by-key). The former stays inside one contract and predecessor; the latter searches the same key string globally. -- Using KV FastData when the user really wants balances or holdings. -- Confusing indexed FastData rows with exact canonical contract state. -- Assuming a FastData key is directly queryable through raw RPC `view_state`. -- Assuming a fresh write will be indexed synchronously with chain inclusion. -- Reusing pagination tokens or changing filters mid-scan. +- Starting with broad predecessor scans when the exact FastData rows are already known. +- Treating [History by Key](/fastdata/kv/history-by-key) as if it were the same as [GET History by Exact Key](/fastdata/kv/get-history-key). The first is global by key string; the second stays inside one contract and predecessor. +- Using KV FastData when the real question is about balances, holdings, or account summaries. +- Confusing indexed FastData rows with canonical on-chain contract state. +- Assuming every FastData investigation needs a fresh write before any read is useful. ## Related guides - [KV FastData API](/fastdata/kv) +- [Transactions API](/tx) - [RPC Reference](/rpc) -- [FastNear API](/api) - [Choosing the Right Surface](/agents/choosing-surfaces) - [Agent Playbooks](/agents/playbooks) diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index cb587ab..12976d8 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /neardata/examples title: NEAR Data Examples -description: Plain-language workflows for polling optimistic and finalized blocks and handing off to RPC when needed. +description: Plain-language workflows for checking whether a contract was touched in the latest finalized block and extracting the exact hashes worth following up. displayed_sidebar: nearDataApiSidebar page_actions: - markdown @@ -10,84 +10,96 @@ page_actions: ## Quick start -Start with the two helper routes that tell you what changed right now. +Start with one recent finalized block and ask for the smallest possible touch summary first. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID -curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print "optimistic:", $2}' \ - | tr -d '\r' +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" -curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print "final:", $2}' \ - | tr -d '\r' +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | jq --arg target "$TARGET_ACCOUNT_ID" '{ + height: .block.header.height, + hash: .block.header.hash, + direct_tx_count: ([.shards[].chunk.transactions[]? + | select((.transaction.receiver_id // .receiver_id) == $target)] | length), + incoming_receipt_count: ([.shards[].chunk.receipts[]? + | select(.receiver_id == $target)] | length), + outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? + | select( + (.receipt.receiver_id // "") == $target + or (.execution_outcome.outcome.executor_id // "") == $target + )] | length), + state_change_count: ([.shards[].state_changes[]? + | select((.change.account_id // "") == $target)] | length) + } | . + { + touched: ( + (.direct_tx_count > 0) + or (.incoming_receipt_count > 0) + or (.outcome_hit_count > 0) + or (.state_change_count > 0) + ) + }' ``` -This gives you the current optimistic and final redirect targets before you fetch full block documents. +This is the smallest useful NEAR Data summary for an app team: one finalized block, one yes-or-no answer, and a few counts before you widen. ## Worked investigation -### Catch a new block early, then confirm it after finality +### Did my contract get touched in the latest finalized block? -Use this investigation when you want to notice a new block as early as possible, but the final answer still needs a finalized block and sometimes an exact RPC read. +Use this investigation when you want a concrete yes/no answer before you widen into Transactions API or RPC.
Strategy -

Let NEAR Data tell you something changed, then reuse the same block family for the stable confirmation.

+

Anchor on one finalized block, scan the whole block family for your target account, then keep only one small summary plus the identifiers worth following up.

-

01block-optimistic or last-block-optimistic gives the earliest useful signal.

-

02block or last-block-final confirms whether the same observation survived into finalized history.

-

03RPC block is only the last step, once you know the exact height or hash that matters.

+

01last-block-final gives you one stable block height without guessing.

+

02block is the main read: it already contains the transactions, receipts, receipt execution outcomes, and state changes you need to answer “touched or not?”.

+

03Only if the answer is “yes” do you widen: keep the shard ids, tx hashes, and receipt ids you discovered, then hand those exact identifiers to [Transactions API](/tx) or [RPC Reference](/rpc).

**Goal** -- Notice one recent block-family change early, then confirm which finalized block caught up. +- Decide whether one target contract was touched in the latest finalized block, and keep only the shard ids, counts, and sample identifiers worth investigating next. | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | -| Fastest detection | NEAR Data [`block-optimistic`](/neardata/block-optimistic) | Poll optimistic block reads to notice a new block-family change as early as possible | Gives the earliest useful signal before finalized confirmation exists | -| Latest optimistic helper | NEAR Data [`last-block-optimistic`](/neardata/last-block-optimistic) | Use the redirect helper when the client should always follow the newest optimistic target | Keeps the polling client simple when “latest” matters more than explicit heights | -| Stable confirmation | NEAR Data [`block`](/neardata/block) or [`last-block-final`](/neardata/last-block-final) | Re-check the same block family once finality catches up | Confirms that the observed optimistic change survived into finalized history | -| Light block summary | NEAR Data [`block-headers`](/neardata/block-headers) | Read header-level data if only timing or progression is needed | Avoids wider block payloads when header-level confirmation is enough | -| Exact RPC follow-up | RPC [Block by ID](/rpc/block/block-by-id) or [Block by Height](/rpc/block/block-by-height) | Fetch the exact block once you know which one matters | This is the point where RPC becomes useful if you need the protocol's own block object | +| Latest stable anchor | NEAR Data [`last-block-final`](/neardata/last-block-final) | Get one finalized block height without guessing | Gives you a stable starting point for the whole question | +| Whole block family | NEAR Data [`block`](/neardata/block) | Scan transactions, receipts, receipt execution outcomes, and state changes for the target account | This is the main answer surface for “was my contract touched?” | +| Light block summary | NEAR Data [`block-headers`](/neardata/block-headers) | Use when you only need the height, hash, timing, or chunk headers | Avoids the wider block payload when contract-level filtering is not needed | +| Optional shard follow-up | NEAR Data [`block-chunk`](/neardata/block-chunk) or [`block-shard`](/neardata/block-shard) | Re-open only the touched shard if you need deeper payload details | Useful after you already know which shard mattered | +| Exact follow-up surfaces | [Transactions API](/tx) or [RPC Reference](/rpc) | Reuse the discovered tx hashes or receipt ids only if you need the full execution story | NEAR Data tells you whether widening is necessary at all | **What a useful answer should include** -- which optimistic redirect target and resolved block first triggered the investigation -- when the finalized helper caught up and which block it resolved to -- whether the exact RPC block changed the interpretation +- finalized height and hash +- touched or not touched +- counts for direct txs, incoming receipts, outcome hits, and state changes +- one sample tx hash or receipt id per category when present -### Optimistic signal to finalized confirmation shell walkthrough +### Final block to contract-touch answer shell walkthrough -Use this when you want to notice a fresh block-family change immediately, then prove which finalized block caught up and confirm that exact height in RPC. +Use this when the target account is already known and you want one recent finalized answer, not a long polling loop. **What you're doing** -- Inspect the redirect returned by `GET /v0/last_block/optimistic`. -- Fetch the resolved optimistic block document and keep its height and hash. -- Inspect the redirect returned by `GET /v0/last_block/final` and keep the finalized counterpart. -- Compare the optimistic and finalized observations, then reuse the finalized height in RPC `block` by height. +- Get the latest finalized block redirect target. +- Fetch the full block document once. +- Build one small touch summary for one `TARGET_ACCOUNT_ID`. +- Return a yes/no answer plus the smallest useful counts and sample identifiers. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -RPC_URL=https://rpc.mainnet.fastnear.com - -OPTIMISTIC_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -printf 'Optimistic redirect target: %s\n' "$OPTIMISTIC_LOCATION" - -curl -s "$NEARDATA_BASE_URL$OPTIMISTIC_LOCATION" \ - | tee /tmp/neardata-optimistic-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' +TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -95,134 +107,140 @@ FINAL_LOCATION="$( | tr -d '\r' )" +BLOCK_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | sed -E 's#.*/([0-9]+)$#\1#')" + printf 'Final redirect target: %s\n' "$FINAL_LOCATION" +printf 'Final block height: %s\n' "$BLOCK_HEIGHT" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' - -jq -n \ - --slurpfile optimistic /tmp/neardata-optimistic-block.json \ - --slurpfile final /tmp/neardata-final-block.json '{ - optimistic: { - height: $optimistic[0].block.header.height, - hash: $optimistic[0].block.header.hash - }, - final: { - height: $final[0].block.header.height, - hash: $final[0].block.header.hash - }, - same_height: ( - $optimistic[0].block.header.height - == $final[0].block.header.height - ) - }' - -BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "block", - params: { - block_id: ($block_height | tonumber) + | tee /tmp/neardata-block.json >/dev/null + +jq --arg target "$TARGET_ACCOUNT_ID" ' + ( + [ + .shards[] + | .chunk.transactions[]? + | select((.transaction.receiver_id // .receiver_id) == $target) + | (.transaction.hash // .hash) + ] + ) as $txs + | ( + [ + .shards[] + | .chunk.receipts[]? + | select(.receiver_id == $target) + | .receipt_id + ] + ) as $receipts + | ( + [ + .shards[] + | .receipt_execution_outcomes[]? + | select( + (.receipt.receiver_id // "") == $target + or (.execution_outcome.outcome.executor_id // "") == $target + ) + | .tx_hash + | select(. != null) + ] + | unique + ) as $outcomes + | ( + [ + .shards[] + | .state_changes[]? + | select((.change.account_id // "") == $target) + | .type + ] + ) as $state_changes + | { + height: .block.header.height, + hash: .block.header.hash, + touched: ( + ($txs | length) > 0 + or ($receipts | length) > 0 + or ($outcomes | length) > 0 + or ($state_changes | length) > 0 + ), + direct_tx_count: ($txs | length), + incoming_receipt_count: ($receipts | length), + outcome_hit_count: ($outcomes | length), + state_change_count: ($state_changes | length), + sample_direct_tx: ($txs[0] // null), + sample_incoming_receipt: ($receipts[0] // null), + sample_outcome_tx_hash: ($outcomes[0] // null) } - }')" \ - | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json ``` -**Why this next step?** - -This gives you both sides of the story: the earliest optimistic anchor and the later finalized anchor. Once the finalized helper gives you a concrete block height, RPC is the natural next read if you want the exact block object the protocol would return. - -## Common jobs - -### Monitor the optimistic head - -**Start here** - -- [Optimistic block](/neardata/block-optimistic) for the freshest block-family read. - -**Next page if needed** - -- [Last optimistic block redirect](/neardata/last-block-optimistic) if your client wants a helper route that always points at the newest optimistic block. - -**Stop when** - -- You can report the latest optimistic head or detect freshness drift. - -**Switch when** - -- The user needs finalized stability instead of maximum freshness. Move to [Final block by height](/neardata/block) or [Last final block redirect](/neardata/last-block-final). - -### Track finalized block progress safely +If you need richer shard-by-shard or full-list detail later, keep reusing `/tmp/neardata-block.json`. The point of this first pass is to answer “touched or not?” before you widen into longer arrays or deeper traces. -**Start here** +Optional extension: if you still want the touched shard ids, compute them from the same cached block without changing the main answer shape: -- [Final block by height](/neardata/block) when you already know the height you want to confirm. -- [Block headers](/neardata/block-headers) when header-level polling is enough. - -**Next page if needed** - -- [Last final block redirect](/neardata/last-block-final) when the client should follow the newest finalized block without computing the height first. - -**Stop when** - -- You can show finalized progress without pulling in deeper protocol detail. - -**Switch when** - -- The user needs exact block fields or transaction semantics. Move to [RPC Reference](/rpc). - -### Use redirect helpers in a polling client - -**Start here** - -- [Last final block redirect](/neardata/last-block-final) or [Last optimistic block redirect](/neardata/last-block-optimistic) depending on the freshness requirement. - -**Next page if needed** - -- Follow the block URL returned by the helper and keep reading from there. - -**Stop when** - -- The client can reliably follow the helper route and consume the final block resource. - -**Switch when** - -- Redirect behavior itself becomes a problem for the client. Move to the direct block routes instead. - -### Move from recent block polling to exact RPC inspection - -**Start here** - -- Use the relevant NEAR Data block route to find the recent block or block-family event of interest. +```bash +jq --arg target "$TARGET_ACCOUNT_ID" ' + [ + .shards[] + | .shard_id as $shard_id + | select( + ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) + or ([.chunk.receipts[]? | .receiver_id] | index($target)) + or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) + or ([.state_changes[]? | .change.account_id] | index($target)) + ) + | $shard_id + ] | unique +' /tmp/neardata-block.json +``` -**Next page if needed** +If that answer says `touched: true` and you want one shard-level follow-up, reopen only the first touched shard: -- [Block by Height](/rpc/block/block-by-height), [Block by ID](/rpc/block/block-by-id), or another RPC method once you know the exact block or follow-up object you need. +```bash +TOUCHED_SHARD_ID="$( + jq -r --arg target "$TARGET_ACCOUNT_ID" ' + first( + .shards[] + | .shard_id as $shard_id + | select( + ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) + or ([.chunk.receipts[]? | .receiver_id] | index($target)) + or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) + or ([.state_changes[]? | .change.account_id] | index($target)) + ) + | $shard_id + ) // empty + ' /tmp/neardata-block.json +)" -**Stop when** +if [ -n "$TOUCHED_SHARD_ID" ]; then + curl -s "$NEARDATA_BASE_URL/v0/block/$BLOCK_HEIGHT/chunk/$TOUCHED_SHARD_ID" \ + | jq '{ + shard_id: .header.shard_id, + chunk_hash: .header.chunk_hash, + tx_hashes: ([.transactions[]? | (.transaction.hash // .hash)] | .[:5]), + receipt_ids: ([.receipts[]? | .receipt_id] | .[:5]), + receipt_receivers: ([.receipts[]? | .receiver_id] | .[:5]) + }' +fi +``` -- You can clearly name the recent block that deserves RPC follow-up. +**Why this next step?** -**Switch when** +This keeps the question as small as possible: first answer “was my contract touched?”, then widen only if one of the sample identifiers justifies a deeper trace. NEAR Data is the discovery layer here, not just a block monitor. -- The user asks for the exact protocol structure, not just recent block polling. ## Common mistakes -- Treating NEAR Data like a push stream instead of a polling API. -- Starting with RPC when the real need is a recent block monitor. -- Forgetting that redirect helpers may return `401` before redirecting if the key is invalid, or may be awkward for some HTTP clients. -- Staying on NEAR Data when the user has already asked for exact protocol-native block details. +- Treating NEAR Data like a push stream instead of a polling or point-read API. +- Starting with RPC before checking whether one finalized block already answers the contract-touch question. +- Looking only for direct transactions and forgetting that contracts are often touched through receipts or state changes. +- Assuming one hard-coded shard id should be checked before you inspect the block family itself. +- Widening to Transactions API or RPC before extracting the exact shard ids, tx hashes, or receipt ids from NEAR Data. ## Related guides - [NEAR Data API](/neardata) -- [RPC Reference](/rpc) - [Transactions API](/tx) +- [RPC Reference](/rpc) - [Choosing the Right Surface](/agents/choosing-surfaces) - [Agent Playbooks](/agents/playbooks) diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index 45df884..c075b7e 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -44,94 +44,31 @@ This is the smallest reliable RPC example on the page: one request, one exact an ## Transaction Submission and Tracking -Start here when the real question is not just “how do I send this?” but “which RPC endpoint should I use, and how do I track the transaction all the way to done?” +### Two-part pattern: submit a transaction, or track a known tx hash to final execution -### Submit a transaction, then track it from hash to final execution +Default pattern: -Use this when the user story is simple: “I have a signed transaction. Which endpoint do I call first, and what should I poll after I get the hash?” Not every tx question wants the same RPC method. The practical pattern is to submit fast, then track deliberately. +- `broadcast_tx_async` to submit +- `tx` with `wait_until: "FINAL"` to track +- `EXPERIMENTAL_tx_status` only if the next question is about receipts -This walkthrough is intentionally pinned and historical. It uses one real mainnet transaction that wrote a NEAR Social follow edge: +This walkthrough is intentionally split in two: + +- submit a fresh signed transaction and keep the returned hash +- track one known historical tx hash with reproducible output + +The tracking half uses one pinned historical transaction, so the status lookups use the archival host: - transaction hash: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - signer: `mike.near` - receiver: `social.near` -- included block height: `79574923` -- receipt execution block for the SocialDB write: `79574924` - -Because this transaction is already old and finalized, you cannot literally replay its true pending window. That is fine. The point here is to teach the right submission and tracking pattern, then inspect one pinned transaction with the same tools. - -
-
- Strategy -

Submit fast, poll the simpler status path first, and only drop into the receipt tree when the headline status stops being enough.

-
-
-

01RPC broadcast_tx_async is the low-latency submission surface when your client will track separately.

-

02RPC tx is the default polling surface for included, optimistic, and final guarantees.

-

03RPC EXPERIMENTAL_tx_status is the deeper follow-up when you need the receipt tree, not the default loop.

-
-
+- `https://archival-rpc.mainnet.fastnear.com` -**What you're deciding** - -- which submission endpoint to reach for first -- what to poll after you have a tx hash -- how `wait_until` thresholds relate to included, optimistic, and final guarantees -- when to stop using `tx` and switch to `EXPERIMENTAL_tx_status` - -```mermaid -flowchart LR - S["Sign transaction"] --> A["broadcast_tx_async
returns tx hash"] - A --> T["tx polling
INCLUDED_FINAL -> FINAL"] - T --> F["Transaction fully done"] - T -. "only when needed" .-> E["EXPERIMENTAL_tx_status
receipt tree + outcomes"] - F -. "optional readable story" .-> X["POST /v0/transactions"] -``` - -| Method | Use it when | What comes back | Position here | -| --- | --- | --- | --- | -| [`broadcast_tx_async`](/rpc/transaction/broadcast-tx-async) | your client wants to own tracking after submission | just the tx hash | **default submit path** | -| [`send_tx`](/rpc/transaction/send-tx) | you want the node to wait to a chosen threshold for you | tx result up to `wait_until` | blocking alternative | -| [`broadcast_tx_commit`](/rpc/transaction/broadcast-tx-commit) | older code or quick one-call confirmation is the point | execution result with commit-style waiting | legacy convenience | -| [`tx`](/rpc/transaction/tx-status) | you already have the tx hash and want to know how far it got | status plus outcomes at the threshold you asked for | **default tracking path** | -| [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | you need receipt-tree detail or a richer async story | full receipt tree and detailed outcomes | deep follow-up only | - -**Status and wait map** - -`wait_until` values are waiting thresholds, not a permanent lifecycle enum you should treat as the user's one true transaction status. The word `pending` is still useful in human conversation, but here it means “the transaction has been submitted and is not yet included.” - -| Phase or threshold | What it means in practice | Best RPC surface | -| --- | --- | --- | -| pre-inclusion / pending | the client has submitted the tx, but it is not yet anchored in a block | your own submission state plus retry/backoff logic | -| `INCLUDED` | the tx is in a block, but that block may not be final yet | `tx` | -| `INCLUDED_FINAL` | the inclusion block is final | `tx` | -| `EXECUTED_OPTIMISTIC` | execution has happened with optimistic finality | `tx` or `send_tx` | -| `FINAL` | all relevant execution has completed and finalized | `tx` by default, `EXPERIMENTAL_tx_status` when you need more detail | - -The key practical distinction is simple: - -- use `broadcast_tx_async` when the tx hash is enough to keep going -- use `tx` as the normal tracking loop -- use `EXPERIMENTAL_tx_status` when the next question is about the receipt tree rather than the headline status - -**What you're doing** - -- Show what a live submission would look like with `broadcast_tx_async`. -- Poll the pinned tx with `tx` at two thresholds: `INCLUDED_FINAL` and `FINAL`. -- Only after that inspect the same tx with `EXPERIMENTAL_tx_status`. -- Optionally pivot to Transactions API if the human-readable story is what matters next. +1. Submit a fresh signed transaction and keep the returned hash. ```bash RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near -RECEIVER_ID=social.near -``` - -1. If this were a live client flow, submit with `broadcast_tx_async` and keep the returned hash. -```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data '{ @@ -143,40 +80,16 @@ curl -s "$RPC_URL" \ | jq . ``` -In a real app, that response is the moment you stop waiting on submission and start tracking by tx hash. +That first step is only about the submission shape. The returned hash is what you would track next for your own live transaction. -2. Poll with `tx` at the first threshold that answers the user question. +2. Track one known tx hash until you have the simplest final answer. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "INCLUDED_FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' +RPC_URL=https://archival-rpc.mainnet.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near ``` -What to notice: - -- on a live tx, this threshold is useful when you care that the tx is safely included before you claim success to the user -- on this historical tx, it returns immediately because the transaction is long past inclusion -- `transaction_outcome.outcome.status` still tells you that the original action handed off into receipt execution - -3. Poll again with `FINAL` when you want the completed transaction story rather than just safe inclusion. - ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ @@ -194,18 +107,12 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - status: .result.status, + transaction_status: .result.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -What to notice: - -- for a historical tx, this call also returns immediately -- in a real tracking loop, this is the threshold that answers “is the transaction actually done?” -- for many apps, this is where you stop and move on - -4. Only switch to `EXPERIMENTAL_tx_status` when you need the richer receipt tree. +3. Only switch to `EXPERIMENTAL_tx_status` when that known tx now needs receipt-tree detail. ```bash curl -s "$RPC_URL" \ @@ -224,277 +131,57 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - status: .result.status, transaction_handoff: .result.transaction_outcome.outcome.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -This is where you go when “did it finish?” turns into “show me the receipt tree and the full async execution story.” - -5. Optional: pivot to Transactions API only when you want the readable story surface. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transaction_handoff: .transactions[0].transaction_outcome.outcome.status - }' -``` - -That last step is intentionally optional. The RPC truth is already enough for submission and tracking. This is only the human-readable story surface when the next user question becomes “what actually happened?” instead of “how far did the tx get?” - -**Recommended pattern** - -- Use `broadcast_tx_async` plus `tx` polling when you want the best client control and the fastest feedback. -- Use `send_tx` when you really do want one blocking call to wait up to a chosen threshold. -- Use `EXPERIMENTAL_tx_status` when the normal polling loop stops being enough and the receipt tree becomes the real question. +If you want the node to wait for you instead of tracking separately, use [`send_tx`](/rpc/transaction/send-tx). The default pattern on this page is still: submit with `broadcast_tx_async`, then track a hash with `tx`. ## Account and Key Mechanics -Start here when the question is about exact permissions, exact key state, or one contract-level write flow. +Start here when the question is about exact permissions, exact key state, or whether one key can call one contract right now. -### Audit and remove old Near Social function-call keys +### Does this access key let me call this contract right now? -Use this when you know an account has accumulated older `social.near` function-call keys and you want to inspect them, choose one intentionally, and remove it with raw RPC submission. +Use this when you already have an account, one public key, and one target contract, and you want a plain yes-or-no answer before you try to sign anything.
Strategy -

Use exact key reads to narrow the target first, then sign exactly one delete.

+

Filter the account’s keys first, inspect the exact key second, and classify the permission last.

-

01RPC view_access_key_list finds only the function-call keys scoped to social.near.

-

02RPC view_access_key double-checks the one key you plan to remove, and POST /v0/account is only for optional account-level context.

-

03RPC send_tx submits the DeleteKey, then RPC view_access_key_list closes the loop.

+

01RPC view_access_key_list narrows the account down to keys that could matter for the target contract.

+

02RPC view_access_key gives the exact permission object for the one public key you might actually use.

+

03jq turns that permission object into full_access, function_call_match, receiver_mismatch, or method_not_allowed.

**What you're doing** -- Use RPC itself to list every access key on the account. -- Narrow that list to function-call keys scoped to `social.near`. -- Inspect one candidate key exactly before you delete it. -- Build and sign a `DeleteKey` transaction with a full-access key, then submit it through RPC and verify the key is gone. - -Two caveats matter up front: - -- The deleting key must be a full-access key. A function-call key cannot sign a `DeleteKey` action. -- This flow is about exact key state and cleanup. The optional Transactions API step below gives account-level context, not authoritative per-key “last used” forensics. - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export SOCIAL_RECEIVER_ID=social.near -export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' -export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' -export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' -``` - -1. List all access keys on the account, then narrow to `social.near` function-call keys. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } - }')" \ - | tee /tmp/fastnear-access-keys.json >/dev/null - -jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' - .result.keys[] - | select((.access_key.permission | type) == "object") - | select(.access_key.permission.FunctionCall.receiver_id == $receiver) - | { - public_key, - nonce: .access_key.nonce, - receiver_id: .access_key.permission.FunctionCall.receiver_id, - method_names: .access_key.permission.FunctionCall.method_names, - allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") - } -' /tmp/fastnear-access-keys.json -``` - -Pick one `public_key` from that filtered list and set `DELETE_PUBLIC_KEY` to it. - -2. Inspect the specific candidate key one more time before deleting it. +- List the account’s access keys and narrow them to the contract you care about. +- Inspect the exact key you might sign with. +- Decide whether it can call this receiver and method without leaving RPC. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$DELETE_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } - }')" \ - | jq '{nonce: .result.nonce, permission: .result.permission}' -``` - -3. Optional: pull recent function-call activity for the account to decide whether you want to investigate more before cleanup. - -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - is_function_call: true, - limit: 10 - }')" \ - | jq '{ - account_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_success - } - ] - }' -``` - -That query helps answer “has this account still been doing function-call work recently?”, but it does not prove that a specific access key was the one used. - -4. Sign a `DeleteKey` transaction for `DELETE_PUBLIC_KEY` with a full-access key. - -Run this in a directory where `near-api-js@5` is installed. The command reads the environment variables above, fetches the latest nonce for `FULL_ACCESS_PUBLIC_KEY`, fetches a fresh final block hash, signs a `DeleteKey` action, and stores the resulting `signed_tx_base64` in `SIGNED_TX_BASE64`. +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +TARGET_CONTRACT_ID=crossword.puzzle.near +TARGET_METHOD_NAME=new_puzzle +TARGET_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_WANT_TO_CHECK' -```bash -SIGNED_TX_BASE64="$( - node --input-type=module <<'EOF' -import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; - -const { - ACCOUNT_ID, - NETWORK_ID = 'mainnet', - RPC_URL = 'https://rpc.mainnet.fastnear.com', - DELETE_PUBLIC_KEY, - FULL_ACCESS_PUBLIC_KEY, - FULL_ACCESS_PRIVATE_KEY, -} = process.env; - -for (const name of [ - 'ACCOUNT_ID', - 'DELETE_PUBLIC_KEY', - 'FULL_ACCESS_PUBLIC_KEY', - 'FULL_ACCESS_PRIVATE_KEY', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); -const derivedPublicKey = keyPair.getPublicKey().toString(); - -if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { - throw new Error( - `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: ACCOUNT_ID, - public_key: FULL_ACCESS_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const transaction = transactions.createTransaction( - ACCOUNT_ID, - utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), - ACCOUNT_ID, - BigInt(accessKey.nonce) + 1n, - [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -)" +# Sample live values observed on April 19, 2026: +# ACCOUNT_ID=mike.near +# TARGET_CONTRACT_ID=crossword.puzzle.near +# TARGET_METHOD_NAME=new_puzzle +# TARGET_PUBLIC_KEY='ed25519:otwaB1X88ocpmUdC1B5XaifucfDLmLKaonb26KqTj96' ``` -5. Submit the signed transaction through raw RPC and wait for `FINAL`. +1. List the account’s keys and narrow them to the target contract. ```bash curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Re-run the access-key list and verify that the deleted key is gone. - -```bash -if curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc: "2.0", @@ -506,65 +193,28 @@ if curl -s "$RPC_URL" \ finality: "final" } }')" \ - | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' - .result.keys[] - | select(.public_key == $public_key) - ' >/dev/null; then - echo "Key is still present: $DELETE_PUBLIC_KEY" -else - echo "Key deleted: $DELETE_PUBLIC_KEY" -fi -``` - -**Why this next step?** - -Re-running `view_access_key_list` closes the loop on the same RPC method you used for discovery. If the delete succeeded there, you do not need an indexed API to prove the cleanup. - -### Which transaction added this `social.near` function-call key, and who authorized it? - -Use this when you can already see a live access key on the account, but you want to trace it back to the `AddKey` transaction that created it and identify which public key actually authorized that change. - -
-
- Strategy -

Start from the live key, then walk backward only as far as you need.

-
-
-

01RPC view_access_key gives the current stored nonce, which is the best historical clue you have.

-

02POST /v0/account turns that nonce into a tight candidate window instead of a whole-account search.

-

03POST /v0/transactions tells you whether the key was added directly or through delegated authorization, and POST /v0/receipt is only for the exact AddKey execution block.

-
-
- -**What you're doing** - -- Read the exact key first with RPC and keep its current nonce as the clue. -- Convert that nonce into a tight block-height window for the likely `AddKey` receipt. -- Search account history only inside that window instead of scanning the whole account. -- Hydrate the candidate transaction and distinguish three different keys: - - the key that got added - - the top-level signer public key - - the delegated authorizing public key, if the change was wrapped in a `Delegate` action - -Three nonce details matter up front: - -- New access keys start with a nonce derived from block height at roughly `block_height * 1_000_000`, so dividing the current nonce by `1_000_000` gives a useful search window. -- The `AddKey` action payload often shows `access_key.nonce: 0`. That is not the stored nonce you later see from `view_access_key`. -- If the key has been used heavily since creation, widen the search window a bit more. - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' - -# Sample live key observed on April 18, 2026: -# export ACCOUNT_ID=mike.near -# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' + | tee /tmp/access-key-list.json >/dev/null + +jq --arg target_contract_id "$TARGET_CONTRACT_ID" '{ + candidate_keys: [ + .result.keys[] + | select( + .access_key.permission == "FullAccess" + or ( + (.access_key.permission | type) == "object" + and .access_key.permission.FunctionCall.receiver_id == $target_contract_id + ) + ) + | { + public_key, + nonce: .access_key.nonce, + permission: .access_key.permission + } + ] +}' /tmp/access-key-list.json ``` -1. Read the exact key first, then turn its current nonce into a search window. +2. Inspect the exact key you want to evaluate. ```bash curl -s "$RPC_URL" \ @@ -582,175 +232,72 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/key-origin-view.json >/dev/null - -CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" -ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" -SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" -SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" + | tee /tmp/exact-access-key.json >/dev/null -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_public_key "$TARGET_PUBLIC_KEY" \ - --argjson current_nonce "$CURRENT_NONCE" \ - --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ - --argjson search_from "$SEARCH_FROM" \ - --argjson search_to "$SEARCH_TO" \ - --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ - account_id: $account_id, - target_public_key: $target_public_key, - current_nonce: $current_nonce, - estimated_receipt_block: $estimated_receipt_block, - search_from_tx_block_height: $search_from, - search_to_tx_block_height: $search_to, - permission: ($permission | fromjson) - }' +jq '{nonce: .result.nonce, permission: .result.permission}' /tmp/exact-access-key.json ``` -If you use the sample key above, the estimated receipt block should land at `112057392`. - -2. Search account history only inside that block neighborhood. +3. Turn that exact permission object into a yes-or-no answer for this contract and method. ```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_tx_block_height "$SEARCH_FROM" \ - --argjson to_tx_block_height "$SEARCH_TO" '{ - account_id: $account_id, - is_real_signer: true, - from_tx_block_height: $from_tx_block_height, - to_tx_block_height: $to_tx_block_height, - desc: false, - limit: 50 - }')" \ - | tee /tmp/key-origin-candidates.json >/dev/null - -jq '{ - txs_count, - candidate_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_signer, - is_real_signer, - is_predecessor, - is_receiver +jq -n \ + --slurpfile key /tmp/exact-access-key.json \ + --arg target_contract_id "$TARGET_CONTRACT_ID" \ + --arg target_method_name "$TARGET_METHOD_NAME" ' + ($key[0].result.permission) as $permission + | if $permission == "FullAccess" then + { + can_call_now: true, + reason: "full_access" } - ] -}' /tmp/key-origin-candidates.json -``` - -With the sample `mike.near` key above, this window returns one candidate transaction: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` at outer tx block `112057390`. - -3. Hydrate those candidates and keep only the transaction that actually added your target key. - -```bash -TX_HASHES_JSON="$( - jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json -)" - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ - | tee /tmp/key-origin-transactions.json >/dev/null - -jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "direct", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $tx.transaction.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }), - ($tx.transaction.actions[]? - | .Delegate? - | .delegate_action as $delegate - | $delegate.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "delegated", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $delegate.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - tx_block_hash: $tx.execution_outcome.block_hash, - receiver_id: $tx.transaction.receiver_id - } + . -' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json -``` - -If `authorization_mode` is `direct`, the top-level signer public key and the authorizing public key are the same. If `authorization_mode` is `delegated`, the key that actually authorized the `AddKey` lives inside `Delegate.delegate_action.public_key`. - -With the sample `mike.near` key above, the match is delegated: - -- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` -- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` -- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` -- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` - -4. Optional: if you need the exact `AddKey` receipt block too, pivot one more time by receipt ID. - -```bash -ADD_KEY_RECEIPT_ID="$( - jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | .receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) - | .receipt.receipt_id - ' /tmp/key-origin-transactions.json | head -n 1 -)" - -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receipt_block_height: .receipt.block_height, - tx_block_height: .receipt.tx_block_height, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - transaction_hash: .receipt.transaction_hash - }' + elif $permission.FunctionCall.receiver_id != $target_contract_id then + { + can_call_now: false, + reason: "receiver_mismatch", + receiver_id: $permission.FunctionCall.receiver_id + } + elif ( + ($permission.FunctionCall.method_names | length) == 0 + or ($permission.FunctionCall.method_names | index($target_method_name)) + ) then + { + can_call_now: true, + reason: ( + if ($permission.FunctionCall.method_names | length) == 0 + then "function_call_any_method" + else "function_call_method_match" + end + ), + allowance: ($permission.FunctionCall.allowance // "unlimited") + } + else + { + can_call_now: false, + reason: "method_not_allowed", + allowed_methods: $permission.FunctionCall.method_names + } + end' ``` -For the sample key above, the exact `AddKey` receipt is `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` in receipt block `112057392`, while the outer transaction landed earlier in block `112057390`. +For the sample `mike.near` key above on April 19, 2026, the answer is `can_call_now: true`: the key is a function-call key for `crossword.puzzle.near`, and `method_names: ["new_puzzle"]` explicitly allows the method we asked about. **Why this next step?** -Start with exact current key state because it gives you the nonce clue. A tight `/v0/account` window turns that clue into a small candidate set. `/v0/transactions` tells you whether the key was added directly or through delegated authorization. `/v0/receipt` is the optional last step when you need the exact `AddKey` receipt block, not just the outer transaction. +`view_access_key_list` is the fastest contract-level filter. `view_access_key` is the exact authority check for the one public key you may actually use. If the answer is `false`, you need a different key or a different permission setup, not a deeper history trace. -### Register FT storage if needed, then transfer tokens +### Does this FT receiver need storage registration before I transfer? -Use this when the user story is “send fungible tokens safely, but first prove whether the receiver is already registered for storage on that FT contract.” +Use this when the user story is “I am about to send fungible tokens, and I want a plain yes-or-no answer about whether the receiver needs `storage_deposit` first.”
Strategy -

Read storage first, then spend the minimum write calls needed to make the transfer stick.

+

Read the receiver storage state first, then stop as soon as you know whether `ft_transfer` can proceed.

01RPC call_function storage_balance_of tells you whether the receiver is already registered.

02RPC call_function storage_balance_bounds only matters if you need the exact minimum deposit before writing.

-

03RPC send_tx submits storage_deposit and ft_transfer, then RPC call_function ft_balance_of proves the result.

+

03jq turns those two reads into one answer: “transfer can proceed” or “send `storage_deposit` first”.

@@ -763,24 +310,19 @@ Use this when the user story is “send fungible tokens safely, but first prove - [FT storage and transfer](https://docs.near.org/integrations/fungible-tokens) - [Pre-deployed FT contract](https://docs.near.org/tutorials/fts/predeployed-contract) -This walkthrough uses the safe public contract `ft.predeployed.examples.testnet`. Before you start, make sure the sender already holds some `gtNEAR` there. If not, mint a small balance first with the pre-deployed contract guide above and then come back to this flow. +This walkthrough uses the safe public contract `ft.predeployed.examples.testnet`. The point here is the read-only decision: do you need a `storage_deposit` first, or can your transfer path proceed already? **What you're doing** - Use exact RPC view calls to check whether the receiver already has FT storage on the contract. -- If needed, fetch the minimum storage requirement. -- Sign and submit `storage_deposit`, then `ft_transfer`. -- Verify the receiver balance with the same contract’s own view method. +- Fetch the exact minimum storage requirement from the same contract. +- Stop once you know whether `ft_transfer` can proceed or whether `storage_deposit` has to come first. ```bash export NETWORK_ID=testnet export RPC_URL=https://rpc.testnet.fastnear.com export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet -export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' -export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' -export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' ``` 1. Check whether the receiver is already registered on the FT contract. @@ -816,7 +358,7 @@ jq '{ }' /tmp/ft-storage-balance.json ``` -2. If the receiver is not registered yet, fetch the minimum storage deposit. +2. Fetch the minimum storage deposit from the same contract. ```bash MIN_STORAGE_YOCTO="$( @@ -841,215 +383,39 @@ MIN_STORAGE_YOCTO="$( printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" ``` -3. Define one reusable signer for contract function calls. - -Run this in a directory where `near-api-js@5` is installed. The function below reads the exported shell variables above and turns each function call into a signed payload for raw RPC submission. - -```bash -sign_function_call() { - METHOD_NAME="$1" \ - ARGS_JSON="$2" \ - DEPOSIT_YOCTO="$3" \ - GAS_TGAS="$4" \ - node --input-type=module <<'EOF' -import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; - -const { - NETWORK_ID = 'testnet', - RPC_URL = 'https://rpc.testnet.fastnear.com', - TOKEN_CONTRACT_ID, - SENDER_ACCOUNT_ID, - SENDER_PUBLIC_KEY, - SENDER_PRIVATE_KEY, - METHOD_NAME, - ARGS_JSON, - DEPOSIT_YOCTO = '0', - GAS_TGAS = '100', -} = process.env; - -for (const name of [ - 'TOKEN_CONTRACT_ID', - 'SENDER_ACCOUNT_ID', - 'SENDER_PUBLIC_KEY', - 'SENDER_PRIVATE_KEY', - 'METHOD_NAME', - 'ARGS_JSON', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); -const signer = await InMemorySigner.fromKeyPair( - NETWORK_ID, - SENDER_ACCOUNT_ID, - keyPair -); - -const derivedPublicKey = keyPair.getPublicKey().toString(); -if (derivedPublicKey !== SENDER_PUBLIC_KEY) { - throw new Error( - `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: SENDER_ACCOUNT_ID, - public_key: SENDER_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const action = transactions.functionCall( - METHOD_NAME, - Buffer.from(ARGS_JSON), - BigInt(GAS_TGAS) * 10n ** 12n, - BigInt(DEPOSIT_YOCTO) -); - -const transaction = transactions.createTransaction( - SENDER_ACCOUNT_ID, - utils.PublicKey.fromString(SENDER_PUBLIC_KEY), - TOKEN_CONTRACT_ID, - BigInt(accessKey.nonce) + 1n, - [action], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - SENDER_ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -} -``` - -4. If needed, register the receiver for storage first. +3. Turn those two reads into one transfer-readiness answer. ```bash -if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then - SIGNED_TX_BASE64="$( - sign_function_call \ - storage_deposit \ - "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id, - registration_only: true - }')" \ - "$MIN_STORAGE_YOCTO" \ - 100 - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash - }' -fi -``` - -5. Transfer the FT after storage is ready. - -```bash -SIGNED_TX_BASE64="$( - sign_function_call \ - ft_transfer \ - "$(jq -nc \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ - receiver_id: $receiver_id, - amount: $amount, - memo: "FastNear RPC example" - }')" \ - 1 \ - 100 -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Verify the receiver’s FT balance with the contract’s own view method. - -```bash -RECEIVER_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - receiver_balance: (.result.result | implode | fromjson) +jq -n \ + --slurpfile balance /tmp/ft-storage-balance.json \ + --slurpfile bounds /tmp/ft-storage-bounds.json \ + --arg receiver_account_id "$RECEIVER_ACCOUNT_ID" ' + ( + $balance[0].result.result + | if length == 0 then null else (implode | fromjson) end + ) as $storage + | ( + $bounds[0].result.result + | implode + | fromjson + ) as $bounds + | { + receiver_account_id: $receiver_account_id, + receiver_registered: ($storage != null), + current_storage: $storage, + minimum_storage_deposit_yocto: $bounds.min, + next_step: ( + if $storage != null + then "receiver already registered; ft_transfer can proceed" + else "send storage_deposit before ft_transfer" + end + ) }' ``` **Why this next step?** -This is a good RPC example because every step stays close to the contract itself: first check storage state, then send the minimum required change calls, then verify the post-transfer balance directly on the contract. +This is the clean RPC question in this workflow: “is the receiver already registered, and if not, what minimum deposit will the contract require?” The signed write path depends on your wallet, CLI, or backend integration, so it does not belong in the smallest core RPC example. ## Contract Reads and Raw State @@ -1057,47 +423,12 @@ Start here when the question is “does this contract method tell me enough?” ### Read a counter straight from contract state, then confirm it with the view method -Use this when the user story is simple: “I know this contract exposes a counter, but can I read that number straight from storage without calling the contract code?” - -This walkthrough uses the live public testnet contract `counter.near-examples.testnet`. The number can change over time. That is fine. The point is that both reads agree when you run them: - -- `view_state` reads the raw `STATE` entry directly from contract storage -- `call_function get_num` asks the contract for the same current number through its public view API - -
-
- Strategy -

Read the raw storage first, decode the bytes you got back, then let the contract confirm the same answer through its view method.

-
-
-

01RPC view_state reads the raw STATE entry without running contract code.

-

02Decode the base64 value into bytes, then interpret those bytes with the contract’s known Borsh layout.

-

03RPC call_function get_num is the friendly cross-check that the raw-state read and the view method still agree.

-
-
- -The mental model matters more than the counter itself: - -- `view_state` is a direct storage read from the trie -- `call_function` executes a read-only method on the contract -- both can answer the same question, but they do different work to get there - -```mermaid -flowchart LR - S["RPC view_state
prefix STATE"] --> R["Raw STATE bytes"] - R --> D["Decode base64 + Borsh"] - D --> N["Signed counter value"] - C["RPC call_function get_num"] --> J["JSON method result"] - N --> X["Compare"] - J --> X - X --> A["Same current counter value"] -``` +Use this when you already know the exact storage key family and want the smallest possible contrast between raw state and the contract’s public read API. -**What you're doing** +This uses the live public testnet contract `counter.near-examples.testnet`: -- Read the raw `STATE` key from contract storage. -- Decode the returned bytes into the current signed counter value. -- Call `get_num` through the view method and confirm that the method answer matches the raw-state decode. +- `view_state` reads the raw `STATE` entry directly +- `call_function get_num` asks the contract for the same current number ```bash export NETWORK_ID=testnet @@ -1106,7 +437,7 @@ export CONTRACT_ID=counter.near-examples.testnet export STATE_PREFIX_BASE64=U1RBVEU= ``` -1. Read the raw contract state first. +1. Read the raw `STATE` entry. ```bash curl -s "$RPC_URL" \ @@ -1127,49 +458,30 @@ curl -s "$RPC_URL" \ | tee /tmp/counter-view-state.json >/dev/null jq '{ - block_height: .result.block_height, - key_base64: .result.values[0].key, + key: (.result.values[0].key | @base64d), value_base64: .result.values[0].value }' /tmp/counter-view-state.json - -jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json ``` -That last command should print `STATE`. This is the key family you already knew ahead of time, so `view_state` can go straight to the raw storage entry without asking the contract to execute any method. +That should show `key: "STATE"`. This is the case where `view_state` makes sense: you already know the exact key family. -2. Decode the returned value bytes into the signed counter. +2. Decode the raw bytes. ```bash RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" -python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . +python3 - "$RAW_VALUE_BASE64" <<'PY' import base64 -import json import sys raw = base64.b64decode(sys.argv[1]) - -print(json.dumps({ - "value_base64": sys.argv[1], - "bytes": list(raw), - "hex": raw.hex(), - "signed_i8": int.from_bytes(raw, "little", signed=True), - "unsigned_u8": int.from_bytes(raw, "little", signed=False), -})) +print(int.from_bytes(raw, "little", signed=True)) PY ``` -For this specific contract, one byte is enough because the Rust counter stores `val: i8` inside the contract state. That is why a raw value like `CQ==` decodes to one byte `0x09`, which reads as the signed integer `9`. - -One small signed-value note is worth keeping in your head: if the counter were negative, the same one-byte payload would still decode correctly as a signed two's-complement `i8`. For example, `/w==` is the single byte `0xff`, which means `-1` as `signed_i8`, not `255`. - -The reusable recipe is small: +For this contract, `STATE` is a one-byte signed counter, so decoding is trivial. On other contracts the layout may be more complex, but the rule stays the same: bytes first, schema second. -- `view_state` gives you base64-encoded raw bytes -- you decode those bytes with the contract’s known storage layout -- for larger contracts, that layout may be more complex, but the idea is the same: bytes first, schema second - -3. Now ask the contract the friendly way and compare. +3. Ask the contract the friendly way and compare. ```bash curl -s "$RPC_URL" \ @@ -1194,7 +506,7 @@ jq '{ }' /tmp/counter-call-function.json ``` -4. Compare both answers directly. +4. Compare both answers. ```bash RAW_STATE_NUMBER="$( @@ -1220,93 +532,53 @@ jq -n \ }' ``` -If `agrees_now` is `true`, you have proved the point of the example: - -- `view_state` answered the question by reading storage directly -- `call_function get_num` answered the same question by running the contract’s public read method - **Why this next step?** -Use `view_state` when the real question is about exact storage and you already know the key family. Use `call_function` when you want the contract’s public read API. If the next question becomes historical instead of “what is it right now?”, that is the moment to widen into [KV FastData API](/fastdata/kv). +Use `view_state` when you already know the exact storage key family and want raw bytes. Use `call_function` when you want the contract’s public read API. If the next question becomes historical instead of “what is it right now?”, widen into [KV FastData API](/fastdata/kv). -## NEAR Social and BOS Exact Reads +## SocialDB Exact Reads -These stay on exact SocialDB reads and on-chain readiness checks until the question turns historical. +Stay on exact `call_function get` reads when you already know the SocialDB key you want. On standard RPC, raw `view_state` against `social.near` is not a practical teaching path because the contract state is too large to scan directly. -### Can this account still publish to NEAR Social right now? +### Read one SocialDB post exactly as stored right now -Use this when the user story is “I’m about to publish a profile change, widget update, or graph write under `mike.near`, and I want a plain go/no-go answer before I open wallet signing.” +Use this when a product, support tool, or agent already knows the account and wants the live SocialDB post payload without widening into transaction history.
Strategy -

Ask social.near for the two things that matter before you sign anything.

+

Read the current post key first, then fetch that exact post payload from social.near.

-

01RPC view_account makes sure the signer account exists and can actually submit a transaction.

-

02RPC call_function get_account_storage tells you whether the target account has room left on social.near.

-

03RPC call_function is_write_permission_granted only comes into play when a different signer is trying to write on that account’s behalf.

+

01RPC call_function get on mike.near/index/post tells you which post key is current.

+

02RPC call_function get on mike.near/post/main returns the exact stored post payload.

+

03If the next question becomes “which transaction wrote this?”, switch to [Transactions Examples](/tx/examples).

-This is the same question real NEAR Social clients have to answer before they try a write: - -- does the target account already have storage on `social.near`? -- if it does, is there still room left in that storage? -- if a different signer is trying to write under that account, has write permission already been granted? - **Official references** - [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) **What you're doing** -- Check that the signer account itself exists and can pay gas. -- Ask `social.near` how much storage the target account has left. -- If the signer differs from the target account, ask `social.near` whether that delegated write is already allowed. -- Turn those exact RPC answers into one simple “ready now” or “fix this first” summary. +- Read the current post pointer under `mike.near/index/post`. +- Reuse that key to fetch the exact post payload under `mike.near/post/`. +- Stop once you have the exact stored JSON and only widen into history if provenance matters. ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com export SOCIAL_CONTRACT_ID=social.near export ACCOUNT_ID=mike.near -export SIGNER_ACCOUNT_ID=mike.near -``` - -1. Check the signer account itself first. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } - }')" \ - | tee /tmp/social-publish-signer.json >/dev/null - -jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - signer_account_id: $signer_account_id, - amount: .result.amount, - locked: .result.locked, - storage_usage: .result.storage_usage -}' /tmp/social-publish-signer.json ``` -If this query fails, you do not have a signer account to work with. If it succeeds, you know the signer exists and can at least pay gas. - -2. Ask `social.near` how much storage is already available for the account you want to write under. +1. Read the current post pointer first. ```bash -SOCIAL_STORAGE_ARGS_BASE64="$( +INDEX_POST_ARGS_BASE64="$( jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id + keys: [($account_id + "/index/post")] }' | base64 | tr -d '\n' )" @@ -1314,213 +586,53 @@ curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + --arg args_base64 "$INDEX_POST_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { request_type: "call_function", account_id: $account_id, - method_name: "get_account_storage", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-account-storage.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - storage: (.result.result | implode | fromjson), - storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) -}' /tmp/social-account-storage.json -``` - -If `available_bytes` is greater than zero, storage is not the blocker. If this method returns `null` or `available_bytes` is zero, the account needs a `storage_deposit` top-up before a new write can land. - -3. If the signer is different from the target account, check delegated write permission too. - -```bash -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - jq -n --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - signer_matches_target: true, - permission_granted: true, - reason: "owner write" - }' -else - WRITE_PERMISSION_ARGS_BASE64="$( - jq -nc \ - --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ - --arg key "$ACCOUNT_ID" '{ - predecessor_id: $predecessor_id, - key: $key - }' | base64 | tr -d '\n' - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - signer_matches_target: false, - permission_granted: (.result.result | implode | fromjson) - }' -fi -``` - -4. Turn the storage and permission checks into one readable answer. - -```bash -AVAILABLE_BYTES="$( - jq -r ' - .result.result - | if length == 0 then "0" - else (implode | fromjson | .available_bytes // 0 | tostring) - end - ' /tmp/social-account-storage.json -)" - -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - PERMISSION_GRANTED=true -else - PERMISSION_GRANTED="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq -r '.result.result | implode | fromjson' - )" -fi - -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ - --argjson available_bytes "$AVAILABLE_BYTES" \ - --argjson permission_granted "$PERMISSION_GRANTED" '{ - account_id: $account_id, - signer_account_id: $signer_account_id, - storage_ready: ($available_bytes > 0), - permission_ready: $permission_granted, - ready_to_publish_now: (($available_bytes > 0) and $permission_granted) - }' -``` - -If that final object says `ready_to_publish_now: true`, RPC has already answered the question. If it says `false`, you know whether the blocker is storage, delegated permission, or both. - -**Why this next step?** - -This keeps the whole question on exact on-chain reads. `social.near` itself answers whether the target account has room left and whether a delegated signer is already allowed to write. That is a better NEAR Social readiness check than guessing from wallet state alone. - -### What does `mob.near/widget/Profile` actually contain right now? - -Use this when the question is simple: “show me the live source for `mob.near/widget/Profile`, tell me when that widget key was last written, and keep me on exact RPC reads.” - -
-
- Strategy -

Stay on exact SocialDB reads, and only widen into history if the question turns forensic.

-
-
-

01RPC call_function keys shows the widget catalog and the last-write blocks under mob.near/widget/*.

-

02RPC call_function get reads the exact source for widget/Profile.

-

03If the next question becomes “which transaction wrote this?”, hand off to the widget proof recipe in /tx/examples.

-
-
- -**Official references** - -- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) - -**What you're doing** - -- Ask `social.near` for the widget catalog under `mob.near`. -- Keep the block heights so you know when each widget key last changed. -- Confirm that `Profile` is really there, then read its exact source through the same contract. -- If the next question becomes “which transaction wrote this widget?”, switch to the NEAR Social proof recipes in [Transactions Examples](/tx/examples). - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mob.near -export WIDGET_NAME=Profile -``` - -1. List the widget catalog and keep the last-write block heights. - -```bash -WIDGET_KEYS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/widget/*")], - options: {return_type: "BlockHeight"} - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "keys", + method_name: "get", args_base64: $args_base64, finality: "final" } }')" \ - | tee /tmp/social-widget-keys.json >/dev/null + | tee /tmp/social-index-post.json >/dev/null jq --arg account_id "$ACCOUNT_ID" ' .result.result | implode | fromjson - | .[$account_id].widget - | to_entries - | sort_by(.value * -1) - | map({ - widget_name: .key, - last_write_block: .value - }) - | .[0:20] -' /tmp/social-widget-keys.json + | { + account_id: $account_id, + index_entry: (.[$account_id].index.post | fromjson), + current_post_key: (.[$account_id].index.post | fromjson | .key) + } +' /tmp/social-index-post.json ``` -2. Confirm that `Profile` is really in the catalog, then print the exact source stored in SocialDB. +At the time of writing, the current post key for `mike.near` was `main`. + +2. Read that exact post payload. ```bash -WIDGET_GET_ARGS_BASE64="$( +POST_KEY="$( + jq -r --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].index.post + | fromjson + | .key + ' /tmp/social-index-post.json +)" + +POST_ARGS_BASE64="$( jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] + --arg post_key "$POST_KEY" '{ + keys: [($account_id + "/post/" + $post_key)] }' | base64 | tr -d '\n' )" @@ -1528,7 +640,7 @@ curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + --arg args_base64 "$POST_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", @@ -1540,131 +652,26 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/social-widget-source.json >/dev/null - -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:25] - | join("\n") - ' /tmp/social-widget-source.json -``` - -3. Pull the last-write block for the same widget so you keep one useful historical anchor. + | tee /tmp/social-post-main.json >/dev/null -```bash -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - ' /tmp/social-widget-keys.json \ - | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +jq --arg account_id "$ACCOUNT_ID" --arg post_key "$POST_KEY" ' + .result.result + | implode + | fromjson + | { + account_id: $account_id, + post_key: $post_key, + post: (.[$account_id].post[$post_key] | fromjson) + } +' /tmp/social-post-main.json ``` -At the time of writing, the live last-write block for `mob.near/widget/Profile` was `86494825`. Keep that block if you later want to prove which transaction wrote this version. +That gives you the exact JSON stored for the current post, including fields like `type`, `text`, and `image`. **Why this next step?** -Sometimes the right RPC answer is just: here is the widget, here is the live source, and here is the block height to keep if provenance matters later. - -## Common jobs - -### Check exact account or access-key state - -**Start here** - -- [View Account](/rpc/account/view-account) for exact account fields. -- [View Access Key](/rpc/account/view-access-key) or [View Access Key List](/rpc/account/view-access-key-list) for key inspection. - -**Next page if needed** - -- [FastNear API full account view](/api/v1/account-full) if you want a readable holdings summary after checking the exact RPC state. -- [Transactions API account history](/tx/account) if the next question is "what has this account been doing?" - -**Stop when** - -- The RPC fields already answer the state or permission question. - -**Switch when** - -- The user wants balances, NFTs, staking, or another readable account summary. -- The user really wants recent activity history rather than current state. - -### Check one exact block or protocol snapshot - -**Start here** - -- [Block by ID](/rpc/block/block-by-id) or [Block by Height](/rpc/block/block-by-height) when you already know which block you care about. -- [Latest Block](/rpc/protocol/latest-block) when the question is “what is the current head right now?” -- [Status](/rpc/protocol/status), [Health](/rpc/protocol/health), or [Network Info](/rpc/protocol/network-info) when the real question is about node or network condition, not transaction history. - -**Next page if needed** - -- [Block Effects](/rpc/block/block-effects) if the block payload tells you what block you are looking at but not what changed in it. -- [Transactions API block history](/tx/block) or [Transactions API block range](/tx/blocks) if the question becomes “what actually happened around this block?” rather than “what does this block payload say?” - -**Stop when** - -- One exact block or protocol response already answers the question directly. - -**Switch when** - -- The user wants to watch fresh blocks arrive rather than inspect one exact snapshot. Move to [NEAR Data API](/neardata). -- The user needs a readable story across many transactions, not just one block payload. Move to [Transactions API](/tx). - -### What does this contract return right now? - -**Start here** - -- Start with the counter example above when the real decision is “should I use `call_function` or `view_state`?” or “can I read this storage directly instead of calling a method?” -- [Call Function](/rpc/contract/call-function) when you already know the view method you want and just need the exact return value. -- [View State](/rpc/contract/view-state) when the real question is about raw contract storage or key prefixes, not a method result. -- [View Code](/rpc/contract/view-code) when the real question is “is there code here at all?” or “which code hash is deployed?” - -**Next page if needed** - -- [FastNear API](/api) if the raw contract answer is technically correct but the user actually wanted a readable holdings or account summary. -- [KV FastData API](/fastdata/kv) if the next question becomes “what did this storage key look like over time?” instead of “what is it right now?” - -**Stop when** - -- The view call, storage read, or code hash already answers the contract question exactly. - -**Switch when** - -- The user wants indexed history or a simpler summary instead of raw contract output. -- The user stops asking “what does it return right now?” and starts asking “what changed over time?” - -### Send and confirm a transaction - -**Start here** - -- Start with the worked example above when the real question is which submission endpoint to use and how to track the transaction through completion. -- [Send Transaction](/rpc/transaction/send-tx) when you want RPC submission with explicit waiting semantics. -- [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) or [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit) when those exact submission modes are the point. -- [Transaction Status](/rpc/transaction/tx-status) to confirm the final result. - -**Next page if needed** - -- [Transactions by Hash](/tx/transactions) for a readable history record after submission. -- [Receipt Lookup](/tx/receipt) when you need to investigate downstream execution or callback flow. -- [Transactions Examples](/tx/examples) when the next question is “one batched action failed, did the earlier actions roll back too?” - -**Stop when** - -- You have the submission result and final status you needed. - -**Switch when** +This is the clean RPC pattern for SocialDB: ask the contract for one exact key, decode the returned JSON, and stop. If the question turns into “who wrote this and when?”, move to the transaction examples instead of trying to brute-force raw `social.near` state. -- The next question is about receipts, affected accounts, or execution history in a human-friendly order. -- You need a fuller investigation workflow instead of one status check. ## Common mistakes diff --git a/docs/snapshots/examples.mdx b/docs/snapshots/examples.mdx index 78b31c1..ef2b8aa 100644 --- a/docs/snapshots/examples.mdx +++ b/docs/snapshots/examples.mdx @@ -12,6 +12,8 @@ page_actions: If the job is simply “bring a mainnet RPC node back fast,” start with one runnable command. +These helpers are maintained by FastNear and are meant to optimize for recovery speed. If your environment requires change review, fetch the script first, inspect it, and only then execute it instead of piping it straight to `bash`. + ```bash DATA_PATH=~/.near/data @@ -108,79 +110,6 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ Hot and cold archival data need to come from the same snapshot cut. Reusing one captured `LATEST` value across both commands keeps the archival dataset internally consistent and makes later nearcore configuration much less surprising. -## Common jobs - -### Bootstrap an optimized mainnet `fast-rpc` node - -**Start here** - -- Use the optimized mainnet `fast-rpc` command anchor above. - -**Next page if needed** - -- [Mainnet snapshots](/snapshots/mainnet) if you need to tune `THREADS`, `BWLIMIT`, or a custom `DATA_PATH`. - -**Stop when** - -- You have the right `fast-rpc` command and environment variables for the target machine. - -**Switch when** - -- The real requirement is archival retention rather than fast sync. - -### Recover a standard RPC node to the default nearcore path - -**Start here** - -- Use the standard mainnet RPC command anchor above. - -**Next page if needed** - -- [Mainnet snapshots](/snapshots/mainnet) if you need to tune `DATA_PATH`, `THREADS`, or bandwidth settings further. - -**Stop when** - -- You can run the correct RPC recovery command with the expected data path. - -**Switch when** - -- The operator actually needs archival history or hot/cold data placement. - -### Bring up archival mainnet hot and cold data correctly - -**Start here** - -- Use the archival walkthrough above. - -**Next page if needed** - -- Fetch the latest archival snapshot height, then run separate hot-data and cold-data downloads with the correct paths. - -**Stop when** - -- The hot-data and cold-data plan is clear and the order of operations is correct. - -**Switch when** - -- The operator is really looking for general nearcore bootstrap guidance beyond FastNear snapshots. - -### Bootstrap testnet archival hot data - -**Start here** - -- [Testnet snapshots](/snapshots/testnet), archival section. - -**Next page if needed** - -- Fetch the latest testnet archival snapshot height before the download step. - -**Stop when** - -- You have the right testnet archival hot-data command and block anchor. - -**Switch when** - -- The user is not doing infrastructure bootstrap and should be routed back to API or RPC docs. ## Common mistakes diff --git a/docs/transaction-flow/advanced-features.mdx b/docs/transaction-flow/advanced-features.mdx index 62db7b4..76ad575 100644 --- a/docs/transaction-flow/advanced-features.mdx +++ b/docs/transaction-flow/advanced-features.mdx @@ -161,22 +161,13 @@ submit(signed_tx); ### Step 3: Runtime Processing -```mermaid -sequenceDiagram - participant User as Alice (sender) - participant Relayer as Relayer - participant RT as Runtime - participant Contract as contract.near - - User->>Relayer: SignedDelegateAction - Relayer->>RT: Transaction (Delegate action) - RT->>RT: Verify delegate signature (NEP-461) - RT->>RT: Check expiration - RT->>RT: Validate access key & nonce - RT->>Contract: Execute inner actions as Alice - Contract-->>RT: Result - RT-->>Relayer: Gas refund -``` +For docs users, the relayer flow is the only part worth keeping in short-term memory: + +1. the user signs a `DelegateAction` +2. the relayer wraps and submits it +3. the runtime verifies signature, expiry, and access-key rules +4. the inner actions execute as the user +5. gas refund returns to the relayer **Source:** `runtime/runtime/src/actions.rs` @@ -415,26 +406,12 @@ pub fn on_oracle_data(&mut self) { ### Flow Diagram -```mermaid -sequenceDiagram - participant User - participant Contract - participant Oracle as Off-Chain Oracle - participant RT as Runtime - - User->>Contract: request_oracle_data("ETH/USD") - Contract->>RT: promise_yield_create() - RT-->>Contract: data_id - Contract->>Contract: Store data_id, emit event - Contract-->>User: Transaction complete (yield pending) - - Note over Oracle: Monitors events, fetches data - - Oracle->>Contract: resume_oracle(data_id, price_data) - Contract->>RT: promise_yield_resume() - RT->>Contract: on_oracle_data() callback - Contract->>Contract: Process price data -``` +For docs users, the yield/resume path is: + +1. the contract yields and gets a `data_id` +2. some external system keeps that `data_id` +3. the external system later resumes with payload data +4. the callback runs with either data or failure ## Timeout Mechanism diff --git a/docs/transaction-flow/finality.mdx b/docs/transaction-flow/finality.mdx index 88dbd40..8e62e5b 100644 --- a/docs/transaction-flow/finality.mdx +++ b/docs/transaction-flow/finality.mdx @@ -73,16 +73,7 @@ pub enum ExecutionStatus { A single transaction can produce many outcomes: -```mermaid -flowchart TD - TX1[Transaction Hash: TX1] --> TO[TransactionOutcome] - TO -->|status: SuccessReceiptId R1| R1[ReceiptOutcome R1] - TO -->|receipt_ids: R1, R2| R2[ReceiptOutcome R2] - R1 -->|status: SuccessValue| R3[ReceiptOutcome R3] - R1 -->|receipt_ids: R3| done1[Done] - R2 -->|status: SuccessValue| done2[Done] - R3 -->|Refund| done3[Done] -``` +For docs users, the important shape is simpler than the full outcome tree: one `tx_hash` gives you one `transaction_outcome` plus zero or more `receipts_outcome`. Your job is to combine those objects into one readable story of what happened. **Example outcome tree:** ``` diff --git a/docs/transaction-flow/foundations.mdx b/docs/transaction-flow/foundations.mdx index fb8b4f9..6066122 100644 --- a/docs/transaction-flow/foundations.mdx +++ b/docs/transaction-flow/foundations.mdx @@ -1,6 +1,6 @@ --- title: Foundations -description: Transaction structure, actions, and serialization formats in NEAR +description: Transaction fields, actions, and serialization formats that show up in RPC submission and status responses slug: /transaction-flow/foundations sidebar_position: 1 keywords: @@ -13,7 +13,7 @@ keywords: # Foundations -This section covers the fundamental building blocks of NEAR transactions: data structures, action types, account models, and serialization formats. +Use this section to understand the fields you actually see in signed transactions, RPC submission, and status responses. The goal is not to memorize internal actors; it is to recognize what the signer, receiver, nonce, block hash, and action list mean when you inspect a transaction. ## What is a Transaction? @@ -30,35 +30,17 @@ Every transaction in NEAR has these fundamental properties: The atomicity guarantee applies only within a single shard. Cross-shard operations (function calls to contracts on different shards) are executed asynchronously and do **not** have atomic semantics. See [Async Model](./async-model) for details. ::: -### The Transaction Lifecycle +### What matters for docs users ```mermaid flowchart LR - subgraph Creation - A[Client Signs Tx] - end - subgraph Submission - B[RPC Layer] - end - subgraph Validation - C[Handler Actor] - end - subgraph Network - D[P2P Forward] - end - subgraph Block Production - E[Chunk Producer] - end - subgraph Execution - F[Runtime Applies Actions] - end - A -->|base64/borsh| B - B -->|struct| C - C -->|struct| D - D -->|borsh| E - E -->|struct| F + A["SignedTransaction fields"] --> B["Submit or inspect with RPC"] + B --> C["Get tx hash and actions"] + C --> D["Interpret outcomes and receipts"] ``` +For this docs site, that is the useful mental model. The internal handoff through pools, chunk producers, and runtime matters only if it changes the meaning of a field you are already reading. + ## The SignedTransaction Data Structure The `SignedTransaction` is the fundamental unit that travels through the system. diff --git a/docs/transaction-flow/gas-economics.mdx b/docs/transaction-flow/gas-economics.mdx index f507a32..5c9b551 100644 --- a/docs/transaction-flow/gas-economics.mdx +++ b/docs/transaction-flow/gas-economics.mdx @@ -174,16 +174,12 @@ fn compute_gas_refund( ### Refund Flow -```mermaid -flowchart TD - A[Prepaid Gas] --> B[Execute Transaction] - B --> C{Gas Burnt} - C --> D[Gross Refund = Prepaid - Burnt] - D --> E[Apply NEP-536 Penalty] - E --> F[Net Refund] - F --> G[Convert to NEAR at Original Price] - G --> H[Refund Receipt to Signer] -``` +For docs users, the refund path is simpler than the runtime code: + +1. gas is prepaid up front +2. execution burns part of that gas +3. unused gas becomes a refund amount +4. the refund comes back as a later receipt ### Refund Types @@ -243,14 +239,7 @@ All unused gas is currently refunded in full. Gas price adjusts based on block utilization: -```mermaid -flowchart LR - A[Block Utilization] --> B{> 50%?} - B -->|Yes| C[Gas Price Increases] - B -->|No| D[Gas Price Decreases] - C --> E[Discourages spam] - D --> F[Encourages usage] -``` +When blocks are busier, protocol gas price rises. When blocks are quieter, it falls. For docs users, the important part is that this adjustment is protocol-driven, not a fee-bidding market. The adjustment follows an exponential smoothing algorithm bounded by protocol parameters. @@ -397,19 +386,7 @@ console.log(`Tokens burnt: ${Number(outcome.tokens_burnt) / 1e24} NEAR`); Refunds arrive as receipts in subsequent blocks: -```mermaid -sequenceDiagram - participant User - participant ShardA as Shard A - participant ShardB as Shard B - - User->>ShardA: Transaction (prepay 100 TGas) - ShardA->>ShardA: Execute (burn 30 TGas) - ShardA->>ShardB: Cross-shard receipt - ShardB->>ShardB: Execute - ShardB->>ShardA: Refund receipt (unused gas) - ShardA->>ShardA: Apply refund to User balance -``` +Refunds often land in later blocks, especially when cross-shard receipts are involved. Do not expect the account balance view to reflect them immediately at the moment the first transaction outcome appears. Don't expect instant balance updates after transaction completion. diff --git a/docs/transaction-flow/index.mdx b/docs/transaction-flow/index.mdx index 01ea783..a063ba4 100644 --- a/docs/transaction-flow/index.mdx +++ b/docs/transaction-flow/index.mdx @@ -1,6 +1,6 @@ --- title: Transaction Flow in NEAR Protocol -description: Complete guide to transaction lifecycle from JSON-RPC submission to validator execution +description: Practical guide to the user-visible stages of a NEAR transaction, from RPC submission to receipts and finality sidebar_position: 0 slug: /transaction-flow sidebar_label: Overview @@ -15,16 +15,13 @@ keywords: # Transaction Flow in NEAR Protocol -This guide traces the complete lifecycle of a transaction in NEAR Protocol, from JSON-RPC submission to validator execution. +This guide focuses on the stages a docs user can actually observe: submission, transaction status, receipt fan-out, and final outcome. It keeps protocol background only where that background changes what you should query next. ```mermaid flowchart LR - Client["Client Signs Tx"] --> RPC["RPC Layer"] - RPC --> Validation["Validation"] - Validation --> P2P["P2P Forward"] - P2P --> Pool["Transaction Pool"] - Pool --> Chunk["Chunk Producer"] - Chunk --> Runtime["Runtime Execution"] + Submit["Submit signed transaction"] --> Track["Track tx hash"] + Track --> Receipts["Inspect receipts only if needed"] + Receipts --> Final["Confirm final outcome or final state"] ``` ## Quick Reference diff --git a/docs/transaction-flow/infrastructure.mdx b/docs/transaction-flow/infrastructure.mdx index 16135e8..3b41271 100644 --- a/docs/transaction-flow/infrastructure.mdx +++ b/docs/transaction-flow/infrastructure.mdx @@ -303,7 +303,7 @@ fn account_id_to_shard_id(&self, account_id: &AccountId) -> ShardId { } ``` -**Current mainnet boundaries:** +**Example contiguous boundaries:** ``` boundary_accounts: ["aurora", "aurora-0", "kkuuue2akv_1630967379.near"] @@ -330,14 +330,7 @@ pub struct ShardLayoutV2 { Accounts map to shards based on **alphabetical ordering**: -```mermaid -flowchart LR - A[account_id] --> B{Compare to boundaries} - B -->|"< aurora"| S0[Shard 0] - B -->|">= aurora, < aurora-0"| S1[Shard 1] - B -->|">= aurora-0, < kkuuue..."| S2[Shard 2] - B -->|">= kkuuue..."| S3[Shard 3] -``` +For docs users, the exact boundary table is rarely the task. The practical takeaway is simpler: the signer account determines the first shard that sees the transaction, and subaccounts can land on a different shard than their parent because sorting uses the full account name. :::warning Account Naming Implications Alphabetical ordering uses the **full account name**. Subaccounts sort by their full name, not parent: diff --git a/docs/transaction-flow/network-blocks.mdx b/docs/transaction-flow/network-blocks.mdx index e96ebed..c0a5240 100644 --- a/docs/transaction-flow/network-blocks.mdx +++ b/docs/transaction-flow/network-blocks.mdx @@ -202,21 +202,11 @@ pub enum InsertTransactionResult { ## Round-Robin Fair Selection -When a chunk producer builds a chunk, they use a **round-robin algorithm** that gives each account a fair chance: - -```mermaid -flowchart TD - A[Start at randomized position] --> B[For each account/key group] - B --> C[Pop lowest-nonce transaction] - C --> D{Valid?} - D -->|Yes| E[Add to chunk] - D -->|No| F[Drop or skip] - E --> G{Limits reached?} - F --> G - G -->|No| H[Rotate to next group] - H --> B - G -->|Yes| I[Chunk complete] -``` +For docs users, the exact scheduler is less important than three user-visible consequences: + +- inclusion is fair rather than fee-bid driven +- transactions for the same account/key still respect nonce order +- full chunks delay inclusion instead of reordering for higher bids ### Selection Properties @@ -277,21 +267,7 @@ A chunk contains: - Gas used and limits ### Production Flow - -```mermaid -sequenceDiagram - participant BP as Block Producer - participant CP as Chunk Producer - participant Net as Network - - BP->>CP: Signal "produce chunk for shard X at height H" - CP->>CP: Select transactions from pool - CP->>CP: Execute transactions (create receipts) - CP->>CP: Create chunk header and body - CP->>BP: Send chunk - BP->>BP: Assemble block with all chunks - BP->>Net: Propagate block -``` +From a docs perspective, chunk production matters only because it determines when a transaction or receipt first becomes visible in block-scoped data. You usually do not need the producer handshake; you need the resulting block height, shard placement, and receipt timing. ### Chunk Header @@ -319,31 +295,14 @@ Once transactions become receipts, they're executed in a **strict three-phase or **Source:** `runtime/runtime/src/lib.rs`, function `process_receipts()` -```mermaid -flowchart TD - subgraph Phase1[Phase 1: LOCAL RECEIPTS] - L1[Receipts from THIS block's transactions] - L2[Order: Transaction inclusion order] - end - - subgraph Phase2[Phase 2: DELAYED RECEIPTS] - D1[Backlog from previous blocks] - D2[Order: Strict FIFO queue] - end - - subgraph Phase3[Phase 3: INCOMING RECEIPTS] - I1[Cross-shard receipts arriving THIS block] - I2[Order: Chunk order - deterministic] - end - - subgraph Phase4[Phase 4: YIELD TIMEOUTS] - Y1[NEP-519 promises that expired] - end - - Phase1 --> Phase2 - Phase2 --> Phase3 - Phase3 --> Phase4 -``` +For docs users, keep the ordering rule simpler than the runtime implementation: + +1. local receipts from this block's transactions +2. delayed receipts from older backlog +3. incoming cross-shard receipts +4. yield timeouts + +The exact phase boundary matters only when you are explaining why one receipt became visible before another. ### Phase Details diff --git a/docs/transaction-flow/runtime-execution.mdx b/docs/transaction-flow/runtime-execution.mdx index 261b1b2..9128d1b 100644 --- a/docs/transaction-flow/runtime-execution.mdx +++ b/docs/transaction-flow/runtime-execution.mdx @@ -75,15 +75,12 @@ Actions execute in order within a transaction. All actions in a single transacti ### Execution Order -```mermaid -flowchart TD - A[Validate all actions] --> B{All valid?} - B -->|No| C[Return validation error] - B -->|Yes| D[Execute actions sequentially] - D --> E[Generate receipts for cross-shard effects] - E --> F[Update state] - F --> G[Return execution outcome] -``` +For docs users, the actionable model is short: + +1. validate the batch +2. execute actions in order +3. emit receipts for async work +4. return one execution outcome The detailed flow: diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index d5fe8cc..c043024 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -51,27 +51,27 @@ This is the shortest way to answer “did funds move here, and which receipt sho ## Worked walkthrough -### Find one suspicious transfer, then chase its receipt +### Find one outgoing transfer, then pivot to execution details if needed -Use this when the user story is “I know funds moved, but I want the exact execution anchor behind that movement without dragging in the whole account history yet.” +Use this when the user story is “I know this account sent funds in this window, and I may need the exact execution anchor behind one row, but I do not want the whole account history yet.”
Strategy -

Stay narrow on movement first, then pivot once into execution history.

+

Stay narrow on movement first, then pivot once into execution history only if the transfer row is not enough.

01POST /v0/transfers gives you the tight outgoing window and the specific movement worth chasing.

-

02jq lifts one receipt_id without dragging in the rest of the account history.

-

03POST /v0/receipt turns that movement into one execution anchor you can keep following in /tx.

+

02Print the rows first, then choose one transfer_index before lifting its receipt_id.

+

03POST /v0/receipt is the optional widening step when you need execution history behind that transfer.

**What you're doing** - Query a bounded outgoing transfer window for one account on mainnet. -- Pull out one transfer that looks like the movement you care about. -- Reuse its `receipt_id` in Transactions API to move from balance movement into execution history. +- Pull out one transfer row that looks like the movement you care about. +- Reuse its `receipt_id` in Transactions API only if you need to move from balance movement into execution history. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com @@ -79,113 +79,66 @@ TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=YOUR_ACCOUNT_ID FROM_TIMESTAMP_MS=1711929600000 TO_TIMESTAMP_MS=1712016000000 +TRANSFER_INDEX=0 -RECEIPT_ID="$( - curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ - account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, - desc: true, - limit: 10 - }')" \ - | tee /tmp/transfers-window.json \ - | jq -r '.transfers[0].receipt_id' -)" +curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | tee /tmp/transfers-window.json >/dev/null jq '{ resume_token, transfers: [ - .transfers[] + .transfers + | to_entries[] | { - transaction_id, - receipt_id, - asset_id, - amount, - other_account_id, - block_height + transfer_index: .key, + transaction_id: .value.transaction_id, + receipt_id: .value.receipt_id, + asset_id: .value.asset_id, + amount: .value.amount, + other_account_id: .value.other_account_id, + block_height: .value.block_height } ] }' /tmp/transfers-window.json -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' -``` - -**Why this next step?** - -The transfer query answers the first question quickly: did this account send funds in this window, and to whom? Looking up the `receipt_id` gives you the exact execution anchor for that movement without dragging in the whole account history yet. If you still need more rows afterward, keep paginating with the same `resume_token` and unchanged filters. - -## Common jobs - -### Find outgoing transfers for one account in a narrow time window - -**Start here** - -- [Query Transfers](/transfers/query) with the account, outgoing direction, and the tightest useful time filter. - -**Next page if needed** - -- Narrow again by asset or amount if the response still contains unrelated transfers. - -**Stop when** - -- You can answer who sent what, when, and in which asset. - -**Switch when** - -- The user asks why the transfer happened or what other actions surrounded it. Move to [Transactions API](/tx). - -### Keep paging through a transfer feed without losing your place - -**Start here** - -- [Query Transfers](/transfers/query) for the first page of recent events, using the tightest stable filters you can. - -**Next page if needed** - -- Reuse the exact returned `resume_token` to fetch the next page with the same filters. -- Keep the filters unchanged while you paginate, or you are no longer looking at the same feed. - -**Stop when** - -- You have enough pages to answer the requested feed, support review, or compliance check. - -**Switch when** - -- The user asks for transaction metadata beyond transfer events. -- The feed needs balances or holdings, not just movement. Move to [FastNear API](/api). - -### Escalate from transfer-only history to full transaction investigation - -**Start here** - -- [Query Transfers](/transfers/query) to identify the specific transfer events that matter. - -**Next page if needed** +RECEIPT_ID="$( + jq -r --argjson transfer_index "$TRANSFER_INDEX" \ + '.transfers[$transfer_index].receipt_id // empty' \ + /tmp/transfers-window.json +)" -- [Transactions API account history](/tx/account) if the user wants the surrounding execution story for the same account. -- [Transactions by Hash](/tx/transactions) when you already know which transaction to inspect next. +printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" +printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" -**Stop when** +if [ -n "$RECEIPT_ID" ]; then + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' +fi +``` -- You have identified the right transfer event and the right next API to open. +**Why this next step?** -**Switch when** +The transfer query answers the first question quickly: did this account send funds in this window, and to whom? Looking up the `receipt_id` is the optional next step when the transfer row itself is not enough and you need the execution anchor behind it. If you still need more rows afterward, keep paginating with the same `resume_token` and unchanged filters. -- The user explicitly needs receipt-level detail or exact RPC confirmation. Move to [Transactions API](/tx) first, then [RPC Reference](/rpc) if needed. ## Common mistakes diff --git a/docs/tx/berry-club.mdx b/docs/tx/berry-club.mdx index ea0f9f5..3924d7f 100644 --- a/docs/tx/berry-club.mdx +++ b/docs/tx/berry-club.mdx @@ -1,8 +1,8 @@ --- -sidebar_label: Berry Club +sidebar_label: Berry Club Case Study slug: /tx/examples/berry-club -title: "Berry Club: Reconstruct historical boards" -description: "Use Transactions API, RPC get_lines, and replayed draw calls to reconstruct Berry Club boards across historical eras." +title: "Berry Club Case Study: Read the live board, then reconstruct one era" +description: "A case study that starts with the live Berry Club board from RPC get_lines, then uses Transactions API to reconstruct one older era." displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -14,138 +14,71 @@ keywords: - get_lines - draw - pixel board - - historical snapshots --- import Link from '@site/src/components/LocalizedLink'; -import BerryClubSnapshotGallery from '@site/src/components/BerryClubSnapshotGallery'; -import berryClubSnapshots from '@site/src/data/berryClubSnapshots.json'; +import BerryClubLiveBoard from '@site/src/components/BerryClubLiveBoard'; -{/* FASTNEAR_AI_DISCOVERY: This case study shows how to reconstruct Berry Club boards with FastNear. It separates current-state reads via get_lines from historical archaeology via block ranges, account history, transaction hydration, and replayed draw payloads. */} +{/* FASTNEAR_AI_DISCOVERY: This case study shows the shortest useful Berry Club flow: read the live board with RPC get_lines, then use Transactions API only when you need to reconstruct one older era from draw calls. */} -# Berry Club: Reconstruct historical boards +# Berry Club Case Study: Read the live board, then reconstruct one era -Use this when the question is: “what did Berry Club look like during one era, and which `draw` calls made the board look that way?” +Use this case study when the live board is easy to read, but you need one historical reconstruction path behind it. -This is a read-only Transactions case study. If all you need is the board right now, use `get_lines` and stop. If you want to explain how the board got there, switch to block history, account history, hydrated `draw` calls, and replay. +Start with the live board. If that already answers the question, stop there. -
-
- Strategy -

Read the live board first, bound the era second, and only then replay the draws that explain it.

-
-
-

01RPC call_function get_lines gives the current 50x50 board and tells you what “now” looks like.

-

02POST /v0/blocks plus POST /v0/account bounds one era and yields candidate draw hashes.

-

03POST /v0/transactions hydrates those draws so you can replay them into historical checkpoints.

-
-
+Only switch to Transactions API when the question becomes historical: “what did Berry Club look like during one older era, and which `draw` calls made it look that way?” -Keep these close: - -- [js.fastnear.com](https://js.fastnear.com/) -- [fastnear/js-monorepo](https://github.com/fastnear/js-monorepo) -- Transactions API: Account History -- Transactions API: Transactions by Hash -- Transactions API: Block Range -- RPC: call_function - -Berry Club archaeology is a mainnet-only story in this guide. The rendered checkpoints below come from reproducible mainnet snapshot data checked into this repo. - -## The short version - -Berry Club gives you a clean current-state read through `get_lines`, but it does not give you a ready-made “board at block N” endpoint. - -That splits the job into two parts: - -- use RPC `call_function` when the question is “what does the board look like now?” -- use indexed history when the question is “which writes produced that board?” -- use archival RPC only when you want to materialize a known checkpoint directly - -```mermaid -flowchart TD - A["RPC call_function: get_lines"] --> B["Current 50x50 board"] - C["Transactions API: /v0/blocks"] --> D["Bound the era"] - D --> E["/v0/account for berryclub.ek.near"] - E --> F["Candidate draw transaction hashes"] - F --> G["/v0/transactions hydration"] - G --> H["Replay draw writes into a historical board"] -``` - -## Why Berry Club is a good NEAR history example - -Berry Club gives you both sides of the problem in one contract: - -- a clean current-state read through `get_lines` -- a long-running stream of `draw` transactions with ordinary `FunctionCall` args -- a board format simple enough to decode and replay in normal JavaScript + -That makes it a very NEAR-native history example: one view method for current state, one write method for changes, and indexed history when you want to explain how the current state came to exist. +## 1. Read the live board -## 1. Read the current board first +This is the shortest useful read: -The live demo uses `berryclub.ek.near` and calls `get_lines` as a read-only view: +```bash +ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" -```javascript -await near.view({ - contractId: 'berryclub.ek.near', - methodName: 'get_lines', - args: { - lines: [...Array(50).keys()], - }, -}); +curl -sS https://rpc.mainnet.fastnear.com \ + -H 'content-type: application/json' \ + --data "{ + \"jsonrpc\": \"2.0\", + \"id\": \"berry-live-board\", + \"method\": \"query\", + \"params\": { + \"request_type\": \"call_function\", + \"finality\": \"final\", + \"account_id\": \"berryclub.ek.near\", + \"method_name\": \"get_lines\", + \"args_base64\": \"$ARGS_BASE64\" + } + }" | jq '.result | {block_height, line_count: (.result | implode | fromjson | length)}' ``` -That is the “board right now” path. It does not tell you how the board got there. +That gives you the current 50x50 board from the contract itself. The only decode step is turning each returned base64 line into 50 pixel colors. -| Question | Best surface | Why | -| --- | --- | --- | -| what does the board look like now? | RPC `call_function` | the contract already exposes current state through `get_lines` | -| which draws happened in this era? | `/v0/account` + `/v0/transactions` | indexed history gives you bounded candidate writes and hydrated args | -| what did the board look like at a known checkpoint? | archival RPC or full replay | use archival state for direct materialization, or replay historical writes yourself | +## 2. Reconstruct one older era -## 2. Decode `get_lines` into a 50x50 grid +When you need history, keep the flow short: -The useful part of the `js.fastnear.com` Berry Club markup is the line decoder: +1. bound one era +2. list candidate `draw` transactions for `berryclub.ek.near` +3. hydrate those hashes +4. replay the `pixels` arrays oldest-first -- each returned line is base64 -- decode it to bytes -- skip the first 4 bytes -- then read 32-bit little-endian colors every 8 bytes - -```javascript -function decodeLine(encodedLine) { - const bytes = Buffer.from(encodedLine, 'base64'); - const colors = []; - - for (let offset = 4; offset < bytes.length; offset += 8) { - colors.push(bytes.readUInt32LE(offset) & 0xffffff); - } - - return colors; -} -``` - -Apply that to all 50 returned lines and you have a full 50x50 board ready to render. - -## 3. Bound the era you care about - -Start by bounding the era before you hunt for draws. The checked-in launch checkpoint in this repo sits at block `21898354`, and the midpoint checkpoint sits at block `97601515`. - -Use the block-range surface first: - -```bash -curl -sS https://tx.main.fastnear.com/v0/blocks \ - -H 'content-type: application/json' \ - --data '{ - "from_block_height": 21898350, - "to_block_height": 21898355, - "desc": false, - "limit": 5 - }' -``` - -Then switch to account history and ask for Berry Club activity inside a bounded block window: +This example uses a narrow window around block `97601515`: ```bash curl -sS https://tx.main.fastnear.com/v0/account \ @@ -157,21 +90,14 @@ curl -sS https://tx.main.fastnear.com/v0/account \ "is_real_receiver": true, "from_tx_block_height": 97576515, "to_tx_block_height": 97601516, - "desc": true, - "limit": 40 - }' + "desc": false, + "limit": 200 + }' | jq '.account_txs | map({transaction_hash, tx_block_height}) | .[-5:]' ``` -This is the useful sequence: - -- `/v0/blocks` helps you reason about the block neighborhood -- `/v0/account` gives you candidate Berry Club transaction hashes in that neighborhood - -## 4. Hydrate and keep only `draw` calls - -Once you have candidate hashes, hydrate them and keep only top-level `draw` calls whose receiver is `berryclub.ek.near`. +If you do not know the window yet, `/v0/blocks` can help you choose one first. It is not part of the core Berry Club flow. -The payloads are just normal `FunctionCall` args shaped like `{ pixels: [...] }`: +Hydrate the candidate hashes and keep only top-level `draw` calls: ```bash curl -sS https://tx.main.fastnear.com/v0/transactions \ @@ -185,80 +111,29 @@ curl -sS https://tx.main.fastnear.com/v0/transactions \ | select(.transaction.receiver_id == "berryclub.ek.near") | .transaction.actions[]?.FunctionCall | select(.method_name == "draw") - | { - method_name, - args: (.args | @base64d | fromjson) - }' + | {method_name, args: (.args | @base64d | fromjson)}' ``` -That gives you exactly what replay needs: - -- which transaction wrote pixels -- which coordinates were touched -- which colors were written - -## 5. Replay historical draws into a board - -For a full replay, keep a 50x50 array in memory and apply hydrated `draw` transactions oldest-first. +Replay the `pixels` arrays oldest-first: ```javascript const board = Array.from({ length: 50 }, () => Array(50).fill(0)); -function applyDraw(boardState, drawArgs) { - for (const pixel of drawArgs.pixels) { +for (const drawTx of drawTransactionsOldestFirst) { + for (const pixel of drawTx.args.pixels) { if (pixel.x < 0 || pixel.x >= 50 || pixel.y < 0 || pixel.y >= 50) { continue; } - boardState[pixel.y][pixel.x] = pixel.color; + board[pixel.y][pixel.x] = pixel.color; } } - -for (const drawTx of drawTransactionsOldestFirst) { - applyDraw(board, drawTx.args); -} ``` -Keep the split straight: - -- `get_lines` is current state -- `tx/account` plus `tx/transactions` is replay material +That is the whole historical pattern. Berry Club does not expose a ready-made “board at block N” endpoint, so older eras come from replaying `draw` writes. -## 6. Rendered checkpoints by era +## Related guides -The gallery below uses checked-in snapshot data generated from Berry Club mainnet history: - -- `launch` is the latest successful `draw` within the first 24 hours after the first successful draw -- `mid` is the latest successful `draw` at or before the midpoint timestamp of Berry Club history -- `recent` is the latest successful `draw` seen during the snapshot rebuild run - - - -These rendered checkpoints currently resolve to: - -- `launch`: `BDNFpCpLXjBrgjR6z6wCZmB9EWdHnVMdqau3iTWTRE5H` at block `21898354` -- `mid`: `Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ` at block `97601515` -- `recent`: `8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL` at block `194588754` - -## Where to go for signed interactions - -Keep this page read-only. - -If you want the live signed interaction path for `draw` and `buy_tokens`, go here instead: - -- [js.fastnear.com](https://js.fastnear.com/) -- [fastnear/js-monorepo Berry Club example](https://github.com/fastnear/js-monorepo/tree/main/examples/static/berryclub) - -That is the right place for wallet-connected flows. This page is for historical reconstruction. +- RPC: call_function +- Transactions API: Account History +- Transactions API: Transactions by Hash diff --git a/docs/tx/examples.md b/docs/tx/examples.md index 9dc159e..dcfda05 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /tx/examples title: Transactions Examples -description: Plain-language investigations and case studies for following receipts, transactions, NEAR Social writes, promise chains, and NEAR Intents settlements. +description: Plain-language transaction investigations for common developer jobs first, plus a few deeper case studies when you need them. displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -37,7 +37,7 @@ curl -s "$TX_BASE_URL/v0/transactions" \ This is the shortest investigation on the page. Only move to RPC or receipt IDs if this output is not enough. -If you want the longer case-study version of the same surface, jump to [Berry Club](/tx/examples/berry-club) for historical board reconstruction or [OutLayer](/tx/examples/outlayer) for worker and callback tracing. +If you want the longer case-study version of the same surface, jump to the [Berry Club case study](/tx/examples/berry-club) for historical board reconstruction or the [OutLayer case study](/tx/examples/outlayer) for worker and callback tracing. ## Start Here @@ -189,6 +189,140 @@ That last step is optional on purpose. If all you wanted was the transaction sto `POST /v0/transactions` is the cleanest starting point when all you have is a tx hash and need one readable answer. RPC is the follow-up for exact status semantics. `POST /v0/receipt` is the handoff when the next question stops being about the transaction as a whole and starts being about one receipt inside it. +### Which receipt emitted this log or event? + +Use this investigation when the user story is “I have one tx hash and one log fragment, and I need to know exactly which receipt emitted it.” + +This is a different question from “did the callback run?” later on the page. Here the goal is simpler: attribute one observed log line to one exact receipt ID, one method, and one executor. + +
+
+ Strategy +

Fetch the receipt list once, filter by the log fragment, and stop as soon as one receipt owns that log.

+
+
+

01POST /v0/transactions gives the full indexed receipt list for one tx hash, including receipt logs.

+

02jq filters that list down to receipts whose logs contain the fragment you care about.

+

03Once one receipt matches, keep its receipt_id, executor, and method name as the exact answer.

+
+
+ +**Goal** + +- Start from one mainnet tx hash plus one log fragment and identify the exact receipt that emitted that log. + +For this pinned mainnet example, use: + +- transaction hash: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` +- log fragment: `Refund` +- expected matching receipt ID: `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` +- expected executor: `wrap.near` +- expected method: `ft_resolve_transfer` + +This transaction is useful because it has two different logged receipts in the same story: + +- one `Transfer ...` log on the earlier `ft_transfer_call` receipt +- one `Refund ...` log on the later `ft_resolve_transfer` receipt + +```mermaid +flowchart LR + T["One tx hash
2KhhB1uD..."] --> L["Read all receipt logs"] + L --> X["Match fragment:
Refund"] + X --> R["Exact receipt
9sLHQpaG..."] + R --> A["Answer:
wrap.near / ft_resolve_transfer"] +``` + +| Surface | Endpoint | How we use it | Why we use it | +| --- | --- | --- | --- | +| Log attribution | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the tx once and filter its receipts by a log fragment such as `Refund` | Gives the shortest path from one observed log line to the exact receipt that emitted it | +| Optional next pivot | Transactions API [`POST /v0/receipt`](/tx/receipt) | Reuse the matching `receipt_id` only if the receipt itself becomes the next anchor | Keeps the receipt ready for later investigation without making this example longer than needed | + +**What a useful answer should include** + +- which receipt ID emitted the log +- which contract executed that receipt +- which method ran there +- the exact matching log line +- one plain sentence such as “the `Refund` log came from `wrap.near` in the `ft_resolve_transfer` receipt” + +#### Log-attribution shell walkthrough + +Use this when you already have one tx hash and the next question is “which receipt said that?” + +**What you're doing** + +- Fetch the transaction once and keep the receipt list locally. +- Filter the receipts by one log fragment. +- Stop as soon as you have one exact `receipt_id`, one executor, and one method name. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +LOG_FRAGMENT=Refund +``` + +1. Fetch the transaction and keep the receipt list. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/log-attribution-transaction.json >/dev/null +``` + +2. Filter the receipt list down to logs that contain the fragment you care about. + +```bash +jq --arg fragment "$LOG_FRAGMENT" '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id + }, + matching_receipts: [ + .transactions[0].receipts[] + | select(any(.execution_outcome.outcome.logs[]?; contains($fragment))) + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + block_height: .execution_outcome.block_height, + logs: .execution_outcome.outcome.logs + } + ] +}' /tmp/log-attribution-transaction.json + +# What to notice: +# - the `Refund` fragment matches exactly one receipt +# - that receipt is 9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w +# - the receipt executed on wrap.near +# - the method name is ft_resolve_transfer +``` + +3. If you want to see the logged receipts side by side, print only the receipts that logged anything. + +```bash +jq '{ + logged_receipts: [ + .transactions[0].receipts[] + | select((.execution_outcome.outcome.logs | length) > 0) + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + logs: .execution_outcome.outcome.logs + } + ] +}' /tmp/log-attribution-transaction.json +``` + +That final comparison is useful because it proves the log attribution is not guesswork. This transaction has more than one logged receipt, and the `Refund` fragment belongs to one exact later receipt, not to the transaction as a whole. + +**Why this next step?** + +Receipt logs live on receipts, not on some abstract top-level transaction object. `POST /v0/transactions` is enough to attribute one log line to one exact receipt without dropping into a deeper async trace. + ### Turn one ugly receipt ID from logs into a human story Use this investigation when all you have is one ugly `receipt_id` from logs, traces, or an error report, and you want to turn it into a plain-English answer a teammate can understand. @@ -246,19 +380,6 @@ flowchart LR #### Ugly receipt ID to human story shell walkthrough -## Failure and Async - -This is where the page stops being simple lookup and starts teaching NEAR execution semantics: atomic batches, later async failures, and callback order. - -Use this when you already have one raw `receipt_id` from logs and want to turn it into a readable explanation fast. - -**What you're doing** - -- Resolve the receipt first. -- Extract `receipt.transaction_hash` with `jq`. -- Reuse that transaction hash in `POST /v0/transactions`. -- Finish with one human summary you could paste into chat or a ticket. - ```bash TX_BASE_URL=https://tx.main.fastnear.com RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' @@ -332,6 +453,12 @@ That is the core trick: you do not need to explain every receipt field. You need `POST /v0/receipt` tells you what the raw receipt is attached to. `POST /v0/transactions` tells you what the signer was actually trying to do. Once you have those two pieces together, you can usually explain the receipt in one sentence before deciding whether you really need block context, account history, or canonical RPC status. +## Failure and Async + +This is where the page stops being simple lookup and starts teaching NEAR execution semantics: atomic batches, later async failures, and whether a callback ever made it back to the originating contract. + +Use this section when you already know the transaction worked across more than one receipt and the next question is about execution shape rather than simple identity lookup. + ### Prove that one failed action reverted the whole batch Use this investigation when one transaction tried to create and fund a new account, add a key, and then call a method on that same new account. The final action failed because the fresh account had no contract code. The real question is simple: did the earlier actions stick, or did the whole batch revert? @@ -687,829 +814,175 @@ Stop here. As of **April 18, 2026**, `seq-dr.mike.testnet` no longer resolves on When a NEAR app “looked successful” and still broke later, the thing to ask is not just “what was the transaction status?” but “which receipt succeeded, and which later receipt failed?” This example gives you that exact split: indexed receipt timeline for the shape, RPC status for the exact semantics, and no pretend live router-state read after the historical contract disappeared. -### Trace an async promise chain and prove callback order +### Did my callback run at all? + +Use this investigation when one transaction kicked off downstream work on another contract and the real question is not “did the receiver succeed?” but “did the originating contract ever get its callback receipt back?” -Use this investigation when one transaction creates promise work for later, a second transaction resumes it, and the real question is not “did both transactions succeed?” but “did the cross-contract callbacks actually run in the order I intended?” +This is the smallest useful callback proof on the page: + +- start from one tx hash +- identify the downstream receipt on the other contract +- look for the later callback receipt back on the origin contract +- stop once callback existence and callback outcome are proven
Strategy -

Treat the two tx hashes as one async story: prove the work was live, recover the requested order, then compare it with observed downstream state.

+

Use the indexed receipt list first, then drop to RPC only if you need canonical callback semantics.

-

01RPC call_function on the deferred-work view proves the promise work was really live before the resume step.

-

02POST /v0/transactions gives both block anchors and the exact order that the resume transaction requested.

-

03RPC EXPERIMENTAL_tx_status plus the downstream recorder view prove where the callbacks actually ran and in what visible order.

+

01POST /v0/transactions shows the downstream call and the later receipt that returns to the origin contract.

+

02jq narrows that receipt list to one downstream call and one callback receipt.

+

03RPC EXPERIMENTAL_tx_status is optional confirmation when you need the callback receipt's canonical outcome and logs.

**Goal** -- Turn two transaction hashes into one readable proof story: what promise work was created, what order the resume call requested, and what order later showed up in downstream contract state. +- Prove, from one pinned mainnet transaction, that `wrap.near` sent an `ft_transfer_call` to `v2.ref-finance.near`, the receiver ran `ft_on_transfer`, and `wrap.near` later got the `ft_resolve_transfer` callback back. -If your codebase or helper scripts call this a “stage/release” or “yield/resume” flow, that is fine. For docs, the more useful mental model is simpler: +This pinned mainnet callback example was observed on **April 19, 2026**: -- **create promise work**: one transaction sets up deferred async work for later -- **resume promise work**: a later transaction asks the contract to continue that work in a requested order -- **trace the async path**: receipt trees show where the cross-contract callbacks actually ran -- **observe state**: downstream contract state shows what order became visible to users or integrators +- transaction hash: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` +- sender account: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` +- origin contract: `wrap.near` +- downstream receiver: `v2.ref-finance.near` +- top-level method: `ft_transfer_call` +- downstream method: `ft_on_transfer` +- callback method: `ft_resolve_transfer` +- transaction block: `194692298` +- downstream receipt block: `194692300` +- callback receipt block: `194692301` ```mermaid flowchart LR - Y["Tx 1
creates promise work"] --> H["Yielded promises become live
staged_calls_for(...)"] - H --> R["Tx 2
resumes promises in order beta -> alpha -> gamma"] - R --> C["Async cross-contract callbacks"] - C --> B["Recorder state
beta"] - B --> A["Recorder state
alpha"] - A --> G["Recorder state
gamma"] - Y -. "main receipt-tree evidence lives here" .-> D["Original promise DAG"] - R -. "requested order lives here" .-> P["Resume payload"] - G -. "observed order ends here" .-> O["Observed downstream order"] + T["One mainnet tx
ft_transfer_call on wrap.near"] --> D["Downstream receipt
v2.ref-finance.near.ft_on_transfer"] + D --> F["Receiver failed
E51: contract paused"] + F --> C["Callback receipt back on wrap.near
ft_resolve_transfer"] + C --> R["Refund log on wrap.near"] ``` -That distinction matters because a successful resume transaction still does not prove the observed order by itself. You also need evidence that the promised work was really live before resume, and evidence that downstream state changed in the same order the resume call requested. - -For NEAR engineers, the important mental model is: the resume transaction tells you the **requested order**, but the original promise transaction usually remains the primary forensic anchor because the resumed callbacks still live on that original async receipt tree. Downstream contract state is what lets you compare requested order with observed order. - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Promise-chain trace capture | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Query the original promise transaction hash and the later resume transaction hash with `wait_until: "FINAL"`, usually hot RPC first and archival RPC on `UNKNOWN_TRANSACTION` | The receipt DAG is the primary proof surface for callback order and tells you which receipts belong to which async transaction tree | -| Promise-readiness check | RPC [`query(call_function)`](/rpc/contract/call-function) | Poll the contract view that exposes deferred promise work, such as `staged_calls_for({ caller_id })`, with `finality: "final"` until the yielded promises appear | Confirms the promise work was really live before the resume transaction tried to continue it | -| Requested-order anchor | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch both transaction hashes to recover `block_height`, `block_hash`, `receiver_id`, indexed execution status, and the resume payload | Gives each transaction a durable block anchor and preserves the exact order the resume step requested | -| Downstream state snapshots | RPC [`query(call_function)`](/rpc/contract/call-function) | Read the downstream recorder state before resume, then poll it after resume until the expected entries appear | Proves actual callback order in contract state, not just metadata in the receipt tree | -| Receipt pivot | Transactions API [`POST /v0/receipt`](/tx/receipt) | Use any interesting yielded or downstream receipt ID to reconnect it to the originating transaction | Lets you move quickly from one receipt in the DAG back to the broader transaction story | -| Per-block reconstruction | Transactions API [`POST /v0/block`](/tx/block) | Fetch the included block and the cascade blocks with receipts enabled | Reconstructs the block-by-block execution timeline once you know which blocks matter | -| Account activity context | Transactions API [`POST /v0/account`](/tx/account) | Fetch function-call history for the contracts that participated in the cascade over the same window | Gives humans a simpler account-history view to compare against the trace | -| Block-pinned state replay | RPC [`query(call_function)`](/rpc/contract/call-function) | Re-run the recorder view with `block_id` pinned to the interesting heights | Turns final state into a time series so you can say when state changed, not just what it became | - -**What a useful answer should include** - -- a one-sentence conclusion in plain language, such as “the first transaction created three deferred promises, the second transaction resumed them in order `beta -> alpha -> gamma`, and the recorder state later confirmed that same callback order” -- why the original promise transaction, not only the resume transaction, is usually the primary forensic anchor -- the requested callback order and the observed downstream effect order -- the blocks where the observable state changed -- any receipt or account pivots the next investigator should keep - -## SocialDB Proofs - -These examples start from readable NEAR Social state and walk back to the exact write that made it true. - -### Prove that `mike.near` set `profile.name` to `Mike Purvis`, then recover the SocialDB profile write transaction - -Use this investigation when the user story is “I can see `Mike Purvis` on `mike.near`'s NEAR Social profile, but I want to prove exactly when that field was written and which transaction wrote it.” - -
-
- Strategy -

Start from the readable field value, then turn its field-level block into one receipt and one write transaction.

-
-
-

01NEAR Social POST /get gives both the current profile.name value and the field-level :block.

-

02POST /v0/block turns that block into the concrete mike.near -> social.near receipt and transaction hash.

-

03POST /v0/transactions proves the write payload, and RPC call_function get confirms the field still resolves that way now.

-
-
- -**Goal** - -- Start from one readable SocialDB profile field, then recover the exact receipt and originating transaction that wrote it. - -**Official references** - -- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) -- [NEAR Social live read surface](https://api.near.social) - -This follows the same proof recipe as the follow-edge investigation, but it teaches one extra SocialDB nuance: for historical proof, the field-level `:block` is usually more precise than the parent object's `:block`. In this live case, `mike.near/profile/name` was written at block `78675795`, while the broader `mike.near/profile` object later advanced to a different block because unrelated sibling fields changed. FastNear's role is to turn that field-level block into a receipt, then a transaction, and then a readable write payload. - -For this live example, the current `profile.name` value is `Mike Purvis`, the field-level SocialDB write block is `78675795`, the receipt ID is `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN`, the originating transaction hash is `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ`, and the outer transaction block is `78675794`. +One useful NEAR detail shows up here: a downstream failure does not mean the callback vanished. In this case, `v2.ref-finance.near` failed its `ft_on_transfer` receipt, but `wrap.near` still later received `ft_resolve_transfer` and logged the refund. | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | -| Semantic field lookup | NEAR Social `POST /get` | Read `mike.near/profile/name` with block metadata enabled | Gives the human-readable field value and the field-level SocialDB `:block` anchor where that value was written | -| Receipt bridge | Transactions API [`POST /v0/block`](/tx/block) | Use the SocialDB field block with `with_receipts: true`, then filter the block receipts back down to `mike.near -> social.near` | Turns the field-level write block into a concrete receipt and originating transaction hash | -| Transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the originating transaction by hash and decode the first `FunctionCall.args` payload | Proves that the underlying write was a `social.near set` call that carried `profile.name` and the surrounding profile fields in the same payload | -| Canonical current-state confirmation | RPC [`query(call_function)`](/rpc/contract/call-function) | Call `social.near get` directly at `final` | Confirms the field still has that value now, even though the earlier steps already proved the specific historical write | +| Indexed receipt chain | Transactions API [`POST /v0/transactions`](/tx/transactions) | Start from the tx hash and print only the downstream receiver receipt plus the later callback receipt on the origin contract | Gives the fastest readable answer to “did the callback come back?” | +| Canonical receipt confirmation | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Reuse the same tx hash and sender only if you need the callback receipt's canonical status and logs | Useful when the indexed answer is enough for shape but you still want protocol-native proof | **What a useful answer should include** -- whether `mike.near/profile/name` still resolves to `Mike Purvis` -- the field-level SocialDB write block height (`78675795`) and why that anchor is better than the parent profile block for this question -- the specific receipt ID and originating transaction hash behind that write -- proof that the write was a `set` call carrying `profile.name` and other profile fields in the same payload -- the distinction between the receipt execution block (`78675795`) and the outer transaction inclusion block (`78675794`) +- which contract received the downstream call +- which method ran on that downstream contract +- whether a later receipt returned to the originating contract +- which callback method ran there and in which block +- one plain sentence such as “the receiver failed, but the origin contract still got its callback and settled the transfer” -#### NEAR Social profile-proof shell walkthrough +#### Callback-existence shell walkthrough -Use this when you want a concrete, repeatable proof chain from one readable NEAR Social profile field to the exact SocialDB write transaction behind it. +Use this when you want one concrete callback proof without turning the page into a full promise-theory exercise. **What you're doing** -- Read the current `profile.name` field from NEAR Social and capture its field-level SocialDB write block. -- Reuse that block height in FastNear block receipts to recover the receipt ID and transaction hash. -- Reuse the transaction hash in `POST /v0/transactions` to prove the payload was a `social.near set` write carrying `profile.name`. -- Finish with canonical RPC confirmation that the field still resolves to the same value at `final`. +- Fetch the transaction once and narrow the receipt list down to the downstream call plus the callback receipt. +- Reuse the callback receipt ID only if you still need canonical RPC confirmation. +- Stop once you can say whether the callback came back and what it did. ```bash -SOCIAL_API_BASE_URL=https://api.near.social TX_BASE_URL=https://tx.main.fastnear.com RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near -PROFILE_FIELD=profile/name +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +SENDER_ACCOUNT_ID=7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72 +ORIGIN_CONTRACT_ID=wrap.near +DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near ``` -1. Read the profile field from NEAR Social and capture the field-level SocialDB write block. +1. Fetch the transaction and print the downstream receipt plus the callback receipt. ```bash -PROFILE_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-profile-name.json \ - | jq -r --arg account_id "$ACCOUNT_ID" \ - '.[ $account_id ].profile.name[":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - current_name: .[$account_id].profile.name[""], - field_block_height: .[$account_id].profile.name[":block"], - parent_profile_block_height: .[$account_id].profile[":block"] -}' /tmp/mike-profile-name.json - -# Expected current_name: "Mike Purvis" -# Expected field block height: 78675795 -``` - -2. Reuse that block height in FastNear block receipts and recover the receipt and transaction bridge. - -```bash -PROFILE_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-profile-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/callback-check-transaction.json >/dev/null -jq --arg account_id "$ACCOUNT_ID" '{ - profile_receipt: ( +CALLBACK_RECEIPT_ID="$( + jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | { - receipt_id, - transaction_hash, - block_height, - tx_block_height - } + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + | .receipt.receipt_id ) - ) -}' /tmp/mike-profile-block.json - -# Expected receipt ID: 2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN -# Expected transaction hash: 6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ -``` - -3. Reuse the derived transaction hash in `POST /v0/transactions` and decode the SocialDB write payload. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-profile-transaction.json >/dev/null + ' /tmp/callback-check-transaction.json +)" -jq '{ +jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" '{ transaction: { hash: .transactions[0].transaction.hash, signer_id: .transactions[0].transaction.signer_id, receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + tx_block_height: .transactions[0].execution_outcome.block_height }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), - description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), - tags: ( - .args - | @base64d - | fromjson - | .data["mike.near"].profile.tags - | keys - ) - } - ) -}' /tmp/mike-profile-transaction.json -``` - -4. Finish with canonical current-state confirmation via raw RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-profile-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - finality: "final", - current_name: ( - .result.result - | implode - | fromjson - | .[$account_id].profile.name - ) -}' /tmp/mike-profile-rpc.json -``` - -That last step confirms the field still resolves to `Mike Purvis` now. The earlier NEAR Social and FastNear steps are what proved which historical write set that field and which transaction carried the write. - -**Why this next step?** - -NEAR Social gives you the semantic field value. FastNear block receipts give you the bridge to a specific write. FastNear transaction lookup turns that write into a readable profile payload. RPC gives you canonical current-state confirmation. - -### Prove that `mike.near` followed `mob.near`, then recover the SocialDB write transaction - -Use this investigation when the user story is “I can see that `mike.near` follows `mob.near`, but I want to prove exactly when that follow edge was written and which transaction wrote it.” - -
-
- Strategy -

Start from the semantic follow edge, then use its write block as the bridge back to one receipt and one transaction.

-
-
-

01NEAR Social POST /get gives the readable follow edge and the SocialDB :block where it was written.

-

02POST /v0/block turns that write block into the specific receipt and transaction hash behind the edge.

-

03POST /v0/transactions proves the graph.follow plus index.graph payload, and RPC call_function get confirms the edge still exists now.

-
-
- -**Goal** - -- Start from the readable NEAR Social follow edge, then recover the exact receipt and originating transaction that wrote it into SocialDB. - -**Official references** - -- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) -- [NEAR Social live read surface](https://api.near.social) - -The readable follow edge comes from NEAR Social data, not from FastNear. The important bridge is the SocialDB `:block` metadata: it tells you the receipt execution block where that value was written. That block is not the same thing as the original outer transaction inclusion block. FastNear's job in this workflow is to turn that block height into a receipt, then into a transaction, and finally into a readable execution story. - -For this live example, the current edge is `mike.near -> mob.near`, the SocialDB write block is `79574924`, the receipt ID is `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th`, the originating transaction hash is `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb`, and the outer transaction block is `79574923`. - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Semantic edge lookup | NEAR Social `POST /get` | Read `mike.near/graph/follow/mob.near` with block metadata enabled | Gives the human-readable follow edge and the SocialDB `:block` anchor where that value was written | -| Receipt bridge | Transactions API [`POST /v0/block`](/tx/block) | Use the SocialDB block height with `with_receipts: true`, then filter the block receipts back down to `mike.near -> social.near` | Turns the SocialDB write block into a concrete receipt and originating transaction hash | -| Transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the originating transaction by hash and decode the first `FunctionCall.args` payload | Proves that the underlying write was a `social.near set` call that wrote both `graph.follow` and `index.graph` entries | -| Canonical current-state confirmation | RPC [`query(call_function)`](/rpc/contract/call-function) | Call `social.near get` directly at `final` | Confirms the follow edge still exists now, even though the earlier steps already proved the specific historical write | - -**What a useful answer should include** - -- whether the `mike.near -> mob.near` follow edge exists now -- the SocialDB write block height (`79574924`) and why it is a receipt execution block -- the specific receipt ID and originating transaction hash behind that write -- proof that the write was a `set` call carrying both `graph.follow.mob.near` and the matching `index.graph` entry -- the distinction between the receipt execution block (`79574924`) and the outer transaction inclusion block (`79574923`) - -#### NEAR Social follow-proof shell walkthrough - -Use this when you want a concrete, repeatable proof chain from one readable NEAR Social follow edge to the exact SocialDB write transaction behind it. - -**What you're doing** - -- Read the current follow edge from NEAR Social and capture the SocialDB write block. -- Reuse that block height in FastNear block receipts to recover the receipt ID and transaction hash. -- Reuse the transaction hash in `POST /v0/transactions` to prove the payload was a `social.near set` write. -- Finish with canonical RPC confirmation that the edge still exists at `final`. - -```bash -SOCIAL_API_BASE_URL=https://api.near.social -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near -TARGET_ACCOUNT_ID=mob.near -``` - -1. Read the follow edge from NEAR Social and capture the SocialDB write block. - -```bash -FOLLOW_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - keys: [($account_id + "/graph/follow/" + $target_account_id)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-follow-edge.json \ - | jq -r --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" \ - '.[ $account_id ].graph.follow[ $target_account_id ][":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - follow_edge: .[$account_id].graph.follow[$target_account_id][""], - follow_block_height: .[$account_id].graph.follow[$target_account_id][":block"] -}' /tmp/mike-follow-edge.json - -# Expected follow block height: 79574924 -``` - -2. Reuse that block height in FastNear block receipts and recover the receipt and transaction bridge. - -```bash -FOLLOW_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$FOLLOW_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-follow-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - follow_receipt: ( + downstream_receipt: ( first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") + .transactions[0].receipts[] + | select(.receipt.receiver_id == $downstream) | { - receipt_id, - transaction_hash, - block_height, - tx_block_height + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + status: .execution_outcome.outcome.status, + block_height: .execution_outcome.block_height } ) - ) -}' /tmp/mike-follow-block.json - -# Expected receipt ID: UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th -# Expected transaction hash: FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -``` - -3. Reuse the derived transaction hash in `POST /v0/transactions` and decode the SocialDB write payload. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$FOLLOW_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-follow-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - follow_edge: (.args | @base64d | fromjson | .data["mike.near"].graph.follow["mob.near"]), - index_graph: ( - .args - | @base64d - | fromjson - | .data["mike.near"].index.graph - | fromjson - | map(select(.value.accountId == "mob.near")) - ) - } - ) -}' /tmp/mike-follow-transaction.json -``` - -4. Finish with canonical current-state confirmation via raw RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - keys: [($account_id + "/graph/follow/" + $target_account_id)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-follow-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - finality: "final", - current_follow_edge: ( - .result.result - | implode - | fromjson - | .[$account_id].graph.follow[$target_account_id] - ) -}' /tmp/mike-follow-rpc.json -``` - -That last step confirms the follow edge still exists now. The earlier NEAR Social and FastNear steps are what proved which historical write created the edge and which transaction carried that write. - -**Why this next step?** - -NEAR Social gives you the semantic edge. FastNear block receipts give you the bridge to a specific write. FastNear transaction lookup turns that write into a readable story. RPC gives you canonical current-state confirmation. - -### Which transaction wrote `mob.near/widget/Profile`? - -Use this investigation when the question is “I already know `mob.near/widget/Profile` exists right now. Which transaction wrote the widget version I am looking at?” - -This is the natural tx-side companion to the lighter RPC widget inspection and the provenance-NFT workflow. The job is straightforward: - -- start from the widget's own SocialDB block -- turn that block into one `mob.near -> social.near` receipt -- recover the originating transaction -- decode the `set` payload and prove it really carried the widget source - -
-
- Strategy -

Treat the widget’s write block as the whole bridge: block to receipt, receipt to transaction, transaction to source code.

-
-
-

01POST /v0/block starts from the widget block and narrows it to one mob.near -> social.near receipt.

-

02POST /v0/transactions turns that receipt into one readable set payload carrying the widget source.

-

03RPC call_function get is the final current-state confirmation that the widget still exists now.

-
-
- -**Goal** - -- Turn one widget-level SocialDB block into one readable answer: which transaction wrote `mob.near/widget/Profile`, which receipt executed the write, and what exact widget source appeared in that payload. - -**Official references** - -- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) - -For this live anchor: - -- account: `mob.near` -- widget: `Profile` -- SocialDB write block: `86494825` -- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` -- originating transaction hash: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` -- outer transaction block: `86494824` - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Block-to-receipt bridge | Transactions API [`POST /v0/block`](/tx/block) | Start from block `86494825` with `with_receipts: true`, then filter back down to `mob.near -> social.near` | Turns the widget's write block into one concrete receipt and one concrete transaction hash | -| Transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the originating transaction and decode the `FunctionCall.args` payload | Proves the write was a `social.near set` call carrying the `mob.near/widget/Profile` source code | -| Canonical current-state confirmation | RPC [`query(call_function)`](/rpc/contract/call-function) | Call `social.near get` directly at `final` for the same widget path | Confirms that the widget still exists now, even though the earlier steps already proved which historical transaction wrote it | - -**What a useful answer should include** - -- the write block height and why it is the receipt execution block, not the outer transaction block -- the specific receipt ID and originating transaction hash behind the widget write -- proof that the write payload was a `set` call carrying `mob.near/widget/Profile` -- one plain-English sentence like “`mob.near` wrote `widget/Profile` in tx `9QDup...`, and the payload really did store the current profile widget source” - -#### NEAR Social widget write-proof shell walkthrough - -Use this when you want to turn one widget block anchor into the exact transaction that wrote it. - -**What you're doing** - -- Start from the widget's last-write block. -- Reuse that block height in FastNear block receipts to recover the receipt and transaction bridge. -- Reuse the transaction hash in `POST /v0/transactions` to decode the stored widget source. -- Finish with raw RPC confirmation that the widget still exists now. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -WIDGET_BLOCK_HEIGHT=86494825 -``` - -1. Start from the widget's last-write block and recover the SocialDB receipt plus transaction hash. - -```bash -WIDGET_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$WIDGET_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mob-widget-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - widget_write_receipt: ( + ), + callback_receipt: ( first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) | { - receipt_id, - transaction_hash, - block_height, - tx_block_height + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: .receipt.receipt.Action.actions[0].FunctionCall.method_name, + logs: .execution_outcome.outcome.logs, + status: .execution_outcome.outcome.status, + block_height: .execution_outcome.block_height } ) - ) -}' /tmp/mob-widget-block.json - -# Expected receipt ID: CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6 -# Expected transaction hash: 9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia -``` - -2. Reuse the transaction hash and decode the SocialDB `set` payload. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mob-widget-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].transaction.actions[0].FunctionCall - | { - method_name, - widget_source_head: ( - .args - | @base64d - | fromjson - | .data["mob.near"].widget.Profile[""] - | split("\n")[0:12] + ), + callback_ran: ( + first( + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" ) - } - ) -}' /tmp/mob-widget-transaction.json -``` - -That second step is the payoff. You are no longer just saying “something in that block updated SocialDB.” You are proving that tx `9QDup...` called `social.near set` and carried the actual `mob.near/widget/Profile` widget body in its args. - -3. Finish with canonical current-state confirmation via raw RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mob-widget-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - finality: "final", - current_widget_head: ( - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:5] + | true + ) // false ) -}' /tmp/mob-widget-rpc.json -``` - -That last step confirms the widget still exists now. The earlier block and transaction steps are what proved which historical write created it. - -**Why this next step?** - -The widget's write block gives you the bridge. FastNear block receipts turn that bridge into one receipt and one transaction hash. FastNear transaction lookup turns the hash into readable write proof. RPC then confirms that the widget still exists now. - -### Trace one NEAR Intents settlement and show what actually happened - -Use this investigation when the user story is “I have one `intents.near` transaction. Show me what actually happened on-chain, which contracts participated, and which events prove it.” - -
-
- Strategy -

Treat one settlement like a readable trace before you treat it like protocol theory.

-
-
-

01POST /v0/transactions gives the settlement skeleton: entrypoint, first downstream contracts, and early logs.

-

02POST /v0/block reuses the same anchor when you want the block-level context around that settlement.

-

03RPC EXPERIMENTAL_tx_status is where you go for the canonical receipt DAG and the event names that prove what actually moved.

-
-
- -**Goal** - -- Start from one fixed `intents.near` transaction and turn it into a readable settlement story: which method kicked things off, which downstream contracts appeared, and which event families tell you what moved. - -**Official references** +}' /tmp/callback-check-transaction.json -- [NEAR Intents overview](https://docs.near.org/chain-abstraction/intents/overview) -- [Intent types and execution](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) -- [Account abstraction](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) - -For the live trace below, use this fixed mainnet settlement anchor captured on **April 18, 2026**: - -- transaction hash: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` -- signer and receiver: `intents.near` -- included block height: `194573310` - -The fast mental model is simple: - -- `intents.near` runs the settlement entrypoint -- downstream receipts fan out to the contracts that actually move or withdraw assets -- event logs tell you which settlement actions happened, with names like `token_diff`, `intents_executed`, `mt_transfer`, and `mt_withdraw` - -For this specific settlement, the short human answer is already pretty good: - -- `intents.near` called `execute_intents` -- downstream receipts hit `v2_1.omni.hot.tg` and `bridge-refuel.hot.tg` -- the trace emitted `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw`, and `mt_burn` - -If you want the protocol background, the core matching shape is the two-party `token_diff` intent: one side signs the asset diff it wants, the other side signs the opposite diff, and the matched pair gets submitted for settlement. For operational tracing, though, it is usually clearer to start from one real settlement and read the chain evidence directly. - -```mermaid -flowchart LR - T["One mainnet transaction
4cfei8p4..."] --> I["intents.near
execute_intents"] - I --> R["Downstream receipts"] - R --> C["Other contracts participate"] - R --> E["Event logs appear"] - E --> TD["token_diff"] - E --> IE["intents_executed"] - E --> MT["mt_transfer / mt_withdraw"] -``` - -The public FastNear surfaces are enough to answer the practical question: - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Settlement skeleton | Transactions API [`POST /v0/transactions`](/tx/transactions) | Start from the fixed transaction hash and print the main transaction plus the first downstream receipts | Gives you the fastest readable answer to “what did this settlement call into next?” | -| Block context | Transactions API [`POST /v0/block`](/tx/block) | Fetch the included block with receipts enabled and filter it back down to the same transaction hash | Shows where the settlement landed and which receipts from that transaction appeared in the block | -| Canonical receipt and event proof | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Ask for the same transaction with `wait_until: "FINAL"` and inspect `receipts_outcome` plus `EVENT_JSON` logs | Gives you the protocol-native DAG, executor IDs, and event names that explain what the settlement actually did | - -**What a useful answer should include** - -- the settlement entrypoint you observed on `intents.near` -- which downstream contracts and methods appeared right after it -- which event families the trace emitted -- a one-sentence summary of what happened, in plain language - -This example intentionally stays on public FastNear surfaces. NEAR Intents Explorer and the 1Click Explorer are useful too, but their Explorer API is JWT-gated and not the right default for a public docs walkthrough. - -#### NEAR Intents settlement shell walkthrough - -Use this when you want one concrete `intents.near` settlement that you can inspect immediately with public FastNear endpoints. - -**What you're doing** - -- Pull the readable settlement story from Transactions API. -- Reuse the included block hash in `POST /v0/block` to inspect the containing block. -- Confirm the canonical receipt DAG and event families with `EXPERIMENTAL_tx_status`. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 -INTENTS_SIGNER_ID=intents.near -``` - -1. Start with the settlement transaction itself and recover the first readable receipt flow. - -```bash -INTENTS_BLOCK_HASH="$( - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$INTENTS_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/intents-transaction.json \ - | jq -r '.transactions[0].execution_outcome.block_hash' -)" - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - receipt_flow: [ - .transactions[0].receipts[:6][] - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - block_height: .execution_outcome.block_height, - methods: ( - [.receipt.receipt.Action.actions[]?.FunctionCall.method_name] - | map(select(. != null)) - ), - first_log: (.execution_outcome.outcome.logs[0] // null) - } - ] -}' /tmp/intents-transaction.json -``` - -That first step already gives you a strong operator answer: `intents.near` ran the settlement transaction, and the early downstream receipts show which contracts joined the cascade. - -2. Reuse the block hash to inspect the containing block with receipts enabled. - -```bash -curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_id "$INTENTS_BLOCK_HASH" '{ - block_id: $block_id, - with_receipts: true, - with_transactions: false - }')" \ - | tee /tmp/intents-block.json >/dev/null - -jq --arg tx_hash "$INTENTS_TX_HASH" '{ - block_height: .block.block_height, - block_hash: .block.block_hash, - tx_receipts: [ - .block_receipts[] - | select(.transaction_hash == $tx_hash) - | { - receipt_id, - predecessor_id, - receiver_id, - block_height - } - ] -}' /tmp/intents-block.json +# What to notice: +# - the downstream receipt ran ft_on_transfer on v2.ref-finance.near +# - the later callback receipt ran ft_resolve_transfer on wrap.near +# - callback_ran is true even though the downstream receipt failed ``` -3. Confirm the canonical receipt DAG and extract the event families from RPC. +2. If you want the canonical callback outcome and refund log, confirm the same receipt in RPC. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ - --arg tx_hash "$INTENTS_TX_HASH" \ - --arg sender_account_id "$INTENTS_SIGNER_ID" '{ + --arg tx_hash "$TX_HASH" \ + --arg sender_account_id "$SENDER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "EXPERIMENTAL_tx_status", @@ -1519,115 +992,87 @@ curl -s "$RPC_URL" \ wait_until: "FINAL" } }')" \ - | tee /tmp/intents-rpc.json >/dev/null + | tee /tmp/callback-check-rpc.json >/dev/null -jq '{ - final_execution_status: .result.final_execution_status, - receipts_outcome: [ - .result.receipts_outcome[:6][] - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - first_log: (.outcome.logs[0] // null) - } - ] -}' /tmp/intents-rpc.json +jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ + top_level_status: .result.status, + callback_receipt: ( + first( + .result.receipts_outcome[] + | select(.id == $callback_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + logs: .outcome.logs, + status: .outcome.status + } + ) + ) +}' /tmp/callback-check-rpc.json -jq -r ' - .result.receipts_outcome[] - | .outcome.logs[] - | select(startswith("EVENT_JSON:")) - | capture("event\":\"(?[^\"]+)\"").event -' /tmp/intents-rpc.json | sort -u +# What to notice: +# - the downstream ft_on_transfer receipt failed on v2.ref-finance.near +# - wrap.near still received ft_resolve_transfer afterward +# - the callback log shows the refund back to the sender ``` **Why this next step?** -`POST /v0/transactions` tells you what the settlement called into. `POST /v0/block` shows where that settlement landed in block context. `EXPERIMENTAL_tx_status` is the canonical follow-up when you need executor IDs, receipt-DAG structure, and raw event names to explain what actually happened. - -## Common jobs - -### Look up one transaction - -**Start here** - -- [Transactions by Hash](/tx/transactions) when you already know the transaction ID. - -**Next page if needed** - -- [Receipt Lookup](/tx/receipt) if the interesting part is now a downstream receipt. -- [Block](/tx/block) if the block context matters. -- [Transaction Status](/rpc/transaction/tx-status) if you need canonical RPC confirmation. - -**Stop when** - -- You can explain the outcome, affected accounts, and the main execution takeaway. - -**Switch when** +For callback questions, the important proof is not “did every receipt succeed?” but “did the origin contract get its callback receipt back, and what happened there?” `POST /v0/transactions` gives the fastest readable answer. RPC is only the optional confirmation layer when you need the callback receipt's canonical outcome and logs. -- The user asks for exact RPC-level status semantics or submission behavior. -- The transaction lookup alone is not enough to explain downstream execution. +## Advanced and Case Studies -### Investigate a receipt +The examples below are still useful, but they are longer or more specialized than the default start-here flows above. `Berry Club` and `OutLayer` live as separate case-study pages, the SocialDB provenance pattern now lives on its own advanced page, and the last example here keeps only a slim multi-contract follow-up pattern. -**Start here** +### Advanced SocialDB provenance pattern -- [Receipt Lookup](/tx/receipt) when the receipt ID is your best anchor. +If the readable fact already comes from `api.near.social`, keep the follow-up small: semantic value first, `:block` second, then FastNear block and transaction lookup. Use the [dedicated SocialDB provenance pattern page](/tx/socialdb-proofs) for one canonical example of that flow. -**Next page if needed** +### Advanced: which downstream contracts did this transaction touch? -- [Transactions by Hash](/tx/transactions) to reconnect the receipt to the originating transaction story. -- [Account History](/tx/account) if you need to see the surrounding activity for one of the touched accounts. +Use this when you already have one multi-contract tx hash and the next question is simply “which contracts did this call into after the top-level action?” -**Stop when** +This pinned mainnet anchor still makes a good example, even though it happens to be an `intents.near` settlement: -- You can say where the receipt fits in the execution flow and why it matters. - -**Switch when** - -- The user needs exact canonical confirmation beyond the indexed receipt view. Move to [RPC Reference](/rpc). -- The question shifts from one receipt to a broader history investigation. - -### Review recent account activity - -**Start here** - -- [Account History](/tx/account) for an account-centric activity feed. - -**Next page if needed** - -- [Transactions by Hash](/tx/transactions) for a specific transaction from the feed. -- [Receipt Lookup](/tx/receipt) if one receipt becomes the real focus. - -**Stop when** - -- The account history already answers what the account has been doing. - -**Switch when** - -- The user actually wants transfer-only movement instead of broader execution context. Move to [Transfers API](/transfers). -- The user actually wants exact current state or holdings, not history. Move to [RPC Reference](/rpc) or [FastNear API](/api). - -### Reconstruct a bounded block window - -**Start here** +- transaction hash: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` +- signer and receiver: `intents.near` +- included block height: `194573310` -- [Blocks](/tx/blocks) for a bounded block-range scan. -- [Block](/tx/block) when you already know the exact block you want. +The short answer for this tx is already useful: -**Next page if needed** +- the top-level method was `execute_intents` +- early downstream receipts touched `v2_1.omni.hot.tg` and `bridge-refuel.hot.tg` +- later logs included event families like `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw`, and `mt_burn` -- [Transactions by Hash](/tx/transactions) to inspect a specific item from the block window. -- [Receipt Lookup](/tx/receipt) when one receipt becomes the important follow-up. +For most questions, Transactions API is enough: -**Stop when** +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 -- The bounded history window answers the question without dropping into lower-level protocol details. +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name + }, + downstream_receivers: ( + [.transactions[0].receipts[] | .receipt.receiver_id] + | unique + ), + first_logs: ( + [.transactions[0].receipts[] | .execution_outcome.outcome.logs[]?] + | .[:5] + ) + }' +``` -**Switch when** +If you need the containing block, widen once to Transactions API [`POST /v0/block`](/tx/block). If you need the canonical receipt DAG or raw `EVENT_JSON` logs, widen once more to RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status). The teaching point here is generic: start with one tx hash, list the downstream receivers, and stop unless the trace really needs more. -- The user needs exact canonical block fields or transaction finality. Move to [RPC Reference](/rpc). -- The user really wants freshest polling-oriented block reads rather than indexed history. Move to [NEAR Data API](/neardata). ## Common mistakes diff --git a/docs/tx/outlayer.mdx b/docs/tx/outlayer.mdx index 5d38070..760fb31 100644 --- a/docs/tx/outlayer.mdx +++ b/docs/tx/outlayer.mdx @@ -1,8 +1,8 @@ --- -sidebar_label: OutLayer +sidebar_label: OutLayer Case Study slug: /tx/examples/outlayer -title: "OutLayer: Trace request and worker resolution" -description: "Use Transactions API to pair one caller-side OutLayer request with one later worker-side resolution and inspect the finish receipts." +title: "OutLayer Case Study: Trace request and worker resolution" +description: "A case study that uses Transactions API to pair one caller-side OutLayer request with one later worker-side resolution and inspect the finish receipts." displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -17,11 +17,13 @@ keywords: import Link from '@site/src/components/LocalizedLink'; -{/* FASTNEAR_AI_DISCOVERY: This example stays on observable transaction and receipt evidence. It shows how to pair one caller-side OutLayer request with one later worker-side resolution and inspect the finish receipts. It does not try to prove OutLayer's internal TEE, yield/resume, or CKD/MPC architecture from public chain data alone. */} +{/* FASTNEAR_AI_DISCOVERY: This case study stays on observable transaction and receipt evidence. It shows how to pair one caller-side OutLayer request with one later worker-side resolution and inspect the finish receipts. It does not try to prove OutLayer's internal TEE, yield/resume, or CKD/MPC architecture from public chain data alone. */} -# OutLayer: Trace request and worker resolution +# OutLayer Case Study: Trace request and worker resolution -Use this when the question is: “Which transaction opened the OutLayer request, which later transaction came from the worker, and where did the finish receipts show callback, charging, or refund behavior?” +Use this case study when the question is: “Which transaction opened the OutLayer request, which later transaction came from the worker, and where did the finish receipts show callback, charging, or refund behavior?” + +This page intentionally stays on public chain evidence only. It shows the caller transaction, the later worker transaction, and the finish receipts. It does not try to prove OutLayer’s internal TEE, `yield/resume`, or CKD/MPC architecture from chain traces alone. Treat this as a transaction-history problem first: @@ -29,29 +31,6 @@ Treat this as a transaction-history problem first: - one later worker-side `submit_execution_output_and_resolve` or `resolve_execution` - receipt-level follow-up only when the finish path matters -
-
- Strategy -

Find the two hashes first, hydrate them second, and inspect worker receipts only when you need the finish path.

-
-
-

01POST /v0/account on outlayer.* is the quickest hash-discovery surface.

-

02POST /v0/transactions turns the caller hash and the worker hash into readable signer, method, and log evidence.

-

03Inspect the worker transaction’s receipts only when the real question is about callback, charging, or refund behavior.

-
-
- -**Goal** - -- Recover one caller transaction, one worker transaction, and the finish receipts that belong to the same OutLayer request. - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Hash discovery | Transactions API [`POST /v0/account`](/tx/account) | Pull recent transaction hashes for `outlayer.near` | Gives the fastest contract-local search surface when you do not already know the pair of hashes | -| Transaction hydration | Transactions API [`POST /v0/transactions`](/tx/transactions) | Hydrate the caller-side and worker-side hashes together | Turns the raw hashes into signer, receiver, action, and log evidence | -| Finish-path inspection | Transactions API [`POST /v0/transactions`](/tx/transactions) | Reuse the worker hash and inspect its receipt list | Shows where callback, charging, and refund behavior materialized | -| Optional identity check | RPC [`view_account`](/rpc/account/view-account) | Use RPC only if the next question is about contract identity rather than transaction history | Keeps current-state identity checks separate from history tracing | - ## Verified shell walkthrough This pair worked on April 18, 2026: @@ -87,14 +66,7 @@ jq '{ }' /tmp/outlayer-pair.json ``` -What this establishes: - -- the request transaction came from `solarflux.near` to `outlayer.near` -- the request logs included the resolved project `zavodil.near/near-email` -- the worker transaction came later from `worker.outlayer.near` to `outlayer.near` -- the worker logs included `Stored pending output` and `Resolving execution ...` - -That is the core observable loop in NEAR terms: caller-side request first, worker-side resolution later. +This gives you the core observable loop: caller-side request first, worker-side resolution later, with readable signer, method, and log evidence for both. ### 2. Inspect the worker receipts only when the finish path matters @@ -140,21 +112,10 @@ curl -sS "$TX_BASE_URL/v0/account" \ Use this as a discovery surface only. For this example, `/v0/account` gives you candidate hashes, and `/v0/transactions` is the surface that turns those hashes into readable evidence. -## Scope boundary - -This page intentionally stays on public chain evidence that FastNear and RPC can show directly: - -- caller-side request transaction -- later worker-side resolution transaction -- finish-path receipts and logs - -It does **not** try to prove OutLayer’s internal TEE execution model, same-account `yield/resume` usage, or CKD/MPC trust path from public transaction traces alone. Treat those as separate architecture questions and read them in the OutLayer docs, not as claims established by this trace. - ## Related pages - Transactions API: Account History - Transactions API: Transactions by Hash - Transactions API: Receipt by ID -- RPC: View Account - [OutLayer NEAR Integration](https://outlayer.fastnear.com/docs/near-integration) - [OutLayer Secrets / CKD](https://outlayer.fastnear.com/docs/secrets) diff --git a/docs/tx/socialdb-proofs.mdx b/docs/tx/socialdb-proofs.mdx new file mode 100644 index 0000000..f887310 --- /dev/null +++ b/docs/tx/socialdb-proofs.mdx @@ -0,0 +1,198 @@ +--- +slug: /tx/socialdb-proofs +title: Advanced SocialDB Provenance Pattern +description: One advanced pattern for starting from a readable SocialDB value and recovering the write transaction behind it. +displayed_sidebar: transactionsApiSidebar +page_actions: + - markdown +--- + +# Advanced SocialDB Provenance Pattern + +Use this page only when the starting point is already a readable SocialDB value from `api.near.social` and the next question is historical provenance. + +For FastNear-first jobs, start with [Transactions Examples](/tx/examples). Come here only when the question has become "which write made this readable SocialDB value true?" + +## Canonical example: prove that `mike.near` set `profile.name` to `Mike Purvis` + +Use this when the readable fact is already "the current `profile.name` is `Mike Purvis`" and the remaining question is which write made that field true. + +This is the one SocialDB nuance worth keeping: for historical proof, the field-level `:block` is usually the right bridge, not the parent object's `:block`. + +For this live anchor: + +- current `profile.name`: `Mike Purvis` +- field-level SocialDB write block: `78675795` +- receipt ID: `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN` +- originating transaction hash: `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ` +- outer transaction block: `78675794` + +### Shell walkthrough + +1. Read the field from NEAR Social and capture the field-level write block. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +PROFILE_FIELD=profile/name + +PROFILE_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-profile-name.json \ + | jq -r --arg account_id "$ACCOUNT_ID" \ + '.[ $account_id ].profile.name[":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + current_name: .[$account_id].profile.name[""], + field_block_height: .[$account_id].profile.name[":block"], + parent_profile_block_height: .[$account_id].profile[":block"] +}' /tmp/mike-profile-name.json +``` + +2. Reuse that field-level block in FastNear block receipts and recover the receipt plus tx hash. + +```bash +PROFILE_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-profile-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + profile_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-profile-block.json +``` + +3. Reuse that tx hash in `POST /v0/transactions` and decode the SocialDB write payload. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-profile-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), + description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), + tags: ( + .args + | @base64d + | fromjson + | .data["mike.near"].profile.tags + | keys + ) + } + ) +}' /tmp/mike-profile-transaction.json +``` + +4. Finish with canonical current-state confirmation via raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-profile-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + finality: "final", + current_name: ( + .result.result + | implode + | fromjson + | .[$account_id].profile.name + ) +}' /tmp/mike-profile-rpc.json +``` + +That is the whole provenance pattern: readable value, field-level block, receipt bridge, transaction payload, then optional current-state confirmation. + +## Same pattern, different SocialDB keys + +Use the same bridge whenever you already have a readable SocialDB value and its write block: + +1. Read the semantic value and `:block` from NEAR Social, or start from a known widget write block. +2. Use Transactions API [`POST /v0/block`](/tx/block) to recover the `*.near -> social.near` receipt and transaction hash. +3. Use Transactions API [`POST /v0/transactions`](/tx/transactions) to decode the `social.near set` payload. +4. Use RPC [`query(call_function)`](/rpc/contract/call-function) only if you still need canonical current-state confirmation. + +### Follow edge variant + +Use the same pattern for one readable follow edge: + +- current edge: `mike.near -> mob.near` +- SocialDB write block: `79574924` +- receipt ID: `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th` +- originating transaction hash: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` + +The only meaningful difference from the profile example is the key and payload shape: read `mike.near/graph/follow/mob.near`, then decode both `graph.follow` and the matching `index.graph` entry from the write payload. + +### Widget source variant + +Use the same pattern again when the readable fact is a widget source key: + +- widget key: `mob.near/widget/Profile` +- SocialDB write block: `86494825` +- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` +- originating transaction hash: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` + +The main difference here is that you usually start from the widget's known write block, then decode `.data["mob.near"].widget.Profile[""]` from the originating `social.near set` payload. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index 9fd0a4b..4281f09 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /api/examples title: "Примеры API" -description: "Пошаговые сценарии использования FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга." +description: "Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и классификации стейкинга." displayed_sidebar: fastnearApiSidebar page_actions: - markdown @@ -91,19 +91,19 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. -### У меня обычный стейкинг или liquid staking? +### Есть ли у этого аккаунта прямой стейкинг прямо сейчас? -Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». +Используйте этот сценарий, когда история проста: «скажи, есть ли у аккаунта видимые прямые staking pool прямо сейчас, и покажи, какие именно это пулы».
Стратегия -

Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк.

+

Один раз прочитайте staking-эндпоинт и превратите видимый список пулов в ответ “да / нет”.

-

01GET /v1/account/.../staking находит прямую экспозицию через пулы.

-

02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них.

-

03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed.

+

01GET /v1/account/.../staking возвращает видимые прямые staking-позиции аккаунта.

+

02jq превращает ответ в has_direct_staking_now, pool_count и pool_ids.

+

03Если массив pools пуст, ответ этой поверхности просто звучит как «прямой стейкинг сейчас не виден».

@@ -114,350 +114,159 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Официальные ссылки** - [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) -- [Liquid staking](https://docs.near.org/primitives/liquid-staking) - -Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. **Что вы делаете** - Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. -- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. -- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. +- Печатаете короткий итог “да / нет” и список видимых `pool_id`. +- На этом останавливаетесь, если только следующий вопрос уже не касается `unstake` или `withdraw` в конкретном пуле. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +ACCOUNT_ID=mike.near ``` 1. Получите представление по прямому стейкингу. ```bash curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` + | tee /tmp/account-staking.json >/dev/null -2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null +jq '{ + account_id, + has_direct_staking_now: ((.pools // []) | length > 0), + pool_count: ((.pools // []) | length), + pool_ids: ((.pools // []) | map(.pool_id)) +}' /tmp/account-staking.json ``` -3. Классифицируйте аккаунт на основе этих двух индексированных представлений. - -```bash -jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ - --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens - | { - classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) - }' -``` +На момент написания для `mike.near` здесь возвращались видимые прямые staking-пулы. Если для вашего аккаунта `pool_ids` пуст, этот эндпоинт отвечает: «прямой стейкинг сейчас не виден». **Зачем нужен следующий шаг?** -Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. +Так вопрос остаётся узким и практическим. Если ответ `true`, следующий реальный шаг обычно связан с `unstake` или `withdraw` в конкретном пуле. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. -### Заархивировать версию BOS-виджета как provenance NFT +### Какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает? -Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». +Используйте этот сценарий, когда у wallet-экрана, support-инструмента или агента уже есть `account_id` и нужен быстрый индексированный обзор holdings: FT-балансы плюс NFT-коллекции, из которых этот аккаунт сейчас что-то показывает.
Стратегия -

Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы.

+

Сначала прочитайте FT-балансы, затем NFT-коллекции и только потом соберите их в один компактный индексированный инвентарь.

-

01GET /v1/account/.../nft проверяет, появляется ли у получателя уже запись об этой архивной коллекции.

-

02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB.

-

03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте точные provenance-поля через nft_token.

+

01GET /v1/account/.../ft даёт индексированные FT-балансы кошелька.

+

02GET /v1/account/.../nft даёт NFT-коллекции, из которых этот кошелёк сейчас показывает holdings.

+

03jq превращает эти два индексированных чтения в один wallet-friendly инвентарь.

-**Сети** - -- mainnet для чтения виджета из `social.near` -- testnet для безопасного mint provenance NFT в `nft.examples.testnet` - -**Официальные ссылки** - -- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) -- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - **Что вы делаете** -- Через FastNear API проверяете, появляется ли у получателя уже запись об архивной коллекции. -- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. -- Хешируете исходник виджета и превращаете его в provenance-метаданные. -- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. -- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. +- Читаете FT-балансы аккаунта. +- Читаете NFT-holdings аккаунта на уровне коллекций. +- Печатаете один короткий индексированный инвентарь, который можно переиспользовать в wallet- или support-сценарии. -Зафиксированный исходный виджет: +Этот пример не отвечает на вопросы про нативный баланс, стейкинг, пулы, точные NFT token ID или метаданные. -- аккаунт автора: `mob.near` -- путь виджета: `mob.near/widget/Profile` -- SocialDB-блок уровня виджета: `86494825` +NFT-эндпоинт здесь работает на уровне коллекций. Воспринимайте его как ответ на вопрос «из каких NFT-контрактов этот аккаунт сейчас что-то держит?», а не как полный per-token crawl. ```bash -API_BASE_URL=https://test.api.fastnear.com -MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com -TESTNET_RPC_URL=https://rpc.testnet.fastnear.com -AUTHOR_ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -DESTINATION_COLLECTION_ID=nft.examples.testnet -RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID + +# Пример живого значения, проверенного 19 апреля 2026 года: +# ACCOUNT_ID=mike.near ``` -1. Через FastNear API посмотрите, появляется ли у получателя уже запись об архивной коллекции. +1. Прочитайте FT-балансы аккаунта. ```bash -curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ - | tee /tmp/provenance-account-nfts.json >/dev/null - -jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_collection_entries: [ - .tokens[]? - | select(.contract_id == $destination_collection_id) - | { +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null + +jq '{ + account_id, + ft_contracts: ( + .tokens + | map(select((.balance // "0") != "0") | { contract_id, + balance, last_update_block_height - } - ] -}' /tmp/provenance-account-nfts.json + }) + | .[:10] + ) +}' /tmp/account-ft.json ``` -Это быстрый чек наличия коллекции у получателя, а не точный список токенов. Точный выпущенный токен позже подтверждается через `nft_token`. - -2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. +2. Прочитайте NFT-коллекции для того же аккаунта. ```bash -WIDGET_ARGS_BASE64="$( - jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($author_account_id + "/widget/" + $widget_name)], - options: {with_block_height: true} - }' | base64 | tr -d '\n' -)" +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | tee /tmp/account-nft.json >/dev/null -curl -s "$MAINNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget.json >/dev/null - -jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - widget_path: ($author_account_id + "/widget/" + $widget_name), - socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], - source_preview: ( - .[$author_account_id].widget[$widget_name][""] - | split("\n")[0:8] +jq '{ + account_id, + nft_collections: ( + (.tokens // []) + | map({ + contract_id, + last_update_block_height + }) + | unique_by(.contract_id) + | .[:10] ) -}' /tmp/bos-widget.json +}' /tmp/account-nft.json ``` -3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. +3. Соберите из этих двух чтений один компактный инвентарь. ```bash -jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][""] -' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx - -WIDGET_BLOCK_HEIGHT="$( - jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][":block"] - ' /tmp/bos-widget.json -)" - -SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" -SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" -TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" - -PROVENANCE_METADATA_JSON="$( - jq -nc \ - --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" \ - --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ - --arg block_height "$WIDGET_BLOCK_HEIGHT" \ - --arg source_sha256 "$SOURCE_SHA256" '{ - title: ("BOS widget archive: " + $widget_path), - description: ("Archived from social.near on mainnet at block " + $block_height), - copies: 1, - extra: ({ - author_account_id: $author_account_id, - widget_name: $widget_name, - widget_path: $widget_path, - source_contract_id: "social.near", - source_network: "mainnet", - socialdb_block_height: ($block_height | tonumber), - source_sha256: $source_sha256 - } | @json) +jq -n \ + --slurpfile ft /tmp/account-ft.json \ + --slurpfile nft /tmp/account-nft.json ' + ($ft[0].tokens // []) as $ft_tokens + | ($nft[0].tokens // []) as $nft_tokens + | { + account_id: ($ft[0].account_id // $nft[0].account_id), + ft_contract_count: ( + $ft_tokens + | map(select((.balance // "0") != "0")) + | length + ), + nft_collection_count: ( + $nft_tokens + | map(.contract_id) + | unique + | length + ), + ft_contracts: ( + $ft_tokens + | map(select((.balance // "0") != "0") | { + contract_id, + balance, + last_update_block_height + }) + | .[:10] + ), + nft_collections: ( + $nft_tokens + | map({ + contract_id, + last_update_block_height + }) + | unique_by(.contract_id) + | .[:10] + ) }' -)" - -printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' -``` - -4. Выпустите provenance NFT в testnet. - -```bash -near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ - --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --argjson metadata "$PROVENANCE_METADATA_JSON" '{ - token_id: $token_id, - receiver_id: $receiver_id, - metadata: $metadata - }')" \ - --accountId "$SIGNER_ACCOUNT_ID" \ - --deposit 0.1 \ - --networkId testnet ``` -5. Подтвердите через точное чтение `nft_token`, что выпущенный NFT действительно несёт ожидаемые provenance-поля. - -Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. - -```bash -NFT_TOKEN_ARGS_BASE64="$( - jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ - | base64 | tr -d '\n' -)" - -for attempt in 1 2 3 4 5; do - curl -s "$TESTNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_token", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget-provenance-token.json >/dev/null - - if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then - break - fi - - sleep 1 -done - -jq '{ - token_id, - owner_id, - title: .metadata.title, - provenance: (.metadata.extra | fromjson) -}' /tmp/bos-widget-provenance-token.json -``` +Для `mike.near` на 19 апреля 2026 года эти чтения вернули десятки FT-контрактов и NFT-коллекций. Этого достаточно для частого wallet-вопроса: «какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает?» **Зачем нужен следующий шаг?** -FastNear API даёт быстрый чек наличия коллекции со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](/tx/examples). - -## Частые задачи - -### Что этот аккаунт вообще держит прямо сейчас? - -**Начните здесь** - -- [V1 Full Account View](/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» - -**Следующая страница при необходимости** - -- [V1 Account FT](/api/v1/account-ft), [V1 Account NFT](/api/v1/account-nft) или [V1 Account Staking](/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. -- [Transactions API account history](/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» - -**Остановитесь, когда** - -- Сводка уже отвечает на вопрос по активам в одной выдаче. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](/rpc). -- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](/tx). - -### Определить аккаунты по публичному ключу - -**Начните здесь** - -- [V1 Public Key Lookup](/api/v1/public-key), когда нужен основной аккаунт для ключа. -- [V1 Public Key Lookup All](/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. - -**Следующая страница при необходимости** - -- [V1 Full Account View](/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. - -**Остановитесь, когда** - -- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](/rpc/account/view-access-key) или [View Access Key List](/rpc/account/view-access-key-list). -- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](/tx). - -### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? - -**Начните здесь** - -- [V1 Account FT](/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. -- [V1 Account NFT](/api/v1/account-nft), когда вопрос конкретно про владение NFT. -- [V1 Account Staking](/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. - -**Следующая страница при необходимости** - -- [V1 Full Account View](/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. -- [Transactions API account history](/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» - -**Остановитесь, когда** - -- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. - -**Переходите дальше, когда** - -- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](/rpc). -- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](/tx). +Переходите к [`GET /v1/account/{account_id}/full`](/api/v1/account-full), когда следующий вопрос уже требует ещё и стейкинг, пулы или нативное состояние аккаунта. Переходите к contract-specific чтениям только тогда, когда вопрос меняется на «какие именно идентификаторы NFT-токенов и метаданные мне принадлежат?» ## Частые ошибки diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index c0a8286..416b756 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: "Примеры KV FastData" -description: "Пошаговые сценарии для проверки точных FastData-ключей, чтения истории точного ключа и привязки индексированных строк к исходной транзакции." +description: "Пошаговые сценарии для чтения точных строк FastData, проверки истории точного ключа и привязки индексированной строки к исходной транзакции." displayed_sidebar: kvFastDataSidebar page_actions: - markdown @@ -10,18 +10,24 @@ page_actions: ## Быстрый старт -Если контракт, predecessor и точное имя строки уже известны, сначала прочитайте именно эту строку. +Если точные FastData-ключи уже известны, читайте их напрямую. ```bash KV_BASE_URL=https://kv.test.fastnear.com CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=YOUR_TESTNET_ACCOUNT -FASTDATA_ROW=value +PREDECESSOR_ID=kv.gork-agent.testnet -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_ROW" \ +curl -s "$KV_BASE_URL/v0/multi" \ + -H 'content-type: application/json' \ + --data '{ + "keys": [ + "kv.gork-agent.testnet/kv.gork-agent.testnet/key", + "kv.gork-agent.testnet/kv.gork-agent.testnet/value" + ] + }' \ | jq '{ - latest_row: ( - .entries[0] + entries: [ + .entries[] | { current_account_id, predecessor_id, @@ -29,148 +35,107 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_RO key, value } - ) + ] }' ``` -Это самое короткое чтение FastData на странице. Полный walkthrough ниже добавляет управляемую запись, историю точного ключа и привязку к транзакции. +Это самое короткое полезное чтение FastData на странице: один запрос и сразу две точные строки. ## Готовое расследование -### Сделать одну testnet-запись FastData и проверить точные индексированные ключи +### Прочитать одну индексированную настройку, а затем привязать её к записи -Используйте это расследование, когда нужно доказать, какие именно строки FastData породила одна запись, подтвердить точную историю одного из этих ключей, а затем привязать индексированную строку к исходной транзакции. +Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки, менялось ли оно раньше и какая транзакция его создала?»
Стратегия -

Сделайте одну управляемую запись FastData, проверьте точные строки ключей, которые она эмитировала, а затем гидратируйте транзакцию, которая их породила.

+

Сначала читайте точные строки настройки, расширяйтесь до метаданных предшественника только если нужна provenance-цепочка, и переходите к Transactions API только для финального доказательства.

-

01near call __fastdata_kv создаёт одну управляемую запись на testnet от вашего собственного аккаунта-предшественника.

-

02get-latest-key и get-history-key проверяют точные строки FastData, которые породил этот вызов.

-

03latest-by-predecessor с метаданными плюс POST /v0/transactions привязывают эти индексированные строки к исходному вызову.

+

01multi или get-latest-key читают точные индексированные строки настройки.

+

02get-history-key показывает, менялось ли это индексированное значение позже.

+

03latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки.

**Цель** -- Доказать, какие именно строки FastData породила запись, и показать, как проследить эти строки обратно до создавшей их транзакции. +- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта, подтвердить историю точного ключа для одной строки и восстановить транзакцию, которая создала обе строки. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Управляемая запись | CLI `near` | Отправляем один testnet-вызов `__fastdata_kv` с уникальным значением | Даёт запись, которую можно сразу проверить, не полагаясь на чужие старые данные | -| Точная индексированная строка | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Читаем точную строку `value`, а затем точную строку `key`, внутри одного предшественника и контракта | Доказывает точные строки FastData, которые сейчас индексированы для этой записи | -| История точного ключа | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) | Забираем историю того же точного ключа `value` | Показывает, менялась ли эта точная строка снова после записи | -| Более широкий паттерн + метаданные | KV FastData [`latest-by-predecessor`](/fastdata/kv/latest-by-predecessor) | Список последних строк для того же предшественника с `include_metadata: true` | Возвращает обе эмитированные строки вместе с `tx_hash` и `receipt_id`, которые их породили | -| Гидратация транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем payload из `FunctionCall.args` | Доказывает, какой именно вызов создал индексированные строки FastData | +| Чтение точной настройки | KV FastData [`multi`](/fastdata/kv/multi) | Читаем известные строки `key` и `value` одним запросом | Это самое узкое полезное чтение, когда точные индексированные строки настройки уже известны | +| Чтение точной строки | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Повторно читаем одну точную строку по path-маршруту | Полезно, когда вопрос только про одну строку, а не про всю пару настройки | +| История точного ключа | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) | Проверяем историю точной строки `value` | Показывает, менялось ли именно это индексированное значение в нескольких записях | +| Необязательный мост к provenance | KV FastData [`latest-by-predecessor`](/fastdata/kv/latest-by-predecessor) | Восстанавливаем `tx_hash` и `receipt_id` для индексированных строк только если provenance действительно важна | Это необязательный мост от индексированных строк обратно к одной записи | +| Необязательная гидратация транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем исходные args только когда нужно это доказательство | Финальное необязательное доказательство того, что обе строки создал один вызов | **Что должен включать полезный ответ** -- какие именно `current_account_id`, `predecessor_id` и `key` были исследованы -- как выглядит последняя индексированная строка и история этого же точного ключа -- какой `tx_hash` или `receipt_id` породил строку, если важна provenance-цепочка -- остаётся ли вопрос про строки FastData или уже перешёл к контрактно-специфичному состоянию в цепочке +- какие именно `current_account_id`, `predecessor_id` и индексированные строки настройки исследовались +- как выглядят последние индексированные строки и история точного ключа для одной из них +- какой `tx_hash` или `receipt_id` создал эти строки, только если важна provenance-цепочка +- остаётся ли вопрос про индексированные строки FastData или уже перешёл к каноническому состоянию контракта + +### Проверенный read-only testnet shell-сценарий -### Проверенный testnet shell-сценарий +Используйте этот сценарий, когда нужен полностью read-only пример на стабильных sample-данных в `kv.gork-agent.testnet`. -Используйте этот сценарий, когда у вас уже настроен testnet-аккаунт в `near` CLI и нужен один воспроизводимый сценарий записи, который можно проверить от начала до конца. +Этот минимальный контракт ведёт себя как крошечное хранилище настроек: одна запись эмитирует две индексированные строки, `key` и `value`. Сейчас sample-настройка выглядит как `test=hello`, и этого достаточно, чтобы показать shape FastData без притворства, будто перед нами более богатый прикладной объект. +Этот sample-контракт индексирует собственные записи, поэтому в этом walkthrough `CURRENT_ACCOUNT_ID` и `PREDECESSOR_ID` намеренно совпадают. **Что вы делаете** -- Пишете одну свежую запись FastData в `kv.gork-agent.testnet`. -- Ждёте, пока KV FastData проиндексирует эту транзакцию. -- Читаете точную строку `value` и точную строку `key`, которые эмитировал контракт. -- Забираете историю точного ключа `value`. -- Расширяетесь до области предшественника с метаданными, чтобы получить индексированный `tx_hash`. -- Гидратируете эту транзакцию и декодируете исходные аргументы вызова `__fastdata_kv`. +- Читаете точные индексированные строки настройки вместе. +- Повторно читаете те же строки по отдельности, чтобы был понятен shape exact-key маршрута. +- Забираете историю точного ключа для строки `value` этой настройки. +- Останавливаетесь на этом, если provenance дальше не нужна. ```bash KV_BASE_URL=https://kv.test.fastnear.com TX_BASE_URL=https://tx.test.fastnear.com CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -SIGNER_ID=YOUR_TESTNET_ACCOUNT -PREDECESSOR_ID="$SIGNER_ID" -FASTDATA_FIELD=verification -FASTDATA_VALUE="verify-$(date -u +%Y%m%dT%H%M%SZ)" - -near call "$CURRENT_ACCOUNT_ID" __fastdata_kv \ - "$(jq -nc --arg key "$FASTDATA_FIELD" --arg value "$FASTDATA_VALUE" '{key: $key, value: $value}')" \ - --accountId "$SIGNER_ID" \ - --networkId testnet \ - --gas 30000000000000 \ - | tee /tmp/kv-fastdata-call.txt - -CLI_TX_HASH="$( - awk '/Transaction Id/{print $3}' /tmp/kv-fastdata-call.txt -)" +PREDECESSOR_ID=kv.gork-agent.testnet -ATTEMPTS=0 -until curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ +curl -s "$KV_BASE_URL/v0/multi" \ -H 'content-type: application/json' \ - --data '{"include_metadata": true, "limit": 20}' \ - | tee /tmp/kv-predecessor-latest.json \ - | jq -e --arg tx_hash "$CLI_TX_HASH" ' - .entries - | map(select(.tx_hash == $tx_hash)) - | length > 0 - ' >/dev/null -do - ATTEMPTS=$((ATTEMPTS + 1)) - if [ "$ATTEMPTS" -ge 30 ]; then - echo "Timed out waiting for KV FastData to index $CLI_TX_HASH" >&2 - exit 1 - fi - sleep 2 -done - -INDEXED_TX_HASH="$( - jq -r --arg tx_hash "$CLI_TX_HASH" ' - first(.entries[] | select(.tx_hash == $tx_hash) | .tx_hash) - ' /tmp/kv-predecessor-latest.json -)" - -test "$CLI_TX_HASH" = "$INDEXED_TX_HASH" \ - && echo "CLI tx hash matches indexed metadata" - -jq --arg tx_hash "$CLI_TX_HASH" '{ - tx_hashes: ([.entries[] | select(.tx_hash == $tx_hash) | .tx_hash] | unique), - receipt_ids: ([.entries[] | select(.tx_hash == $tx_hash) | .receipt_id] | unique), + --data '{ + "keys": [ + "kv.gork-agent.testnet/kv.gork-agent.testnet/key", + "kv.gork-agent.testnet/kv.gork-agent.testnet/value" + ] + }' \ + | jq '{ entries: [ .entries[] - | select(.tx_hash == $tx_hash) | { + current_account_id, + predecessor_id, block_height, key, - value, - tx_hash, - receipt_id + value } ] - }' /tmp/kv-predecessor-latest.json - -jq '{ - latest_value_row: ( - .entries[0] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ) -}' <( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" -) + }' curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ | jq '{ latest_key_row: ( .entries[0] | { - current_account_id, - predecessor_id, + block_height, + key, + value + } + ) + }' + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ + | jq '{ + latest_value_row: ( + .entries[0] + | { block_height, key, value @@ -190,6 +155,39 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ } ] }' +``` + +На этом основной read-path заканчивается: точные строки, их exact latest-чтение и история точного ключа для той же индексированной настройки. + +### Необязательное расширение до provenance + +Переходите сюда только тогда, когда следующий вопрос уже звучит как «какая запись создала эти строки?» + +```bash + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ + -H 'content-type: application/json' \ + --data '{"include_metadata": true, "limit": 10}' \ + | tee /tmp/kv-predecessor-latest.json >/dev/null + +jq '{ + entries: [ + .entries[] + | { + block_height, + key, + value, + tx_hash, + receipt_id + } + ] +}' /tmp/kv-predecessor-latest.json + +INDEXED_TX_HASH="$( + jq -r ' + first(.entries[] | select(.key == "value") | .tx_hash) + ' /tmp/kv-predecessor-latest.json +)" curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ @@ -204,126 +202,29 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | @base64d | fromjson ), - first_receipt_id: .transactions[0].execution_outcome.outcome.receipt_ids[0] + receipt_ids: .transactions[0].execution_outcome.outcome.receipt_ids }' ``` **Зачем нужен следующий шаг?** -Этот контракт эмитирует две строки FastData из одного вызова: строку `key`, в которой лежит логическое имя поля, и строку `value`, в которой лежит само значение. Маршруты точного ключа напрямую доказывают наличие этих строк. Lookup по предшественнику — это мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, породившие эти строки. Гидратация транзакции доказывает, что индексированные строки возникли из одного вызова `__fastdata_kv` с теми же декодированными аргументами. - -Именно здесь проходит важная граница этой поверхности: строки FastData — это индексированный вывод FastData, а не обещание, что сырое RPC `view_state` покажет те же ключи. Поскольку это индексированная поверхность, свежая запись может появиться не мгновенно; дождитесь, пока в индексе появится ваш `tx_hash`, прежде чем считать latest-строки окончательными. Если вопрос пользователя меняется с «какие строки FastData были эмитированы?» на «как выглядит каноническое on-chain-состояние контракта?», переходите к собственному read-методу контракта или к [View State](/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную вам layout-структуру хранилища. - -## Частые задачи - -### Посмотреть один точный ключ FastData прямо сейчас - -**Начните здесь** - -- [Последнее по точному ключу](/fastdata/kv/get-latest-key), когда точный ключ уже известен. - -**Следующая страница при необходимости** - -- [История по точному ключу](/fastdata/kv/get-history-key), если вопрос превращается в «как менялся этот ключ?» - -**Остановитесь, когда** - -- Последняя индексированная запись уже отвечает на вопрос по FastData. - -**Переходите дальше, когда** - -- Пользователя больше не интересуют индексированные строки FastData. Переходите к собственному read-методу контракта или к [View State](/rpc/contract/view-state) только если знаете, какой raw-state layout нужен. - -### Превратить один точный ключ FastData в историю изменений - -**Начните здесь** - -- [История по точному ключу](/fastdata/kv/get-history-key) для path-based истории внутри одного контракта и одного предшественника. -- [History by Key](/fastdata/kv/history-by-key) только тогда, когда вы специально хотите искать один и тот же текст ключа по всем контрактам и предшественникам. - -**Следующая страница при необходимости** - -- Возвращайтесь к [Последнему по точному ключу](/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. - -**Остановитесь, когда** - -- Уже можно объяснить, как ключ менялся со временем. - -**Переходите дальше, когда** - -- Теперь нужно каноническое состояние контракта, а не только индексированная история FastData. Используйте собственный read-метод контракта или [View State](/rpc/contract/view-state) только если форма raw-state уже известна. - -### Проследить записи от одного `predecessor_id` - -**Начните здесь** - -- [Всё по `predecessor_id`](/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. -- [Последнее по `predecessor_id`](/fastdata/kv/latest-by-predecessor), когда нужны строки для одного контракта и одного предшественника, при необходимости вместе с метаданными. -- [История по `predecessor_id`](/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. - -**Следующая страница при необходимости** - -- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования, или переходите к [Transactions by Hash](/tx/transactions), если главным становится provenance-вопрос. - -**Остановитесь, когда** - -- Уже можно ответить, что именно этот предшественник изменил и где. - -**Переходите дальше, когда** - -- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. - -### Привязать одну строку FastData обратно к транзакции - -**Начните здесь** - -- [Последнее по `predecessor_id`](/fastdata/kv/latest-by-predecessor) с `include_metadata: true`, чтобы получить `tx_hash` и `receipt_id`. -- [Transactions by Hash](/tx/transactions), чтобы гидратировать исходный вызов и декодировать его аргументы. - -**Следующая страница при необходимости** - -- Переходите к [Receipt by Id](/tx/receipt), если следующий вопрос уже не про исходный вызов, а про конкретный downstream receipt. - -**Остановитесь, когда** - -- Уже можно объяснить, какой вызов породил индексированную строку FastData. - -**Переходите дальше, когда** - -- Пользователю нужна каноническая семантика исполнения или точный статус на уровне executor. Тогда переходите к RPC transaction status. - -### Пакетно проверить несколько известных ключей - -**Начните здесь** - -- [Пакетный поиск по ключам](/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. - -**Следующая страница при необходимости** - -- Переведите один интересный ключ в [Историю по точному ключу](/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. - -**Остановитесь, когда** - -- Пакетный ответ уже показывает, какие ключи действительно важны. +Этот sample-контракт эмитирует две индексированные строки из одной записи: `key=test` и `value=hello`. Рассматривайте их как одну индексированную настройку. Exact-key маршруты напрямую доказывают эти строки. Lookup по предшественнику с метаданными — это необязательный мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, которые создали эти строки. Гидратация транзакции доказывает, что эти индексированные строки произошли из одного вызова `__fastdata_kv` с декодированными args `{ "key": "test", "value": "hello" }`. -**Переходите дальше, когда** +Именно здесь проходит важная граница этой поверхности: KV FastData отвечает на вопросы про индексированные строки FastData. Если вопрос меняется на каноническое состояние контракта, переходите к собственному read-методу контракта или к [View State](/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную layout-структуру хранилища. -- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. ## Частые ошибки -- Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. -- Путать [Историю по точному ключу](/fastdata/kv/get-history-key) с [History by Key](/fastdata/kv/history-by-key). Первый маршрут остаётся внутри одного контракта и предшественника, второй ищет одинаковый текст ключа глобально. -- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. -- Путать индексированные строки FastData с точным каноническим состоянием контракта. -- Предполагать, что ключ FastData можно напрямую запросить через raw RPC `view_state`. -- Предполагать, что свежая запись будет проиндексирована синхронно с включением в блокчейн. -- Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. +- Начинать с широких выборок по предшественнику, когда точные строки FastData уже известны. +- Считать [History by Key](/fastdata/kv/history-by-key) тем же самым, что и [GET History by Exact Key](/fastdata/kv/get-history-key). Первый маршрут глобальный по строке ключа, второй остаётся внутри одного контракта и predecessor. +- Использовать KV FastData, когда настоящий вопрос про балансы, holdings или account summaries. +- Путать индексированные строки FastData с каноническим on-chain-состоянием контракта. +- Предполагать, что для каждого FastData-расследования сначала обязательно нужна новая запись. ## Полезные связанные страницы - [KV FastData API](/fastdata/kv) +- [Transactions API](/tx) - [RPC Reference](/rpc) -- [FastNear API](/api) - [Choosing the Right Surface](/agents/choosing-surfaces) - [Agent Playbooks](/agents/playbooks) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index ae0c4a0..508fecc 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /neardata/examples title: "Примеры NEAR Data" -description: "Пошаговые сценарии для polling по оптимистичным и финализированным блокам и перехода к RPC, когда это нужно." +description: "Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных хешей для дальнейшего разбора." displayed_sidebar: nearDataApiSidebar page_actions: - markdown @@ -10,84 +10,96 @@ page_actions: ## Быстрый старт -Начните с двух helper-маршрутов, которые показывают, что изменилось прямо сейчас. +Начните с одного недавнего финализированного блока и сначала запросите самую маленькую возможную touch-сводку. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID -curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print "optimistic:", $2}' \ - | tr -d '\r' +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" -curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print "final:", $2}' \ - | tr -d '\r' +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | jq --arg target "$TARGET_ACCOUNT_ID" '{ + height: .block.header.height, + hash: .block.header.hash, + direct_tx_count: ([.shards[].chunk.transactions[]? + | select((.transaction.receiver_id // .receiver_id) == $target)] | length), + incoming_receipt_count: ([.shards[].chunk.receipts[]? + | select(.receiver_id == $target)] | length), + outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? + | select( + (.receipt.receiver_id // "") == $target + or (.execution_outcome.outcome.executor_id // "") == $target + )] | length), + state_change_count: ([.shards[].state_changes[]? + | select((.change.account_id // "") == $target)] | length) + } | . + { + touched: ( + (.direct_tx_count > 0) + or (.incoming_receipt_count > 0) + or (.outcome_hit_count > 0) + or (.state_change_count > 0) + ) + }' ``` -Это даёт текущие optimistic и final redirect target до того, как вы запрашиваете полные документы блоков. +Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. ## Готовое расследование -### Поймать новый блок как можно раньше, а затем подтвердить его после finality +### Был ли мой контракт затронут в последнем финализированном блоке? -Используйте это расследование, когда нужно заметить новый блок как можно раньше, но финальный ответ всё равно должен опираться на финализированный блок и иногда на точное чтение через RPC. +Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC.
Стратегия -

Пусть NEAR Data сначала скажет, что что-то изменилось, а затем переиспользуйте то же семейство блоков для стабильного подтверждения.

+

Зафиксируйтесь на одном финализированном блоке, просканируйте всё семейство блока по целевому аккаунту, а затем оставьте только компактную сводку и идентификаторы, которые действительно стоит разбирать дальше.

-

01block-optimistic или last-block-optimistic дают самый ранний полезный сигнал.

-

02block или last-block-final подтверждают, что то же наблюдение дошло до финализированной истории.

-

03RPC block нужен только в самом конце, когда уже известна точная высота или хеш.

+

01last-block-final даёт одну стабильную высоту блока без угадывания.

+

02block — это главный read: он уже содержит транзакции, receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?»

+

03Только если ответ «да», расширяйтесь дальше: сохраняйте найденные shard id, tx hash и receipt id, а затем передавайте именно эти идентификаторы в [Transactions API](/tx) или [RPC Reference](/rpc).

**Цель** -- Как можно раньше заметить одно свежее изменение в семействе блоков, а затем подтвердить, какой финализированный блок его догнал. +- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только shard id, счётчики и sample-идентификаторы для следующего шага. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | -| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | -| Стабильное подтверждение | NEAR Data [`block`](/neardata/block) или [`last-block-final`](/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | -| Лёгкая сводка по блоку | NEAR Data [`block-headers`](/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | -| Точный разбор через RPC | RPC [Блок по ID](/rpc/block/block-by-id) или [Блок по высоте](/rpc/block/block-by-height) | Получаем точный блок, как только понятно, какой именно блок важен | Здесь уже имеет смысл RPC, если нужен тот самый блок-объект, который вернул бы сам протокол | +| Последняя стабильная точка | NEAR Data [`last-block-final`](/neardata/last-block-final) | Получаем высоту одного финализированного блока без угадывания | Даёт стабильную отправную точку для всего вопроса | +| Всё семейство блока | NEAR Data [`block`](/neardata/block) | Сканируем транзакции, receipts, результаты исполнения receipts и изменения состояния по целевому аккаунту | Это главная поверхность ответа на вопрос «был ли затронут мой контракт?» | +| Лёгкая сводка по блоку | NEAR Data [`block-headers`](/neardata/block-headers) | Используем, когда нужны только высота, хеш, время или заголовки чанков | Позволяет не тянуть более широкий payload блока, когда фильтрация по контракту не нужна | +| Необязательный follow-up по шарду | NEAR Data [`block-chunk`](/neardata/block-chunk) или [`block-shard`](/neardata/block-shard) | Повторно открываем только затронутый шард, если нужен более глубокий payload | Полезно, когда вы уже знаете, какой шард mattered | +| Точные поверхности для продолжения | [Transactions API](/tx) или [RPC Reference](/rpc) | Переиспользуем найденные tx hash или receipt id только если нужна полная история исполнения | NEAR Data позволяет сначала понять, нужен ли вообще переход дальше | **Что должен включать полезный ответ** -- какой redirect target и какой разрешённый оптимистичный блок впервые запустили расследование -- когда helper для finality догнал его и в какой блок он разрешился -- изменил ли точный разбор через RPC интерпретацию +- финализированную высоту и хеш +- ответ “затронут / не затронут” +- счётчики прямых транзакций, входящих receipts, outcome-hit и state changes +- по одному sample tx hash или receipt id на категорию, когда он есть -### Shell-сценарий от оптимистичного сигнала к финализированному подтверждению +### Shell-сценарий от финализированного блока к ответу по контракту -Используйте этот сценарий, когда нужно сразу заметить свежее изменение в семействе блоков, а затем доказать, какой финализированный блок его догнал, и подтвердить именно эту высоту через RPC. +Используйте этот сценарий, когда целевой аккаунт уже известен и нужен один свежий финализированный ответ, а не длинный polling-цикл. **Что вы делаете** -- Смотрите redirect, который возвращает `GET /v0/last_block/optimistic`. -- Загружаете разрешённый оптимистичный блок и сохраняете его высоту и хеш. -- Смотрите redirect, который возвращает `GET /v0/last_block/final`, и сохраняете финализированный counterpart. -- Сравниваете оптимистичное и финализированное наблюдения, а затем переиспользуете финализированную высоту в RPC `block` по высоте. +- Получаете redirect target для последнего финализированного блока. +- Один раз загружаете полный документ блока. +- Собираете один компактный ответ по одному `TARGET_ACCOUNT_ID`. +- Получаете ответ “да / нет” плюс минимально полезные счётчики и sample-идентификаторы. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -RPC_URL=https://rpc.mainnet.fastnear.com - -OPTIMISTIC_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -printf 'Optimistic redirect target: %s\n' "$OPTIMISTIC_LOCATION" - -curl -s "$NEARDATA_BASE_URL$OPTIMISTIC_LOCATION" \ - | tee /tmp/neardata-optimistic-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' +TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -95,134 +107,140 @@ FINAL_LOCATION="$( | tr -d '\r' )" +BLOCK_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | sed -E 's#.*/([0-9]+)$#\1#')" + printf 'Final redirect target: %s\n' "$FINAL_LOCATION" +printf 'Final block height: %s\n' "$BLOCK_HEIGHT" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' - -jq -n \ - --slurpfile optimistic /tmp/neardata-optimistic-block.json \ - --slurpfile final /tmp/neardata-final-block.json '{ - optimistic: { - height: $optimistic[0].block.header.height, - hash: $optimistic[0].block.header.hash - }, - final: { - height: $final[0].block.header.height, - hash: $final[0].block.header.hash - }, - same_height: ( - $optimistic[0].block.header.height - == $final[0].block.header.height - ) - }' - -BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "block", - params: { - block_id: ($block_height | tonumber) + | tee /tmp/neardata-block.json >/dev/null + +jq --arg target "$TARGET_ACCOUNT_ID" ' + ( + [ + .shards[] + | .chunk.transactions[]? + | select((.transaction.receiver_id // .receiver_id) == $target) + | (.transaction.hash // .hash) + ] + ) as $txs + | ( + [ + .shards[] + | .chunk.receipts[]? + | select(.receiver_id == $target) + | .receipt_id + ] + ) as $receipts + | ( + [ + .shards[] + | .receipt_execution_outcomes[]? + | select( + (.receipt.receiver_id // "") == $target + or (.execution_outcome.outcome.executor_id // "") == $target + ) + | .tx_hash + | select(. != null) + ] + | unique + ) as $outcomes + | ( + [ + .shards[] + | .state_changes[]? + | select((.change.account_id // "") == $target) + | .type + ] + ) as $state_changes + | { + height: .block.header.height, + hash: .block.header.hash, + touched: ( + ($txs | length) > 0 + or ($receipts | length) > 0 + or ($outcomes | length) > 0 + or ($state_changes | length) > 0 + ), + direct_tx_count: ($txs | length), + incoming_receipt_count: ($receipts | length), + outcome_hit_count: ($outcomes | length), + state_change_count: ($state_changes | length), + sample_direct_tx: ($txs[0] // null), + sample_incoming_receipt: ($receipts[0] // null), + sample_outcome_tx_hash: ($outcomes[0] // null) } - }')" \ - | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json ``` -**Зачем нужен следующий шаг?** - -Так вы получаете обе стороны истории: самый ранний оптимистичный якорь и более поздний финализированный якорь. Как только helper для finality сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. - -## Частые задачи - -### Отслеживать последний оптимистичный блок - -**Начните здесь** - -- [Оптимистичный блок](/neardata/block-optimistic) для самого свежего чтения по семейству блоков. - -**Следующая страница при необходимости** - -- [Перенаправление на последний оптимистичный блок](/neardata/last-block-optimistic), если нужен маршрут перенаправления, который всегда ведёт к самому новому оптимистичному блоку. - -**Остановитесь, когда** - -- Уже можно сообщить о последнем оптимистичном блоке или зафиксировать отставание по свежести. - -**Переходите дальше, когда** - -- Нужна finalized-стабильность вместо максимальной свежести. Переходите к [Финализированному блоку по высоте](/neardata/block) или [Перенаправлению на последний финализированный блок](/neardata/last-block-final). - -### Безопасно отслеживать ход финализации блоков +Если позже понадобятся более богатые списки или разбор по шардам, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. -**Начните здесь** +Необязательное расширение: если всё же нужны `touched_shards`, их можно вычислить из того же сохранённого блока, не утяжеляя основной ответ: -- [Финализированный блок по высоте](/neardata/block), когда уже известна нужная высота. -- [Заголовки блока](/neardata/block-headers), когда достаточно чтения заголовков. - -**Следующая страница при необходимости** - -- [Перенаправление на последний финализированный блок](/neardata/last-block-final), когда клиент должен следовать за самым новым финализированным блоком без предварительного вычисления высоты. - -**Остановитесь, когда** - -- Уже можно показывать движение финализированных блоков без перехода к более глубоким протокольным деталям. - -**Переходите дальше, когда** - -- Пользователю нужны точные поля блока или семантика транзакций. Переходите к [RPC Reference](/rpc). - -### Использовать маршруты перенаправления в клиенте опроса - -**Начните здесь** - -- [Перенаправление на последний финализированный блок](/neardata/last-block-final) или [Перенаправление на последний оптимистичный блок](/neardata/last-block-optimistic) в зависимости от требуемой свежести. - -**Следующая страница при необходимости** - -- Следуйте по URL блока, который вернул маршрут перенаправления, и уже там читайте нужные данные. - -**Остановитесь, когда** - -- Клиент надёжно проходит по маршруту перенаправления и получает нужный ресурс блока. - -**Переходите дальше, когда** - -- Само перенаправление мешает клиенту. Тогда переходите на прямые маршруты блоков. - -### Перейти от опроса свежих блоков к точному RPC-разбору - -**Начните здесь** - -- Используйте подходящий маршрут NEAR Data, чтобы найти недавний блок или событие в семействе блоков, которое нужно исследовать. +```bash +jq --arg target "$TARGET_ACCOUNT_ID" ' + [ + .shards[] + | .shard_id as $shard_id + | select( + ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) + or ([.chunk.receipts[]? | .receiver_id] | index($target)) + or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) + or ([.state_changes[]? | .change.account_id] | index($target)) + ) + | $shard_id + ] | unique +' /tmp/neardata-block.json +``` -**Следующая страница при необходимости** +Если в этом ответе `touched: true` и нужен один follow-up на уровне шарда, откройте только первый затронутый шард: -- [Block by Height](/rpc/block/block-by-height), [Block by ID](/rpc/block/block-by-id) или другой RPC-метод, как только станет понятно, какой именно блок или следующий объект для проверки нужен. +```bash +TOUCHED_SHARD_ID="$( + jq -r --arg target "$TARGET_ACCOUNT_ID" ' + first( + .shards[] + | .shard_id as $shard_id + | select( + ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) + or ([.chunk.receipts[]? | .receiver_id] | index($target)) + or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) + or ([.state_changes[]? | .change.account_id] | index($target)) + ) + | $shard_id + ) // empty + ' /tmp/neardata-block.json +)" -**Остановитесь, когда** +if [ -n "$TOUCHED_SHARD_ID" ]; then + curl -s "$NEARDATA_BASE_URL/v0/block/$BLOCK_HEIGHT/chunk/$TOUCHED_SHARD_ID" \ + | jq '{ + shard_id: .header.shard_id, + chunk_hash: .header.chunk_hash, + tx_hashes: ([.transactions[]? | (.transaction.hash // .hash)] | .[:5]), + receipt_ids: ([.receipts[]? | .receipt_id] | .[:5]), + receipt_receivers: ([.receipts[]? | .receiver_id] | .[:5]) + }' +fi +``` -- Уже можно чётко назвать недавний блок, который заслуживает проверки через RPC. +**Зачем нужен следующий шаг?** -**Переходите дальше, когда** +Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один из sample-идентификаторов уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. -- Пользователь просит точную структуру данных в терминах протокола, а не просто свежее чтение. ## Частые ошибки -- Воспринимать NEAR Data как push-стрим, а не как API для опроса. -- Начинать с RPC, когда настоящая задача — мониторинг свежих блоков. -- Забывать, что невалидный ключ может вернуть `401` ещё до перенаправления, а сами перенаправления подходят не каждому HTTP-клиенту. -- Оставаться на NEAR Data после того, как пользователь уже попросил точные протокольные детали блока. +- Воспринимать NEAR Data как push-стрим, а не как polling- или point-read API. +- Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. +- Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. +- Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. +- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data точные shard id, tx hash и receipt id. ## Полезные связанные страницы - [NEAR Data API](/neardata) -- [RPC Reference](/rpc) - [Transactions API](/tx) +- [RPC Reference](/rpc) - [Choosing the Right Surface](/agents/choosing-surfaces) - [Agent Playbooks](/agents/playbooks) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index ead737a..dec3319 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -44,94 +44,31 @@ curl -s "$RPC_URL" \ ## Отправка и отслеживание транзакции -Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» +### Двухчастный паттерн: отправить транзакцию или отследить уже известный tx hash до финального исполнения -### Отправить транзакцию и затем проследить её от хеша до финального исполнения +Базовый паттерн: -Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. +- `broadcast_tx_async` для отправки +- `tx` с `wait_until: "FINAL"` для отслеживания +- `EXPERIMENTAL_tx_status` только если следующий вопрос уже про receipts -Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: +Этот walkthrough намеренно разбит на две части: + +- отправить новую подписанную транзакцию и сохранить возвращённый хеш +- отследить один известный исторический tx hash с воспроизводимым выводом + +Для части про отслеживание используется одна зафиксированная историческая транзакция, поэтому status-lookup идёт через архивный хост: - хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - signer: `mike.near` - receiver: `social.near` -- высота блока включения: `79574923` -- высота блока исполнения receipt для записи в SocialDB: `79574924` - -Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. - -
-
- Стратегия -

Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно.

-
-
-

01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше.

-

02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения.

-

03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts.

-
-
+- `https://archival-rpc.mainnet.fastnear.com` -**Что вы здесь решаете** - -- какой эндпоинт отправки брать первым -- что опрашивать после того, как у вас появился tx hash -- как `wait_until` связан с included-, optimistic- и final-гарантиями -- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` - -```mermaid -flowchart LR - S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] - A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] - T --> F["Транзакция полностью завершена"] - T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] - F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] -``` - -| Метод | Когда использовать | Что вернётся | Роль здесь | -| --- | --- | --- | --- | -| [`broadcast_tx_async`](/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | -| [`send_tx`](/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | -| [`broadcast_tx_commit`](/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | -| [`tx`](/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | -| [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | - -**Карта статусов и ожидания** - -Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. - -| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | -| --- | --- | --- | -| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | -| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | -| `INCLUDED_FINAL` | блок включения уже финален | `tx` | -| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | -| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | - -Практическое различие очень простое: - -- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash -- используйте `tx` как обычный цикл опроса -- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу - -**Что вы делаете** - -- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. -- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. -- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. -- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. +1. Отправьте новую подписанную транзакцию и сохраните возвращённый хеш. ```bash RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near -RECEIVER_ID=social.near -``` - -1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. -```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data '{ @@ -143,40 +80,16 @@ curl -s "$RPC_URL" \ | jq . ``` -В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. +Этот первый шаг нужен только для формы отправки. Именно возвращённый хеш вы потом будете отслеживать для своей живой транзакции. -2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. +2. Отслеживайте один известный tx hash, пока не получите самый простой финальный ответ. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "INCLUDED_FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' +RPC_URL=https://archival-rpc.mainnet.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near ``` -Что здесь важно заметить: - -- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality -- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения -- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt - -3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. - ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ @@ -194,18 +107,12 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - status: .result.status, + transaction_status: .result.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Что здесь важно заметить: - -- для исторической tx этот вызов тоже возвращается сразу -- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» -- для многих приложений именно здесь и стоит остановиться - -4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. +3. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда для этого известного tx уже нужен уровень receipts. ```bash curl -s "$RPC_URL" \ @@ -224,277 +131,57 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - status: .result.status, transaction_handoff: .result.transaction_outcome.outcome.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». - -5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transaction_handoff: .transactions[0].transaction_outcome.outcome.status - }' -``` - -Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» - -**Рекомендуемый паттерн** - -- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. -- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. -- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. +Если вы хотите, чтобы узел ждал за вас, используйте [`send_tx`](/rpc/transaction/send-tx). Но базовый паттерн на этой странице остаётся таким: отправка через `broadcast_tx_async`, затем отслеживание хеша через `tx`. ## Механика аккаунтов и ключей Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. -### Проверить и удалить старые function-call-ключи Near Social +### Может ли этот access key прямо сейчас вызвать этот контракт? -Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. +Используйте этот сценарий, когда у вас уже есть аккаунт, один public key и целевой контракт, а вам нужен простой ответ да или нет до того, как вы начнёте что-то подписывать.
Стратегия -

Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление.

+

Сначала отфильтруйте ключи аккаунта, затем прочитайте точный ключ и только потом классифицируйте его права.

-

01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near.

-

02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта.

-

03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат.

+

01RPC view_access_key_list сужает список до ключей, которые вообще могут относиться к целевому контракту.

+

02RPC view_access_key даёт точный permission-object для того public key, которым вы реально можете подписывать.

+

03jq превращает этот permission-object в full_access, function_call_match, receiver_mismatch или method_not_allowed.

**Что вы делаете** -- Через сам RPC получаете полный список access key аккаунта. -- Сужаете этот список до function-call-ключей, привязанных к `social.near`. -- Точно проверяете один выбранный ключ перед удалением. -- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. - -Сразу важны два ограничения: - -- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. -- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export SOCIAL_RECEIVER_ID=social.near -export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' -export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' -export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' -``` - -1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } - }')" \ - | tee /tmp/fastnear-access-keys.json >/dev/null - -jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' - .result.keys[] - | select((.access_key.permission | type) == "object") - | select(.access_key.permission.FunctionCall.receiver_id == $receiver) - | { - public_key, - nonce: .access_key.nonce, - receiver_id: .access_key.permission.FunctionCall.receiver_id, - method_names: .access_key.permission.FunctionCall.method_names, - allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") - } -' /tmp/fastnear-access-keys.json -``` - -Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. - -2. Ещё раз проверьте конкретный ключ перед удалением. +- Получаете access key аккаунта и сужаете список до нужного контракта. +- Точно проверяете тот ключ, которым собираетесь подписывать. +- Решаете, может ли он вызвать этот receiver и method, не выходя за пределы RPC. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$DELETE_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } - }')" \ - | jq '{nonce: .result.nonce, permission: .result.permission}' -``` - -3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. - -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - is_function_call: true, - limit: 10 - }')" \ - | jq '{ - account_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_success - } - ] - }' -``` - -Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. - -4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. - -Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +TARGET_CONTRACT_ID=crossword.puzzle.near +TARGET_METHOD_NAME=new_puzzle +TARGET_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_WANT_TO_CHECK' -```bash -SIGNED_TX_BASE64="$( - node --input-type=module <<'EOF' -import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; - -const { - ACCOUNT_ID, - NETWORK_ID = 'mainnet', - RPC_URL = 'https://rpc.mainnet.fastnear.com', - DELETE_PUBLIC_KEY, - FULL_ACCESS_PUBLIC_KEY, - FULL_ACCESS_PRIVATE_KEY, -} = process.env; - -for (const name of [ - 'ACCOUNT_ID', - 'DELETE_PUBLIC_KEY', - 'FULL_ACCESS_PUBLIC_KEY', - 'FULL_ACCESS_PRIVATE_KEY', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); -const derivedPublicKey = keyPair.getPublicKey().toString(); - -if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { - throw new Error( - `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: ACCOUNT_ID, - public_key: FULL_ACCESS_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const transaction = transactions.createTransaction( - ACCOUNT_ID, - utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), - ACCOUNT_ID, - BigInt(accessKey.nonce) + 1n, - [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -)" +# Пример живых значений, проверенных 19 апреля 2026 года: +# ACCOUNT_ID=mike.near +# TARGET_CONTRACT_ID=crossword.puzzle.near +# TARGET_METHOD_NAME=new_puzzle +# TARGET_PUBLIC_KEY='ed25519:otwaB1X88ocpmUdC1B5XaifucfDLmLKaonb26KqTj96' ``` -5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. +1. Получите ключи аккаунта и сузьте их до целевого контракта. ```bash curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. - -```bash -if curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc: "2.0", @@ -506,65 +193,28 @@ if curl -s "$RPC_URL" \ finality: "final" } }')" \ - | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' - .result.keys[] - | select(.public_key == $public_key) - ' >/dev/null; then - echo "Key is still present: $DELETE_PUBLIC_KEY" -else - echo "Key deleted: $DELETE_PUBLIC_KEY" -fi -``` - -**Зачем нужен следующий шаг?** - -Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. - -### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? - -Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. - -
-
- Стратегия -

Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно.

-
-
-

01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории.

-

02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта.

-

03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey.

-
-
- -**Что вы делаете** - -- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. -- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. -- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. -- Подтягиваете кандидата по транзакциям и различаете три разных ключа: - - ключ, который был добавлен - - public key верхнеуровневого signer - - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` - -Сразу важны три детали про nonce: - -- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. -- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. -- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' - -# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: -# export ACCOUNT_ID=mike.near -# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' + | tee /tmp/access-key-list.json >/dev/null + +jq --arg target_contract_id "$TARGET_CONTRACT_ID" '{ + candidate_keys: [ + .result.keys[] + | select( + .access_key.permission == "FullAccess" + or ( + (.access_key.permission | type) == "object" + and .access_key.permission.FunctionCall.receiver_id == $target_contract_id + ) + ) + | { + public_key, + nonce: .access_key.nonce, + permission: .access_key.permission + } + ] +}' /tmp/access-key-list.json ``` -1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. +2. Прочитайте точное состояние того ключа, который хотите оценить. ```bash curl -s "$RPC_URL" \ @@ -582,175 +232,72 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/key-origin-view.json >/dev/null - -CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" -ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" -SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" -SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" + | tee /tmp/exact-access-key.json >/dev/null -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_public_key "$TARGET_PUBLIC_KEY" \ - --argjson current_nonce "$CURRENT_NONCE" \ - --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ - --argjson search_from "$SEARCH_FROM" \ - --argjson search_to "$SEARCH_TO" \ - --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ - account_id: $account_id, - target_public_key: $target_public_key, - current_nonce: $current_nonce, - estimated_receipt_block: $estimated_receipt_block, - search_from_tx_block_height: $search_from, - search_to_tx_block_height: $search_to, - permission: ($permission | fromjson) - }' +jq '{nonce: .result.nonce, permission: .result.permission}' /tmp/exact-access-key.json ``` -Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. - -2. Ищите историю аккаунта только внутри этого диапазона блоков. +3. Превратите этот permission-object в ответ да или нет для этого контракта и метода. ```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_tx_block_height "$SEARCH_FROM" \ - --argjson to_tx_block_height "$SEARCH_TO" '{ - account_id: $account_id, - is_real_signer: true, - from_tx_block_height: $from_tx_block_height, - to_tx_block_height: $to_tx_block_height, - desc: false, - limit: 50 - }')" \ - | tee /tmp/key-origin-candidates.json >/dev/null - -jq '{ - txs_count, - candidate_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_signer, - is_real_signer, - is_predecessor, - is_receiver +jq -n \ + --slurpfile key /tmp/exact-access-key.json \ + --arg target_contract_id "$TARGET_CONTRACT_ID" \ + --arg target_method_name "$TARGET_METHOD_NAME" ' + ($key[0].result.permission) as $permission + | if $permission == "FullAccess" then + { + can_call_now: true, + reason: "full_access" } - ] -}' /tmp/key-origin-candidates.json -``` - -Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. - -3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. - -```bash -TX_HASHES_JSON="$( - jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json -)" - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ - | tee /tmp/key-origin-transactions.json >/dev/null - -jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "direct", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $tx.transaction.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }), - ($tx.transaction.actions[]? - | .Delegate? - | .delegate_action as $delegate - | $delegate.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "delegated", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $delegate.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - tx_block_hash: $tx.execution_outcome.block_hash, - receiver_id: $tx.transaction.receiver_id - } + . -' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json -``` - -Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. - -Для примерного ключа `mike.near` выше совпадение оказывается делегированным: - -- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` -- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` -- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` -- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` - -4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. - -```bash -ADD_KEY_RECEIPT_ID="$( - jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | .receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) - | .receipt.receipt_id - ' /tmp/key-origin-transactions.json | head -n 1 -)" - -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receipt_block_height: .receipt.block_height, - tx_block_height: .receipt.tx_block_height, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - transaction_hash: .receipt.transaction_hash - }' + elif $permission.FunctionCall.receiver_id != $target_contract_id then + { + can_call_now: false, + reason: "receiver_mismatch", + receiver_id: $permission.FunctionCall.receiver_id + } + elif ( + ($permission.FunctionCall.method_names | length) == 0 + or ($permission.FunctionCall.method_names | index($target_method_name)) + ) then + { + can_call_now: true, + reason: ( + if ($permission.FunctionCall.method_names | length) == 0 + then "function_call_any_method" + else "function_call_method_match" + end + ), + allowance: ($permission.FunctionCall.allowance // "unlimited") + } + else + { + can_call_now: false, + reason: "method_not_allowed", + allowed_methods: $permission.FunctionCall.method_names + } + end' ``` -Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. +Для примерного ключа `mike.near` выше на 19 апреля 2026 года ответ получается `can_call_now: true`: это function-call-key для `crossword.puzzle.near`, а `method_names: ["new_puzzle"]` явно разрешает тот метод, который мы проверяем. **Зачем нужен следующий шаг?** -Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. +`view_access_key_list` — самый быстрый фильтр на уровне контракта. `view_access_key` — точная проверка полномочий для того public key, которым вы действительно хотите пользоваться. Если ответ `false`, вам нужен другой ключ или другая схема permissions, а не более глубокая историческая трассировка. -### Проверить регистрацию FT storage и затем перевести токены +### Нужно ли этому получателю сначала зарегистрировать FT storage? -Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». +Используйте этот сценарий, когда история звучит так: «я собираюсь отправить FT-токен и хочу получить простой ответ “нужен ли сначала `storage_deposit`?”».
Стратегия -

Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу.

+

Сначала прочитайте storage-состояние получателя и остановитесь, как только станет понятно, может ли `ft_transfer` уже пройти.

01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас.

02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит.

-

03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог.

+

03jq превращает эти два чтения в один ответ: «перевод уже может идти» или «сначала нужен `storage_deposit`».

@@ -763,24 +310,19 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) - [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) -В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. +В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Здесь важен именно read-only-ответ: нужен ли сначала `storage_deposit`, или путь перевода уже может продолжаться. **Что вы делаете** - Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. -- При необходимости получаете минимальный размер storage deposit. -- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. -- Подтверждаете баланс получателя тем же view-методом самого контракта. +- Получаете точный минимальный размер storage deposit на этом же контракте. +- Останавливаетесь, как только понимаете: `ft_transfer` уже может идти или сначала нужен `storage_deposit`. ```bash export NETWORK_ID=testnet export RPC_URL=https://rpc.testnet.fastnear.com export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet -export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' -export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' -export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' ``` 1. Проверьте, зарегистрирован ли получатель на FT-контракте. @@ -816,7 +358,7 @@ jq '{ }' /tmp/ft-storage-balance.json ``` -2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. +2. Получите минимальный storage deposit на этом же контракте. ```bash MIN_STORAGE_YOCTO="$( @@ -841,215 +383,39 @@ MIN_STORAGE_YOCTO="$( printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" ``` -3. Определите одну переиспользуемую функцию подписи для function-call к контракту. - -Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. - -```bash -sign_function_call() { - METHOD_NAME="$1" \ - ARGS_JSON="$2" \ - DEPOSIT_YOCTO="$3" \ - GAS_TGAS="$4" \ - node --input-type=module <<'EOF' -import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; - -const { - NETWORK_ID = 'testnet', - RPC_URL = 'https://rpc.testnet.fastnear.com', - TOKEN_CONTRACT_ID, - SENDER_ACCOUNT_ID, - SENDER_PUBLIC_KEY, - SENDER_PRIVATE_KEY, - METHOD_NAME, - ARGS_JSON, - DEPOSIT_YOCTO = '0', - GAS_TGAS = '100', -} = process.env; - -for (const name of [ - 'TOKEN_CONTRACT_ID', - 'SENDER_ACCOUNT_ID', - 'SENDER_PUBLIC_KEY', - 'SENDER_PRIVATE_KEY', - 'METHOD_NAME', - 'ARGS_JSON', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); -const signer = await InMemorySigner.fromKeyPair( - NETWORK_ID, - SENDER_ACCOUNT_ID, - keyPair -); - -const derivedPublicKey = keyPair.getPublicKey().toString(); -if (derivedPublicKey !== SENDER_PUBLIC_KEY) { - throw new Error( - `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: SENDER_ACCOUNT_ID, - public_key: SENDER_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const action = transactions.functionCall( - METHOD_NAME, - Buffer.from(ARGS_JSON), - BigInt(GAS_TGAS) * 10n ** 12n, - BigInt(DEPOSIT_YOCTO) -); - -const transaction = transactions.createTransaction( - SENDER_ACCOUNT_ID, - utils.PublicKey.fromString(SENDER_PUBLIC_KEY), - TOKEN_CONTRACT_ID, - BigInt(accessKey.nonce) + 1n, - [action], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - SENDER_ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -} -``` - -4. При необходимости сначала зарегистрируйте storage для получателя. +3. Превратите эти два чтения в один ответ о готовности перевода. ```bash -if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then - SIGNED_TX_BASE64="$( - sign_function_call \ - storage_deposit \ - "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id, - registration_only: true - }')" \ - "$MIN_STORAGE_YOCTO" \ - 100 - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash - }' -fi -``` - -5. После готовности storage переведите FT. - -```bash -SIGNED_TX_BASE64="$( - sign_function_call \ - ft_transfer \ - "$(jq -nc \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ - receiver_id: $receiver_id, - amount: $amount, - memo: "FastNear RPC example" - }')" \ - 1 \ - 100 -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Подтвердите FT-баланс получателя тем же view-методом контракта. - -```bash -RECEIVER_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - receiver_balance: (.result.result | implode | fromjson) +jq -n \ + --slurpfile balance /tmp/ft-storage-balance.json \ + --slurpfile bounds /tmp/ft-storage-bounds.json \ + --arg receiver_account_id "$RECEIVER_ACCOUNT_ID" ' + ( + $balance[0].result.result + | if length == 0 then null else (implode | fromjson) end + ) as $storage + | ( + $bounds[0].result.result + | implode + | fromjson + ) as $bounds + | { + receiver_account_id: $receiver_account_id, + receiver_registered: ($storage != null), + current_storage: $storage, + minimum_storage_deposit_yocto: $bounds.min, + next_step: ( + if $storage != null + then "получатель уже зарегистрирован; ft_transfer может продолжаться" + else "сначала отправьте storage_deposit, потом делайте ft_transfer" + end + ) }' ``` **Зачем нужен следующий шаг?** -Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +Это чистый RPC-вопрос в этом workflow: «зарегистрирован ли уже получатель и какой минимальный депозит потребует контракт, если нет?» Подписанный write-path зависит уже от вашего wallet, CLI или backend-интеграции, поэтому в самый маленький core RPC-пример он не входит. ## Чтения контракта и сырое состояние @@ -1057,47 +423,12 @@ curl -s "$RPC_URL" \ ### Прочитать счётчик прямо из состояния контракта, а потом подтвердить его через view-метод -Используйте этот сценарий, когда история простая: «я знаю, что этот контракт держит счётчик, но можно ли прочитать это число напрямую из storage, не вызывая код контракта?» - -В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: оба чтения должны совпасть в тот момент, когда вы их запускаете: - -- `view_state` читает сырой ключ `STATE` прямо из storage контракта -- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API - -
-
- Стратегия -

Сначала прочитайте raw storage, затем декодируйте байты, а потом дайте контракту подтвердить тот же ответ через view-метод.

-
-
-

01RPC view_state читает сырой ключ STATE, не запуская код контракта.

-

02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта.

-

03RPC call_function get_num — это удобная перепроверка того, что raw-state-чтение и view-метод по-прежнему дают один и тот же ответ.

-
-
- -Здесь важнее ментальная модель, чем сам счётчик: - -- `view_state` — это прямое чтение storage из trie -- `call_function` исполняет read-only-метод контракта -- оба способа могут ответить на один и тот же вопрос, но делают разную работу - -```mermaid -flowchart LR - S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] - R --> D["Декодировать base64 + Borsh"] - D --> N["Знаковое значение счётчика"] - C["RPC call_function get_num"] --> J["JSON-результат метода"] - N --> X["Сравнить"] - J --> X - X --> A["Одно и то же текущее значение"] -``` +Используйте этот сценарий, когда вы уже знаете точное семейство storage-ключей и хотите увидеть самый короткий контраст между raw state и публичным методом чтения контракта. -**Что вы делаете** +Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`: -- Читаете сырой ключ `STATE` из storage контракта. -- Декодируете возвращённые байты в текущее знаковое значение счётчика. -- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. +- `view_state` читает сырой ключ `STATE` напрямую +- `call_function get_num` спрашивает у контракта то же текущее число ```bash export NETWORK_ID=testnet @@ -1106,7 +437,7 @@ export CONTRACT_ID=counter.near-examples.testnet export STATE_PREFIX_BASE64=U1RBVEU= ``` -1. Сначала прочитайте сырое состояние контракта. +1. Сначала прочитайте сырой ключ `STATE`. ```bash curl -s "$RPC_URL" \ @@ -1127,49 +458,30 @@ curl -s "$RPC_URL" \ | tee /tmp/counter-view-state.json >/dev/null jq '{ - block_height: .result.block_height, - key_base64: .result.values[0].key, + key: (.result.values[0].key | @base64d), value_base64: .result.values[0].value }' /tmp/counter-view-state.json - -jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json ``` -Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. +Здесь должен появиться `key: "STATE"`. Это и есть тот случай, когда `view_state` уместен: семейство ключей вам уже известно заранее. -2. Декодируйте байты значения в знаковое число счётчика. +2. Декодируйте сырые байты. ```bash RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" -python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . +python3 - "$RAW_VALUE_BASE64" <<'PY' import base64 -import json import sys raw = base64.b64decode(sys.argv[1]) - -print(json.dumps({ - "value_base64": sys.argv[1], - "bytes": list(raw), - "hex": raw.hex(), - "signed_i8": int.from_bytes(raw, "little", signed=True), - "unsigned_u8": int.from_bytes(raw, "little", signed=False), -})) +print(int.from_bytes(raw, "little", signed=True)) PY ``` -Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. - -Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. - -Переиспользуемый рецепт здесь короткий: +Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть сложнее, но правило то же: сначала байты, потом схема. -- `view_state` возвращает сырые байты в base64 -- вы декодируете эти байты по известной схеме хранения контракта -- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема - -3. Теперь спросите контракт более привычным способом и сравните. +3. Теперь спросите контракт привычным способом и сравните. ```bash curl -s "$RPC_URL" \ @@ -1194,7 +506,7 @@ jq '{ }' /tmp/counter-call-function.json ``` -4. Сравните оба ответа напрямую. +4. Сравните оба ответа. ```bash RAW_STATE_NUMBER="$( @@ -1220,93 +532,53 @@ jq -n \ }' ``` -Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: - -- `view_state` ответил на вопрос, прочитав storage напрямую -- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта - **Зачем нужен следующий шаг?** -Используйте `view_state`, когда настоящий вопрос относится к точному storage и вы уже знаете семейство ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](/fastdata/kv). +Используйте `view_state`, когда вы уже знаете точное семейство storage-ключей и хотите raw bytes. Используйте `call_function`, когда вам нужен публичный метод чтения самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда уже стоит расширяться в [KV FastData API](/fastdata/kv). -## Точные чтения NEAR Social и BOS +## Точные чтения SocialDB -Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. +Оставайтесь на точных чтениях через `call_function get`, когда вы уже знаете нужный ключ SocialDB. На обычном RPC raw `view_state` для `social.near` не подходит как обучающий путь, потому что состояние контракта слишком велико для прямого чтения. -### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? +### Прочитать один пост SocialDB ровно в том виде, как он хранится сейчас -Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». +Используйте этот сценарий, когда продукту, support-инструменту или агенту уже известен аккаунт и нужен живой payload поста из SocialDB без перехода в историю транзакций.
Стратегия -

Спросите у social.near ровно о двух вещах, которые важны до подписи.

+

Сначала прочитайте текущий ключ поста, затем получите точный payload этого поста из social.near.

-

01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию.

-

02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near.

-

03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer.

+

01RPC call_function get по mike.near/index/post показывает, какой ключ поста сейчас активен.

+

02RPC call_function get по mike.near/post/main возвращает точный сохранённый payload поста.

+

03Если следующий вопрос становится «какая транзакция это записала?», переключайтесь на [Transactions Examples](/tx/examples).

-Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: - -- есть ли у целевого аккаунта storage на `social.near`? -- если есть, осталось ли там ещё место? -- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? - **Официальные ссылки** - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) **Что вы делаете** -- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. -- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. -- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. -- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». +- Читаете текущий указатель поста под `mike.near/index/post`. +- Используете этот ключ, чтобы получить точный payload по `mike.near/post/`. +- Останавливаетесь на точном JSON и расширяетесь в историю только тогда, когда действительно нужна provenance. ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com export SOCIAL_CONTRACT_ID=social.near export ACCOUNT_ID=mike.near -export SIGNER_ACCOUNT_ID=mike.near -``` - -1. Сначала проверьте сам аккаунт signer. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } - }')" \ - | tee /tmp/social-publish-signer.json >/dev/null - -jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - signer_account_id: $signer_account_id, - amount: .result.amount, - locked: .result.locked, - storage_usage: .result.storage_usage -}' /tmp/social-publish-signer.json ``` -Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. - -2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. +1. Сначала прочитайте текущий указатель поста. ```bash -SOCIAL_STORAGE_ARGS_BASE64="$( +INDEX_POST_ARGS_BASE64="$( jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id + keys: [($account_id + "/index/post")] }' | base64 | tr -d '\n' )" @@ -1314,213 +586,53 @@ curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + --arg args_base64 "$INDEX_POST_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { request_type: "call_function", account_id: $account_id, - method_name: "get_account_storage", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-account-storage.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - storage: (.result.result | implode | fromjson), - storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) -}' /tmp/social-account-storage.json -``` - -Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. - -3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. - -```bash -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - jq -n --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - signer_matches_target: true, - permission_granted: true, - reason: "owner write" - }' -else - WRITE_PERMISSION_ARGS_BASE64="$( - jq -nc \ - --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ - --arg key "$ACCOUNT_ID" '{ - predecessor_id: $predecessor_id, - key: $key - }' | base64 | tr -d '\n' - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - signer_matches_target: false, - permission_granted: (.result.result | implode | fromjson) - }' -fi -``` - -4. Сведите проверку storage и разрешения в один читаемый итог. - -```bash -AVAILABLE_BYTES="$( - jq -r ' - .result.result - | if length == 0 then "0" - else (implode | fromjson | .available_bytes // 0 | tostring) - end - ' /tmp/social-account-storage.json -)" - -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - PERMISSION_GRANTED=true -else - PERMISSION_GRANTED="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq -r '.result.result | implode | fromjson' - )" -fi - -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ - --argjson available_bytes "$AVAILABLE_BYTES" \ - --argjson permission_granted "$PERMISSION_GRANTED" '{ - account_id: $account_id, - signer_account_id: $signer_account_id, - storage_ready: ($available_bytes > 0), - permission_ready: $permission_granted, - ready_to_publish_now: (($available_bytes > 0) and $permission_granted) - }' -``` - -Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. - -**Зачем нужен следующий шаг?** - -Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. - -### Что прямо сейчас содержит `mob.near/widget/Profile`? - -Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». - -
-
- Стратегия -

Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой.

-
-
-

01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*.

-

02RPC call_function get читает точный исходник widget/Profile.

-

03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples.

-
-
- -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Что вы делаете** - -- Спрашиваете у `social.near` каталог виджетов под `mob.near`. -- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. -- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. -- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](/tx/examples). - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mob.near -export WIDGET_NAME=Profile -``` - -1. Получите каталог виджетов и сохраните высоты блоков последней записи. - -```bash -WIDGET_KEYS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/widget/*")], - options: {return_type: "BlockHeight"} - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "keys", + method_name: "get", args_base64: $args_base64, finality: "final" } }')" \ - | tee /tmp/social-widget-keys.json >/dev/null + | tee /tmp/social-index-post.json >/dev/null jq --arg account_id "$ACCOUNT_ID" ' .result.result | implode | fromjson - | .[$account_id].widget - | to_entries - | sort_by(.value * -1) - | map({ - widget_name: .key, - last_write_block: .value - }) - | .[0:20] -' /tmp/social-widget-keys.json + | { + account_id: $account_id, + index_entry: (.[$account_id].index.post | fromjson), + current_post_key: (.[$account_id].index.post | fromjson | .key) + } +' /tmp/social-index-post.json ``` -2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. +На момент написания текущим ключом поста для `mike.near` был `main`. + +2. Прочитайте точный payload этого поста. ```bash -WIDGET_GET_ARGS_BASE64="$( +POST_KEY="$( + jq -r --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].index.post + | fromjson + | .key + ' /tmp/social-index-post.json +)" + +POST_ARGS_BASE64="$( jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] + --arg post_key "$POST_KEY" '{ + keys: [($account_id + "/post/" + $post_key)] }' | base64 | tr -d '\n' )" @@ -1528,7 +640,7 @@ curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + --arg args_base64 "$POST_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", @@ -1540,131 +652,26 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/social-widget-source.json >/dev/null - -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:25] - | join("\n") - ' /tmp/social-widget-source.json -``` - -3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. + | tee /tmp/social-post-main.json >/dev/null -```bash -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - ' /tmp/social-widget-keys.json \ - | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +jq --arg account_id "$ACCOUNT_ID" --arg post_key "$POST_KEY" ' + .result.result + | implode + | fromjson + | { + account_id: $account_id, + post_key: $post_key, + post: (.[$account_id].post[$post_key] | fromjson) + } +' /tmp/social-post-main.json ``` -На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. +Так вы получаете точный JSON, который хранится для текущего поста, включая поля вроде `type`, `text` и `image`. **Зачем нужен следующий шаг?** -Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. - -## Частые задачи - -### Проверить точное состояние аккаунта или ключа доступа - -**Начните здесь** - -- [View Account](/rpc/account/view-account) для точных полей аккаунта. -- [View Access Key](/rpc/account/view-access-key) или [View Access Key List](/rpc/account/view-access-key-list) для проверки ключей. - -**Следующая страница при необходимости** - -- [FastNear API full account view](/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. -- [Transactions API account history](/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» - -**Остановитесь, когда** - -- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. - -**Переходите дальше, когда** - -- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. -- Пользователя интересует не текущее состояние, а недавняя история активности. - -### Проверить один точный блок или снимок состояния протокола - -**Начните здесь** - -- [Block by ID](/rpc/block/block-by-id) или [Block by Height](/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. -- [Latest Block](/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» -- [Status](/rpc/protocol/status), [Health](/rpc/protocol/health) или [Network Info](/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. - -**Следующая страница при необходимости** - -- [Block Effects](/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. -- [Transactions API block history](/tx/block) или [Transactions API block range](/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» - -**Остановитесь, когда** - -- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. - -**Переходите дальше, когда** - -- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](/neardata). -- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](/tx). - -### Что этот контракт возвращает прямо сейчас? - -**Начните здесь** - -- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» -- [Call Function](/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. -- [View State](/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. -- [View Code](/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» - -**Следующая страница при необходимости** - -- [FastNear API](/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. -- [KV FastData API](/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» - -**Остановитесь, когда** - -- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. - -**Переходите дальше, когда** - -- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. -- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» - -### Отправить транзакцию и подтвердить результат - -**Начните здесь** - -- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. -- [Send Transaction](/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. -- [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. -- [Transaction Status](/rpc/transaction/tx-status), чтобы подтвердить финальный результат. - -**Следующая страница при необходимости** - -- [Transactions by Hash](/tx/transactions), если после отправки нужна более читаемая история по транзакции. -- [Receipt Lookup](/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. -- [Transactions Examples](/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» - -**Остановитесь, когда** - -- У вас уже есть результат отправки и нужный финальный статус. - -**Переходите дальше, когда** +Это чистый RPC-паттерн для SocialDB: спросите у контракта один точный ключ, декодируйте возвращённый JSON и остановитесь. Если вопрос превращается в «кто и когда это записал?», переходите к примерам по транзакциям, а не пытайтесь brute-force читать raw state `social.near`. -- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. -- Нужен уже не единичный статус, а более широкий сценарий расследования. ## Частые ошибки diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx index 8c35b98..e15c556 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx @@ -12,6 +12,8 @@ page_actions: Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. +Эти helper-скрипты поддерживаются FastNear и оптимизированы под скорость восстановления. Если в вашей среде нужен review изменений, сначала скачайте скрипт, проверьте его и только потом запускайте, вместо прямого piping в `bash`. + ```bash DATA_PATH=~/.near/data @@ -108,79 +110,6 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. -## Частые задачи - -### Поднять optimized `fast-rpc`-узел в mainnet - -**Начните здесь** - -- Используйте якорь с optimized mainnet `fast-rpc` выше. - -**Следующая страница при необходимости** - -- [Снапшоты mainnet](/snapshots/mainnet), если нужно дополнительно настроить `THREADS`, `BWLIMIT` или кастомный `DATA_PATH`. - -**Остановитесь, когда** - -- Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. - -**Переходите дальше, когда** - -- На самом деле требуется архивное хранение, а не просто быстрый запуск. - -### Восстановить обычный RPC-узел в стандартный каталог nearcore - -**Начните здесь** - -- Используйте якорь со стандартным mainnet RPC выше. - -**Следующая страница при необходимости** - -- [Снапшоты mainnet](/snapshots/mainnet), если нужно дополнительно настроить `DATA_PATH`, `THREADS` или ограничения по пропускной способности. - -**Остановитесь, когда** - -- Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. - -**Переходите дальше, когда** - -- Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. - -### Правильно поднять архивные hot- и cold-данные mainnet - -**Начните здесь** - -- Используйте архивный walkthrough выше. - -**Следующая страница при необходимости** - -- Сначала получите последнюю высоту архивного снапшота, затем запускайте отдельные загрузки hot- и cold-данных с правильными путями. - -**Остановитесь, когда** - -- План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. - -**Переходите дальше, когда** - -- Оператору нужны уже общие гайды nearcore по bootstrap, а не только FastNear snapshots. - -### Поднять архивные hot-данные в testnet - -**Начните здесь** - -- [Снапшоты testnet](/snapshots/testnet), раздел архивного режима. - -**Следующая страница при необходимости** - -- Получите последнюю высоту архивного снапшота testnet перед шагом загрузки. - -**Остановитесь, когда** - -- Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. - -**Переходите дальше, когда** - -- Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. ## Частые ошибки diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md index c26496c..c8b15ba 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -51,27 +51,27 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ ## Готовый сценарий -### Найти один подозрительный перевод, а затем пройти по его receipt +### Найти один исходящий перевод и при необходимости перейти к деталям исполнения -Используйте этот сценарий, когда история звучит так: «я вижу, что средства двигались, но хочу получить точную опорную точку исполнения для этого движения, не затягивая сразу всю историю аккаунта». +Используйте этот сценарий, когда история звучит так: «я знаю, что этот аккаунт отправлял средства в этом окне, и мне может понадобиться точная опорная точка исполнения для одной строки, но я не хочу сразу тянуть всю историю аккаунта».
Стратегия -

Сначала оставайтесь на узкой истории движения, а затем один раз переключайтесь в историю исполнения.

+

Сначала оставайтесь на узкой истории движения, а затем переходите в историю исполнения только если строки перевода уже недостаточно.

01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять.

-

02jq поднимает один receipt_id, не затягивая остальную историю аккаунта.

-

03POST /v0/receipt превращает это движение в опорную точку исполнения, которую уже можно продолжать в /tx.

+

02Сначала выведите строки, а затем явно выберите один transfer_index перед тем, как поднимать его receipt_id.

+

03POST /v0/receipt — это необязательное расширение, когда уже нужны детали исполнения именно за этим переводом.

**Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Выделяете один перевод, который действительно похож на нужное вам движение. -- Переиспользуете его `receipt_id` в Transactions API, чтобы перейти от движения актива к истории исполнения. +- Выделяете одну строку перевода, которая действительно похожа на нужное вам движение. +- Переиспользуете его `receipt_id` в Transactions API только если нужно перейти от движения актива к истории исполнения. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com @@ -79,113 +79,66 @@ TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=YOUR_ACCOUNT_ID FROM_TIMESTAMP_MS=1711929600000 TO_TIMESTAMP_MS=1712016000000 +TRANSFER_INDEX=0 -RECEIPT_ID="$( - curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ - account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, - desc: true, - limit: 10 - }')" \ - | tee /tmp/transfers-window.json \ - | jq -r '.transfers[0].receipt_id' -)" +curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | tee /tmp/transfers-window.json >/dev/null jq '{ resume_token, transfers: [ - .transfers[] + .transfers + | to_entries[] | { - transaction_id, - receipt_id, - asset_id, - amount, - other_account_id, - block_height + transfer_index: .key, + transaction_id: .value.transaction_id, + receipt_id: .value.receipt_id, + asset_id: .value.asset_id, + amount: .value.amount, + other_account_id: .value.other_account_id, + block_height: .value.block_height } ] }' /tmp/transfers-window.json -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' -``` - -**Зачем нужен следующий шаг?** - -Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` даёт точную опорную точку в исполнении, не затягивая вас сразу в полную историю аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. - -## Частые задачи - -### Найти исходящие переводы одного аккаунта в узком окне времени - -**Начните здесь** - -- [Запрос переводов](/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. - -**Следующая страница при необходимости** - -- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. - -**Остановитесь, когда** - -- Уже можно ответить, кто что отправил, когда и в каком активе. - -**Переходите дальше, когда** - -- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](/tx). - -### Листать ленту переводов дальше и не потерять своё место - -**Начните здесь** - -- [Запрос переводов](/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. - -**Следующая страница при необходимости** - -- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. -- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. - -**Остановитесь, когда** - -- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. - -**Переходите дальше, когда** - -- Пользователь просит метаданные транзакции сверх самих переводов. -- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](/api). - -### Перейти от истории переводов к полному расследованию транзакции - -**Начните здесь** - -- [Запрос переводов](/transfers/query), чтобы выделить конкретные интересующие переводы. - -**Следующая страница при необходимости** +RECEIPT_ID="$( + jq -r --argjson transfer_index "$TRANSFER_INDEX" \ + '.transfers[$transfer_index].receipt_id // empty' \ + /tmp/transfers-window.json +)" -- [История аккаунта в Transactions API](/tx/account), если нужна окружающая история исполнения для того же аккаунта. -- [Transactions by Hash](/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. +printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" +printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" -**Остановитесь, когда** +if [ -n "$RECEIPT_ID" ]; then + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' +fi +``` -- Уже определено правильное событие перевода и понятно, какой API открывать следующим. +**Зачем нужен следующий шаг?** -**Переходите дальше, когда** +Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный следующий шаг, когда самой строки перевода уже недостаточно и нужна опорная точка в истории исполнения. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. -- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](/tx), затем к [RPC Reference](/rpc), если потребуется. ## Частые ошибки diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx index 32e7684..8651bdd 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx @@ -1,8 +1,8 @@ --- -sidebar_label: Berry Club +sidebar_label: Berry Club Case Study slug: /tx/examples/berry-club -title: "Berry Club: как восстанавливать исторические доски" -description: "Используйте Transactions API, RPC get_lines и проигрывание draw-вызовов, чтобы восстанавливать доски Berry Club по историческим эпохам." +title: "Berry Club Case Study: как читать живую доску и разбирать одну эпоху" +description: "Case study, который начинается с живой доски Berry Club через RPC get_lines, а затем использует Transactions API, чтобы восстановить одну более раннюю эпоху." displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -14,138 +14,71 @@ keywords: - get_lines - draw - pixel board - - historical snapshots --- import Link from '@site/src/components/LocalizedLink'; -import BerryClubSnapshotGallery from '@site/src/components/BerryClubSnapshotGallery'; -import berryClubSnapshots from '@site/src/data/berryClubSnapshots.json'; +import BerryClubLiveBoard from '@site/src/components/BerryClubLiveBoard'; -{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как восстанавливать доски Berry Club через FastNear. Он отделяет текущее состояние из get_lines от исторического разбора через диапазоны блоков, историю аккаунта, раскрытие транзакций и проигрывание draw-аргументов. */} +{/* FASTNEAR_AI_DISCOVERY: Этот case study показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} -# Berry Club: как восстанавливать исторические доски +# Berry Club Case Study: как читать живую доску и разбирать одну эпоху -Используйте этот разбор, когда вопрос звучит так: «как Berry Club выглядел в определённую эпоху и какие `draw`-вызовы сделали доску именно такой?» +Используйте этот case study, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. -Это read-only разбор из семейства Transactions examples. Если нужна только доска прямо сейчас, используйте `get_lines` и остановитесь. Если нужно объяснить, как доска пришла к такому виду, переключайтесь на историю блоков, историю аккаунта, раскрытые `draw`-вызовы и проигрывание. +Начните с живой доски. Если этого уже достаточно для ответа, на этом можно остановиться. -
-
- Стратегия -

Сначала прочитайте живую доску, затем ограничьте эпоху и только после этого проигрывайте draw-вызовы, которые её объясняют.

-
-
-

01RPC call_function get_lines даёт текущую доску 50x50 и показывает, как выглядит «сейчас».

-

02POST /v0/blocks вместе с POST /v0/account ограничивают одну эпоху и дают кандидатные хеши draw.

-

03POST /v0/transactions раскрывает эти draw-вызовы, чтобы их можно было проиграть в исторические контрольные точки.

-
-
+Переходите к Transactions API только тогда, когда вопрос становится историческим: «как Berry Club выглядел в одной более ранней эпохе и какие `draw`-вызовы сделали доску именно такой?» -Держите рядом: - -- [js.fastnear.com](https://js.fastnear.com/) -- [fastnear/js-monorepo](https://github.com/fastnear/js-monorepo) -- Transactions API: история аккаунта -- Transactions API: транзакции по хешу -- Transactions API: диапазон блоков -- RPC: call_function - -В этом руководстве история Berry Club разбирается только на mainnet. Снимки ниже собраны из воспроизводимых данных mainnet, которые уже сохранены в этом репозитории. - -## Короткая версия - -Berry Club даёт чистый view текущего состояния через `get_lines`, но не даёт готового эндпоинта вида «доска на блоке N». - -Из-за этого задача делится на две части: - -- используйте RPC `call_function`, когда вопрос звучит как «как доска выглядит сейчас?» -- используйте индексированную историю, когда вопрос звучит как «какие записи привели к этой доске?» -- используйте архивный RPC только тогда, когда нужно напрямую материализовать уже известную контрольную точку - -```mermaid -flowchart TD - A["RPC call_function: get_lines"] --> B["Текущая доска 50x50"] - C["Transactions API: /v0/blocks"] --> D["Ограничить эпоху"] - D --> E["/v0/account для berryclub.ek.near"] - E --> F["Кандидатные хеши draw-транзакций"] - F --> G["Раскрытие через /v0/transactions"] - G --> H["Проигрывание draw-записей в историческую доску"] -``` - -## Почему Berry Club хорошо учит истории в NEAR - -Berry Club удобно показывает обе стороны задачи: - -- чистое чтение текущего состояния через `get_lines` -- длинную историю вызовов `draw` с обычными аргументами `FunctionCall` -- формат доски, который легко декодировать и рендерить обычным JavaScript + -Это очень NEAR-подобная форма: один view-метод для текущего состояния, один write-метод для изменений и индексированная история, когда нужно объяснить, как это состояние вообще появилось. +## 1. Прочитайте живую доску -## 1. Сначала прочитайте текущую доску +Это самый короткий полезный запрос: -Живое демо использует `berryclub.ek.near` и читает доску через view-вызов `get_lines`: +```bash +ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" -```javascript -await near.view({ - contractId: 'berryclub.ek.near', - methodName: 'get_lines', - args: { - lines: [...Array(50).keys()], - }, -}); +curl -sS https://rpc.mainnet.fastnear.com \ + -H 'content-type: application/json' \ + --data "{ + \"jsonrpc\": \"2.0\", + \"id\": \"berry-live-board\", + \"method\": \"query\", + \"params\": { + \"request_type\": \"call_function\", + \"finality\": \"final\", + \"account_id\": \"berryclub.ek.near\", + \"method_name\": \"get_lines\", + \"args_base64\": \"$ARGS_BASE64\" + } + }" | jq '.result | {block_height, line_count: (.result | implode | fromjson | length)}' ``` -Это путь текущего состояния. Он не отвечает на вопрос, как доска пришла к такому виду. +Этот запрос отдаёт текущую доску 50x50 прямо из контракта. Дальше нужно только декодировать каждую base64-строку в 50 цветов пикселей. -| Вопрос | Лучшая поверхность | Почему | -| --- | --- | --- | -| как доска выглядит сейчас? | RPC `call_function` | контракт уже отдаёт текущее состояние через `get_lines` | -| какие `draw` были в этой эпохе? | `/v0/account` + `/v0/transactions` | индексированная история даёт ограниченный набор записей и раскрытые аргументы | -| как доска выглядела в известной контрольной точке? | архивный RPC или полное проигрывание | можно напрямую материализовать состояние из архива или восстановить его самому по историческим записям | +## 2. Восстановите одну более раннюю эпоху -## 2. Как декодировать `get_lines` в сетку 50x50 +Когда нужна история, держите путь коротким: -Полезная часть Berry Club-разметки из `js.fastnear.com` — это декодер строк: +1. ограничьте одну эпоху +2. получите кандидатные `draw`-транзакции для `berryclub.ek.near` +3. раскройте эти хеши +4. проиграйте массивы `pixels` от старых к новым -- каждая строка приходит в base64 -- её нужно декодировать в байты -- первые 4 байта нужно пропустить -- дальше цвета читаются как 32-битные little-endian значения каждые 8 байт - -```javascript -function decodeLine(encodedLine) { - const bytes = Buffer.from(encodedLine, 'base64'); - const colors = []; - - for (let offset = 4; offset < bytes.length; offset += 8) { - colors.push(bytes.readUInt32LE(offset) & 0xffffff); - } - - return colors; -} -``` - -Примените это ко всем 50 строкам — и получите полную сетку 50x50, готовую к рендерингу. - -## 3. Ограничьте эпоху, которую хотите изучить - -Сначала ограничьте эпоху, прежде чем искать draw-записи. Проверочный снимок запуска в этом репозитории находится на блоке `21898354`, а средний снимок — на блоке `97601515`. - -Сначала зафиксируйте ближайший диапазон блоков: - -```bash -curl -sS https://tx.main.fastnear.com/v0/blocks \ - -H 'content-type: application/json' \ - --data '{ - "from_block_height": 21898350, - "to_block_height": 21898355, - "desc": false, - "limit": 5 - }' -``` - -Затем переключитесь на историю аккаунта и запросите активность Berry Club внутри ограниченного диапазона блоков: +В этом примере используется узкое окно вокруг блока `97601515`: ```bash curl -sS https://tx.main.fastnear.com/v0/account \ @@ -157,21 +90,14 @@ curl -sS https://tx.main.fastnear.com/v0/account \ "is_real_receiver": true, "from_tx_block_height": 97576515, "to_tx_block_height": 97601516, - "desc": true, - "limit": 40 - }' + "desc": false, + "limit": 200 + }' | jq '.account_txs | map({transaction_hash, tx_block_height}) | .[-5:]' ``` -Здесь полезна именно такая последовательность: - -- `/v0/blocks` помогает понять соседство по высотам блоков -- `/v0/account` возвращает кандидатные хеши транзакций Berry Club внутри этого диапазона - -## 4. Раскройте транзакции и оставьте только `draw` - -Когда кандидатные хеши уже есть, раскройте их и оставьте только верхнеуровневые вызовы `draw`, где получатель — `berryclub.ek.near`. +Если окно ещё нужно подобрать, сначала можно использовать `/v0/blocks`. Это не часть основного Berry Club-сценария. -Аргументы вызова — это обычные данные `FunctionCall` вида `{ pixels: [...] }`: +Раскройте кандидатные хеши и оставьте только верхнеуровневые вызовы `draw`: ```bash curl -sS https://tx.main.fastnear.com/v0/transactions \ @@ -185,80 +111,29 @@ curl -sS https://tx.main.fastnear.com/v0/transactions \ | select(.transaction.receiver_id == "berryclub.ek.near") | .transaction.actions[]?.FunctionCall | select(.method_name == "draw") - | { - method_name, - args: (.args | @base64d | fromjson) - }' + | {method_name, args: (.args | @base64d | fromjson)}' ``` -Это даёт всё, что нужно для проигрывания: - -- какая транзакция записывала пиксели -- какие координаты были затронуты -- какие цвета были записаны - -## 5. Проиграйте исторические `draw`-вызовы в доску - -Для полного проигрывания держите в памяти массив 50x50 и применяйте раскрытые транзакции `draw` от старых к новым. +Затем проиграйте массивы `pixels` от старых к новым: ```javascript const board = Array.from({ length: 50 }, () => Array(50).fill(0)); -function applyDraw(boardState, drawArgs) { - for (const pixel of drawArgs.pixels) { +for (const drawTx of drawTransactionsOldestFirst) { + for (const pixel of drawTx.args.pixels) { if (pixel.x < 0 || pixel.x >= 50 || pixel.y < 0 || pixel.y >= 50) { continue; } - boardState[pixel.y][pixel.x] = pixel.color; + board[pixel.y][pixel.x] = pixel.color; } } - -for (const drawTx of drawTransactionsOldestFirst) { - applyDraw(board, drawTx.args); -} ``` -Важно не путать два разных пути: - -- `get_lines` — это текущее состояние -- `tx/account` плюс `tx/transactions` — это материал для проигрывания +В этом и состоит исторический паттерн. У Berry Club нет готового эндпоинта «доска на блоке N», поэтому старые эпохи восстанавливаются проигрыванием `draw`-записей. -## 6. Готовые контрольные точки по эпохам +## Связанные руководства -Галерея ниже использует уже сохранённые данные снимков, собранные из mainnet-истории Berry Club: - -- `launch` — последний успешный `draw` в пределах первых 24 часов после первого успешного draw -- `mid` — последний успешный `draw` не позже средней временной точки всей истории Berry Club -- `recent` — последний успешный `draw`, который увидел скрипт при пересборке снимков - - - -Сейчас эти снимки привязаны к таким транзакциям: - -- `launch`: `BDNFpCpLXjBrgjR6z6wCZmB9EWdHnVMdqau3iTWTRE5H` на блоке `21898354` -- `mid`: `Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ` на блоке `97601515` -- `recent`: `8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL` на блоке `194588754` - -## Куда идти за подписанными взаимодействиями - -Эта страница должна оставаться в режиме чтения. - -Если нужны живые подписанные сценарии для `draw` и `buy_tokens`, переходите сюда: - -- [js.fastnear.com](https://js.fastnear.com/) -- [Berry Club example в fastnear/js-monorepo](https://github.com/fastnear/js-monorepo/tree/main/examples/static/berryclub) - -Именно там уместны кошелёк и подписанные действия. Эта страница посвящена историческому восстановлению. +- RPC: call_function +- Transactions API: история аккаунта +- Transactions API: транзакции по хешу diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index ff11430..e872ed8 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /tx/examples title: "Примеры Transactions API" -description: "Пошаговые расследования и подробные разборы для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents." +description: "Пошаговые расследования транзакций сначала для типовых задач разработчика, а затем для более глубоких case study, когда они действительно нужны." displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -37,7 +37,7 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. -Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](/tx/examples/outlayer) для трассировки воркера и callback-цепочки. +Если нужен более развёрнутый case study на той же поверхности, переходите к [Berry Club case study](/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer case study](/tx/examples/outlayer) для трассировки воркера и callback-цепочки. ## С чего начать @@ -189,6 +189,140 @@ curl -s "$TX_BASE_URL/v0/receipt" \ `POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. +### Какая receipt выдала этот лог или event? + +Используйте это расследование, когда история звучит так: «у меня есть один tx hash и один фрагмент лога, и мне нужно точно понять, какая именно receipt его выдала». + +Это другой вопрос, чем более поздний сценарий «дошёл ли callback?». Здесь цель проще: привязать одну наблюдаемую строку лога к одному точному `receipt_id`, одному методу и одному исполнителю. + +
+
+ Стратегия +

Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога.

+
+
+

01POST /v0/transactions даёт полный индексированный список receipt для одного tx hash, включая receipt-логи.

+

02jq сужает этот список до receipt, в логах которых встречается нужный вам фрагмент.

+

03Как только совпадение осталось одно, сохраняйте его receipt_id, executor и имя метода как точный ответ.

+
+
+ +**Цель** + +- Начать с одного mainnet tx hash и одного фрагмента лога и определить точную receipt, которая выдала этот лог. + +Для этого зафиксированного mainnet-примера используйте: + +- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` +- фрагмент лога: `Refund` +- ожидаемый matching `receipt_id`: `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` +- ожидаемый executor: `wrap.near` +- ожидаемый метод: `ft_resolve_transfer` + +Эта транзакция полезна тем, что в ней есть две разные logged receipt внутри одной истории: + +- ранний лог `Transfer ...` на receipt с `ft_transfer_call` +- более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` + +```mermaid +flowchart LR + T["Один tx hash
2KhhB1uD..."] --> L["Читаем все receipt-логи"] + L --> X["Ищем фрагмент:
Refund"] + X --> R["Точная receipt
9sLHQpaG..."] + R --> A["Ответ:
wrap.near / ft_resolve_transfer"] +``` + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Атрибуция лога | Transactions API [`POST /v0/transactions`](/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | +| Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | + +**Что должен включать полезный ответ** + +- какой `receipt_id` выдал лог +- какой контракт исполнил эту receipt +- какой метод там выполнился +- точную строку лога, которая совпала +- одно простое предложение вроде «лог `Refund` пришёл из `wrap.near` в receipt с методом `ft_resolve_transfer`» + +#### Shell-сценарий атрибуции лога + +Используйте этот сценарий, когда у вас уже есть tx hash и следующий вопрос звучит как «какая receipt это сказала?» + +**Что вы делаете** + +- Один раз получаете транзакцию и сохраняете список её receipt. +- Фильтруете receipt по одному фрагменту лога. +- Останавливаетесь, как только у вас есть один точный `receipt_id`, один executor и одно имя метода. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +LOG_FRAGMENT=Refund +``` + +1. Получите транзакцию и сохраните список receipt. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/log-attribution-transaction.json >/dev/null +``` + +2. Отфильтруйте список receipt до логов, которые содержат нужный вам фрагмент. + +```bash +jq --arg fragment "$LOG_FRAGMENT" '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id + }, + matching_receipts: [ + .transactions[0].receipts[] + | select(any(.execution_outcome.outcome.logs[]?; contains($fragment))) + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + block_height: .execution_outcome.block_height, + logs: .execution_outcome.outcome.logs + } + ] +}' /tmp/log-attribution-transaction.json + +# На что смотреть: +# - фрагмент `Refund` совпадает ровно с одной receipt +# - это receipt 9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w +# - receipt исполнилась на wrap.near +# - имя метода — ft_resolve_transfer +``` + +3. Если хотите увидеть все logged receipt рядом, распечатайте только те receipt, где вообще были логи. + +```bash +jq '{ + logged_receipts: [ + .transactions[0].receipts[] + | select((.execution_outcome.outcome.logs | length) > 0) + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + logs: .execution_outcome.outcome.logs + } + ] +}' /tmp/log-attribution-transaction.json +``` + +Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. + +**Зачем нужен следующий шаг?** + +Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. + ### Превратить один страшный receipt ID из логов в понятную человеческую историю Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. @@ -246,19 +380,6 @@ flowchart LR #### Shell-сценарий: от страшного receipt ID к человеческой истории -## Ошибки и async - -Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и порядок callback-ов. - -Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. - -**Что вы делаете** - -- Сначала разрешаете receipt. -- Извлекаете `receipt.transaction_hash` через `jq`. -- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. -- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. - ```bash TX_BASE_URL=https://tx.main.fastnear.com RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' @@ -332,6 +453,12 @@ jq -r ' `POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. +## Ошибки и async + +Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. + +Используйте этот раздел, когда уже понятно, что транзакция жила дольше одной receipt, и следующий вопрос относится уже к форме исполнения, а не к простому поиску объекта. + ### Доказать, что одно неудачное действие сорвало весь пакет Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? @@ -687,829 +814,175 @@ jq \ Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. -### Проследить асинхронную promise-цепочку и доказать порядок callback-ов +### Дошёл ли callback вообще? + +Используйте это расследование, когда одна транзакция запустила downstream-работу на другом контракте, а настоящий вопрос звучит не как «успешно ли отработал receiver?», а как «вернулся ли callback обратно в исходный контракт?» -Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» +Это самый короткий полезный сценарий про callback на странице: + +- стартуйте с одного tx hash +- найдите downstream-receipt на другом контракте +- найдите более поздний callback-receipt, который вернулся в исходный контракт +- остановитесь, как только доказаны сам факт callback и его результат
Стратегия -

Смотрите на два хеша как на одну async-историю: докажите, что работа была жива, восстановите запрошенный порядок и сравните его с видимым downstream-state.

+

Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а.

-

01RPC call_function на view отложенной работы доказывает, что promise-работа действительно уже была жива до resume-шага.

-

02POST /v0/transactions даёт оба block-anchor и точный порядок, который запросила resume-транзакция.

-

03RPC EXPERIMENTAL_tx_status вместе с downstream-view доказывают, где callback-и реально выполнились и в каком видимом порядке.

+

01POST /v0/transactions показывает downstream-вызов и более поздний receipt, который возвращается в исходный контракт.

+

02jq сужает этот список receipt до одного downstream-вызова и одного callback-receipt.

+

03RPC EXPERIMENTAL_tx_status нужен только как дополнительное подтверждение, если вам важны канонический результат callback-а и его логи.

**Цель** -- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. +- Доказать на одном фиксированном mainnet-примере, что `wrap.near` отправил `ft_transfer_call` в `v2.ref-finance.near`, receiver выполнил `ft_on_transfer`, а затем `wrap.near` получил callback `ft_resolve_transfer` обратно. -Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: +Этот фиксированный mainnet-пример с callback был замечен **19 апреля 2026 года**: -- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом -- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке -- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и -- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору +- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` +- аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` +- исходный контракт: `wrap.near` +- downstream-receiver: `v2.ref-finance.near` +- верхнеуровневый метод: `ft_transfer_call` +- downstream-метод: `ft_on_transfer` +- callback-метод: `ft_resolve_transfer` +- блок транзакции: `194692298` +- блок downstream-receipt: `194692300` +- блок callback-receipt: `194692301` ```mermaid flowchart LR - Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] - H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] - R --> C["Async cross-contract callback-и"] - C --> B["Recorder state
beta"] - B --> A["Recorder state
alpha"] - A --> G["Recorder state
gamma"] - Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] - R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] - G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] + T["Одна mainnet-транзакция
ft_transfer_call на wrap.near"] --> D["Downstream-receipt
v2.ref-finance.near.ft_on_transfer"] + D --> F["Receiver упал
E51: contract paused"] + F --> C["Callback-receipt обратно в wrap.near
ft_resolve_transfer"] + C --> R["Лог refund на wrap.near"] ``` -Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. - -Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | -| Проверка готовности promise-работы | RPC [`query(call_function)`](/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | -| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | -| Снимки downstream-состояния | RPC [`query(call_function)`](/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | -| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | -| Реконструкция по блокам | Transactions API [`POST /v0/block`](/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | -| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | -| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | - -**Что должен включать полезный ответ** - -- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» -- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования -- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался -- в каких блоках стали видны изменения состояния -- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования - -## Доказательства по SocialDB - -Эти примеры стартуют с читаемого состояния в NEAR Social и откатываются назад к точной записи, которая это состояние создала. - -### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB - -Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». - -
-
- Стратегия -

Начните с читаемого значения поля, а затем превратите его field-level block в один receipt и одну транзакцию записи.

-
-
-

01NEAR Social POST /get даёт текущее значение profile.name и field-level :block.

-

02POST /v0/block превращает этот блок в конкретный receipt и хеш транзакции mike.near -> social.near.

-

03POST /v0/transactions доказывает payload записи, а RPC call_function get подтверждает, что поле и сейчас разрешается так же.

-
-
- -**Цель** - -- Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. - -**Официальные ссылки** - -- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) -- [Живая поверхность чтения NEAR Social](https://api.near.social) - -Этот сценарий следует тому же рецепту доказательства, что и расследование по подписке, но добавляет ещё один важный нюанс SocialDB: для исторического доказательства `:block` на уровне конкретного поля обычно точнее, чем `:block` у родительского объекта. В этом живом примере `mike.near/profile/name` был записан на блоке `78675795`, тогда как более широкий объект `mike.near/profile` позже сдвинулся на другой блок из-за изменений в соседних полях. Роль FastNear в этом сценарии — превратить этот блок уровня поля в receipt, затем в транзакцию и потом в читаемый payload записи. - -Для этого живого примера текущее значение `profile.name` равно `Mike Purvis`, блок записи SocialDB на уровне поля равен `78675795`, ID receipt — `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN`, хеш исходной транзакции — `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ`, а внешний блок транзакции — `78675794`. +Здесь хорошо видна одна полезная деталь NEAR: downstream-сбой не означает, что callback исчез. В этом случае `v2.ref-finance.near` уронил свой `ft_on_transfer`, но `wrap.near` всё равно позже получил `ft_resolve_transfer` и залогировал refund. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Семантическое чтение поля | NEAR Social `POST /get` | Читаем `mike.near/profile/name` с включёнными метаданными блока | Даёт читаемое значение поля и опорный `:block` SocialDB на уровне поля, где это значение было записано | -| Мост к receipt | Transactions API [`POST /v0/block`](/tx/block) | Используем блок уровня поля из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи уровня поля в конкретный receipt и хеш исходной транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который нёс `profile.name` и окружающие поля профиля в одном payload | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что поле и сейчас имеет это значение, хотя предыдущие шаги уже доказали конкретную историческую запись | +| Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | +| Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | **Что должен включать полезный ответ** -- разрешается ли `mike.near/profile/name` сейчас в `Mike Purvis` -- высоту блока записи SocialDB на уровне поля (`78675795`) и объяснение, почему для этого вопроса этот якорь лучше, чем блок родительского профиля -- конкретный ID receipt и хеш исходной транзакции за этой записью -- доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload -- различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) +- какой контракт получил downstream-вызов +- какой метод выполнился на downstream-контракте +- вернулся ли более поздний receipt в исходный контракт +- какой callback-метод там выполнился и в каком блоке +- одно простое предложение вроде «receiver упал, но исходный контракт всё равно получил callback и завершил перевод» -#### Shell-сценарий доказательства поля профиля в NEAR Social +#### Shell-сценарий проверки callback-а -Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. +Используйте этот сценарий, когда нужен один конкретный proof callback-а без превращения страницы в полный курс по теории promises. **Что вы делаете** -- Читаете текущее поле `profile.name` из NEAR Social и сохраняете блок записи SocialDB на уровне поля. -- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`, несущей `profile.name`. -- Завершаете каноническим RPC-подтверждением того, что поле всё ещё разрешается в то же значение на `final`. +- Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. +- Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. +- Останавливаетесь сразу, как только можете сказать, вернулся ли callback и что он сделал. ```bash -SOCIAL_API_BASE_URL=https://api.near.social TX_BASE_URL=https://tx.main.fastnear.com RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near -PROFILE_FIELD=profile/name +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +SENDER_ACCOUNT_ID=7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72 +ORIGIN_CONTRACT_ID=wrap.near +DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near ``` -1. Прочитайте поле профиля из NEAR Social и сохраните блок записи SocialDB на уровне поля. +1. Получите транзакцию и распечатайте downstream-receipt вместе с callback-receipt. ```bash -PROFILE_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-profile-name.json \ - | jq -r --arg account_id "$ACCOUNT_ID" \ - '.[ $account_id ].profile.name[":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - current_name: .[$account_id].profile.name[""], - field_block_height: .[$account_id].profile.name[":block"], - parent_profile_block_height: .[$account_id].profile[":block"] -}' /tmp/mike-profile-name.json - -# Ожидаемое current_name: "Mike Purvis" -# Ожидаемая высота блока уровня поля: 78675795 -``` - -2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. - -```bash -PROFILE_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-profile-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/callback-check-transaction.json >/dev/null -jq --arg account_id "$ACCOUNT_ID" '{ - profile_receipt: ( +CALLBACK_RECEIPT_ID="$( + jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | { - receipt_id, - transaction_hash, - block_height, - tx_block_height - } + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + | .receipt.receipt_id ) - ) -}' /tmp/mike-profile-block.json - -# Ожидаемый receipt ID: 2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN -# Ожидаемый хеш транзакции: 6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ -``` - -3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-profile-transaction.json >/dev/null + ' /tmp/callback-check-transaction.json +)" -jq '{ +jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" '{ transaction: { hash: .transactions[0].transaction.hash, signer_id: .transactions[0].transaction.signer_id, receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + tx_block_height: .transactions[0].execution_outcome.block_height }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), - description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), - tags: ( - .args - | @base64d - | fromjson - | .data["mike.near"].profile.tags - | keys - ) - } - ) -}' /tmp/mike-profile-transaction.json -``` - -4. Завершите каноническим подтверждением текущего состояния через raw RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-profile-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - finality: "final", - current_name: ( - .result.result - | implode - | fromjson - | .[$account_id].profile.name - ) -}' /tmp/mike-profile-rpc.json -``` - -Этот последний шаг подтверждает, что поле и сейчас разрешается в `Mike Purvis`. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись установила это поле и какая транзакция несла эту запись. - -**Зачем нужен следующий шаг?** - -NEAR Social даёт семантическое значение поля. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемый payload профиля. RPC даёт каноническое подтверждение текущего состояния. - -### Доказать, что `mike.near` подписался на `mob.near`, а затем восстановить транзакцию записи в SocialDB - -Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». - -
-
- Стратегия -

Начните с семантической связи подписки, а затем используйте блок записи как мост назад к одному receipt и одной транзакции.

-
-
-

01NEAR Social POST /get даёт читаемую связь подписки и SocialDB :block, где она была записана.

-

02POST /v0/block превращает этот блок записи в конкретный receipt и хеш транзакции за этой связью.

-

03POST /v0/transactions доказывает payload с graph.follow и index.graph, а RPC call_function get подтверждает, что связь и сейчас существует.

-
-
- -**Цель** - -- Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. - -**Официальные ссылки** - -- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) -- [Живая поверхность чтения NEAR Social](https://api.near.social) - -Читаемая связь подписки приходит из данных NEAR Social, а не из FastNear. Ключевой мост здесь — метаданные SocialDB `:block`: они указывают на блок, в котором исполнился receipt, записавший это значение. Этот блок не совпадает с блоком, в который была включена внешняя транзакция. Роль FastNear в этом сценарии — превратить эту высоту блока в receipt, затем в транзакцию и, наконец, в читаемую историю исполнения. - -Для этого живого примера текущая связь выглядит как `mike.near -> mob.near`, блок записи SocialDB равен `79574924`, ID receipt — `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th`, хеш исходной транзакции — `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb`, а внешний блок транзакции — `79574923`. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Семантическое чтение связи | NEAR Social `POST /get` | Читаем `mike.near/graph/follow/mob.near` с включёнными метаданными блока | Даёт читаемую связь подписки и опорный `:block` из SocialDB, где это значение было записано | -| Мост к receipt | Transactions API [`POST /v0/block`](/tx/block) | Используем высоту блока из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи SocialDB в конкретный receipt и хеш исходной транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который записал и `graph.follow`, и записи `index.graph` | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что связь подписки существует и сейчас, хотя предыдущие шаги уже доказали конкретную историческую запись | - -**Что должен включать полезный ответ** - -- существует ли сейчас связь подписки `mike.near -> mob.near` -- высоту блока записи SocialDB (`79574924`) и объяснение, почему это блок исполнения receipt -- конкретный ID receipt и хеш исходной транзакции за этой записью -- доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` -- различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) - -#### Shell-сценарий доказательства подписки в NEAR Social - -Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. - -**Что вы делаете** - -- Читаете текущую связь подписки из NEAR Social и сохраняете блок записи SocialDB. -- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`. -- Завершаете каноническим RPC-подтверждением того, что связь всё ещё существует на `final`. - -```bash -SOCIAL_API_BASE_URL=https://api.near.social -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near -TARGET_ACCOUNT_ID=mob.near -``` - -1. Прочитайте связь подписки из NEAR Social и сохраните блок записи SocialDB. - -```bash -FOLLOW_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - keys: [($account_id + "/graph/follow/" + $target_account_id)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-follow-edge.json \ - | jq -r --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" \ - '.[ $account_id ].graph.follow[ $target_account_id ][":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - follow_edge: .[$account_id].graph.follow[$target_account_id][""], - follow_block_height: .[$account_id].graph.follow[$target_account_id][":block"] -}' /tmp/mike-follow-edge.json - -# Ожидаемая высота блока записи: 79574924 -``` - -2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. - -```bash -FOLLOW_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$FOLLOW_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-follow-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - follow_receipt: ( + downstream_receipt: ( first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") + .transactions[0].receipts[] + | select(.receipt.receiver_id == $downstream) | { - receipt_id, - transaction_hash, - block_height, - tx_block_height + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + status: .execution_outcome.outcome.status, + block_height: .execution_outcome.block_height } ) - ) -}' /tmp/mike-follow-block.json - -# Ожидаемый receipt ID: UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th -# Ожидаемый хеш транзакции: FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -``` - -3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$FOLLOW_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-follow-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - follow_edge: (.args | @base64d | fromjson | .data["mike.near"].graph.follow["mob.near"]), - index_graph: ( - .args - | @base64d - | fromjson - | .data["mike.near"].index.graph - | fromjson - | map(select(.value.accountId == "mob.near")) - ) - } - ) -}' /tmp/mike-follow-transaction.json -``` - -4. Завершите каноническим подтверждением текущего состояния через raw RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - keys: [($account_id + "/graph/follow/" + $target_account_id)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-follow-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - finality: "final", - current_follow_edge: ( - .result.result - | implode - | fromjson - | .[$account_id].graph.follow[$target_account_id] - ) -}' /tmp/mike-follow-rpc.json -``` - -Этот последний шаг подтверждает, что связь подписки существует и сейчас. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись создала эту связь и какая транзакция несла эту запись. - -**Зачем нужен следующий шаг?** - -NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. - -### Какая транзакция записала `mob.near/widget/Profile`? - -Используйте это расследование, когда вопрос звучит так: «я уже знаю, что `mob.near/widget/Profile` существует прямо сейчас. Какая именно транзакция записала ту версию виджета, на которую я смотрю?» - -Это естественное tx-продолжение к более лёгкому RPC-сценарию про виджет и к provenance-NFT-сценарию. Задача здесь прямолинейная: - -- стартуем с собственного SocialDB-блока виджета -- превращаем этот блок в один `mob.near -> social.near` receipt -- восстанавливаем исходную транзакцию -- декодируем payload `set` и доказываем, что он действительно нёс исходник виджета - -
-
- Стратегия -

Смотрите на write-block виджета как на весь мост сразу: блок в receipt, receipt в транзакцию, транзакцию в исходник.

-
-
-

01POST /v0/block начинает с блока виджета и сужает его до одного receipt mob.near -> social.near.

-

02POST /v0/transactions превращает этот receipt в один читаемый payload set с исходником виджета.

-

03RPC call_function get — это финальное подтверждение текущего состояния, что виджет и сейчас существует.

-
-
- -**Цель** - -- Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -Для этого живого якоря: - -- аккаунт: `mob.near` -- виджет: `Profile` -- блок записи в SocialDB: `86494825` -- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` -- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` -- внешний блок транзакции: `86494824` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Мост от блока к receipt | Transactions API [`POST /v0/block`](/tx/block) | Берём блок `86494825` с `with_receipts: true`, а затем фильтруем его обратно до `mob.near -> social.near` | Превращает блок записи виджета в один конкретный receipt и один конкретный хеш транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `mob.near/widget/Profile` | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | - -**Что должен включать полезный ответ** - -- высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции -- конкретный receipt ID и хеш исходной транзакции за этой записью виджета -- доказательство, что payload записи был `set` с `mob.near/widget/Profile` -- одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» - -#### Shell-сценарий доказательства записи виджета в NEAR Social - -Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. - -**Что вы делаете** - -- Стартуете с блока последней записи виджета. -- Переиспользуете эту высоту в FastNear block receipts, чтобы получить receipt и мост к транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы декодировать записанный исходник виджета. -- Завершаете сырым RPC-подтверждением, что виджет всё ещё существует сейчас. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -WIDGET_BLOCK_HEIGHT=86494825 -``` - -1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. - -```bash -WIDGET_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$WIDGET_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mob-widget-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - widget_write_receipt: ( + ), + callback_receipt: ( first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) | { - receipt_id, - transaction_hash, - block_height, - tx_block_height + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: .receipt.receipt.Action.actions[0].FunctionCall.method_name, + logs: .execution_outcome.outcome.logs, + status: .execution_outcome.outcome.status, + block_height: .execution_outcome.block_height } ) - ) -}' /tmp/mob-widget-block.json - -# Ожидаемый receipt ID: CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6 -# Ожидаемый хеш транзакции: 9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia -``` - -2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mob-widget-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].transaction.actions[0].FunctionCall - | { - method_name, - widget_source_head: ( - .args - | @base64d - | fromjson - | .data["mob.near"].widget.Profile[""] - | split("\n")[0:12] + ), + callback_ran: ( + first( + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" ) - } - ) -}' /tmp/mob-widget-transaction.json -``` - -Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `9QDup...` вызвала `social.near set` и пронесла в `args` настоящий исходник `mob.near/widget/Profile`. - -3. Завершите каноническим подтверждением текущего состояния через сырой RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mob-widget-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - finality: "final", - current_widget_head: ( - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:5] + | true + ) // false ) -}' /tmp/mob-widget-rpc.json -``` - -Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. - -**Зачем нужен следующий шаг?** - -Блок записи виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. - -### Проследить один расчёт NEAR Intents и показать, что именно произошло - -Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». - -
-
- Стратегия -

Смотрите на один расчёт как на читаемую трассу, а не как на теорию протокола с первой строки.

-
-
-

01POST /v0/transactions даёт каркас расчёта: входную точку, первые downstream-контракты и ранние логи.

-

02POST /v0/block переиспользует тот же якорь, когда нужен контекст включающего блока вокруг этого расчёта.

-

03RPC EXPERIMENTAL_tx_status нужен там, где уже требуется канонический DAG по receipt и имена событий, которые доказывают реальное движение активов.

-
-
- -**Цель** - -- Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. - -**Официальные ссылки** +}' /tmp/callback-check-transaction.json -- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) -- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) -- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) - -Для живой трассировки ниже используйте этот фиксированный якорь расчёта из mainnet, зафиксированный **18 апреля 2026 года**: - -- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` -- аккаунт `signer` и `receiver`: `intents.near` -- высота включающего блока: `194573310` - -Быстрая полезная модель здесь простая: - -- `intents.near` выполняет входную точку расчёта -- последующие receipt расходятся по контрактам, которые реально переводят или выводят активы -- журналы событий показывают, какие действия расчёта случились, через имена вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` - -Для этого конкретного расчёта короткий человеческий ответ уже неплохой: - -- `intents.near` вызвал `execute_intents` -- downstream receipt ушли в `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` -- трасса выдала события `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` - -Если нужен протокольный фон, базовая форма сопоставления здесь — это двухсторонний `token_diff` intent: одна сторона подписывает желаемую разницу по активам, вторая сторона подписывает противоположную разницу, а затем совпавшая пара отправляется на расчёт. Но для рабочего расследования обычно понятнее начать с одной реальной транзакции расчёта и читать доказательства прямо с цепочки. - -```mermaid -flowchart LR - T["Одна mainnet-транзакция
4cfei8p4..."] --> I["intents.near
execute_intents"] - I --> R["Последующие receipt"] - R --> C["Подключаются другие контракты"] - R --> E["Появляются журналы событий"] - E --> TD["token_diff"] - E --> IE["intents_executed"] - E --> MT["mt_transfer / mt_withdraw"] -``` - -Публичных FastNear-поверхностей уже достаточно, чтобы ответить на практический вопрос: - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Каркас расчёта | Transactions API [`POST /v0/transactions`](/tx/transactions) | Начинаем с фиксированного хеша транзакции и печатаем основную транзакцию плюс первые последующие receipt | Даёт самый быстрый читаемый ответ на вопрос «во что этот расчёт пошёл дальше?» | -| Контекст блока | Transactions API [`POST /v0/block`](/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Показывает, в какой блок попал расчёт и какие receipt этой транзакции видны в блоке | -| Каноническое доказательство по receipt и событиям | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` плюс логи `EVENT_JSON` | Даёт протокольно-канонический DAG, `executor_id` и имена событий, которые объясняют, что реально сделал расчёт | - -**Что должен включать полезный ответ** - -- какую входную точку расчёта вы увидели на `intents.near` -- какие downstream-контракты и методы появились сразу после неё -- какие семейства событий выпустила трассировка -- одно итоговое предложение простым языком о том, что произошло - -Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. - -#### Shell-сценарий расчёта NEAR Intents - -Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. - -**Что вы делаете** - -- Получаете читаемую историю расчёта через Transactions API. -- Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. -- Подтверждаете канонический DAG по receipt и семейства событий через `EXPERIMENTAL_tx_status`. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 -INTENTS_SIGNER_ID=intents.near -``` - -1. Начните с самой транзакции расчёта и восстановите первый читаемый поток последующих receipt. - -```bash -INTENTS_BLOCK_HASH="$( - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$INTENTS_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/intents-transaction.json \ - | jq -r '.transactions[0].execution_outcome.block_hash' -)" - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - receipt_flow: [ - .transactions[0].receipts[:6][] - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - block_height: .execution_outcome.block_height, - methods: ( - [.receipt.receipt.Action.actions[]?.FunctionCall.method_name] - | map(select(. != null)) - ), - first_log: (.execution_outcome.outcome.logs[0] // null) - } - ] -}' /tmp/intents-transaction.json -``` - -Этот первый шаг уже даёт сильный операторский ответ: `intents.near` выполнил транзакцию расчёта, а ранние последующие receipt показывают, какие контракты вошли в каскад сразу после этого. - -2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. - -```bash -curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_id "$INTENTS_BLOCK_HASH" '{ - block_id: $block_id, - with_receipts: true, - with_transactions: false - }')" \ - | tee /tmp/intents-block.json >/dev/null - -jq --arg tx_hash "$INTENTS_TX_HASH" '{ - block_height: .block.block_height, - block_hash: .block.block_hash, - tx_receipts: [ - .block_receipts[] - | select(.transaction_hash == $tx_hash) - | { - receipt_id, - predecessor_id, - receiver_id, - block_height - } - ] -}' /tmp/intents-block.json +# На что смотреть: +# - downstream-receipt выполнил ft_on_transfer на v2.ref-finance.near +# - более поздний callback-receipt выполнил ft_resolve_transfer на wrap.near +# - callback_ran равно true, даже несмотря на downstream-сбой ``` -3. Подтвердите канонический DAG по receipt и извлеките семейства событий через RPC. +2. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ - --arg tx_hash "$INTENTS_TX_HASH" \ - --arg sender_account_id "$INTENTS_SIGNER_ID" '{ + --arg tx_hash "$TX_HASH" \ + --arg sender_account_id "$SENDER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "EXPERIMENTAL_tx_status", @@ -1519,115 +992,87 @@ curl -s "$RPC_URL" \ wait_until: "FINAL" } }')" \ - | tee /tmp/intents-rpc.json >/dev/null + | tee /tmp/callback-check-rpc.json >/dev/null -jq '{ - final_execution_status: .result.final_execution_status, - receipts_outcome: [ - .result.receipts_outcome[:6][] - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - first_log: (.outcome.logs[0] // null) - } - ] -}' /tmp/intents-rpc.json +jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ + top_level_status: .result.status, + callback_receipt: ( + first( + .result.receipts_outcome[] + | select(.id == $callback_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + logs: .outcome.logs, + status: .outcome.status + } + ) + ) +}' /tmp/callback-check-rpc.json -jq -r ' - .result.receipts_outcome[] - | .outcome.logs[] - | select(startswith("EVENT_JSON:")) - | capture("event\":\"(?[^\"]+)\"").event -' /tmp/intents-rpc.json | sort -u +# На что смотреть: +# - downstream ft_on_transfer receipt упал на v2.ref-finance.near +# - wrap.near всё равно позже получил ft_resolve_transfer +# - лог callback-а показывает refund обратно отправителю ``` **Зачем нужен следующий шаг?** -`POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. - -## Частые задачи - -### Найти одну транзакцию - -**Начните здесь** - -- [Transactions by Hash](/tx/transactions), когда идентификатор транзакции уже известен. - -**Следующая страница при необходимости** - -- [Receipt Lookup](/tx/receipt), если важной стала последующая квитанция. -- [Block](/tx/block), если нужен контекст блока. -- [Transaction Status](/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. - -**Остановитесь, когда** - -- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. - -**Переходите дальше, когда** +Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. -- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. -- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. +## Расширенные сценарии и case study -### Исследовать квитанцию +Примеры ниже всё ещё полезны, но они длиннее или более специализированы, чем основные start-here-сценарии выше. `Berry Club` и `OutLayer` вынесены в отдельные case-study-страницы, паттерн provenance для SocialDB теперь живёт на отдельной расширенной странице, а последний пример здесь оставляет только компактный multi-contract follow-up. -**Начните здесь** +### Расширенный паттерн provenance для SocialDB -- [Receipt Lookup](/tx/receipt), когда ID квитанции — лучший якорь для расследования. +Если читаемый факт уже приходит из `api.near.social`, держите follow-up маленьким: сначала семантическое значение, затем `:block`, потом lookup по блоку и транзакции в FastNear. Для одного канонического примера такого сценария используйте [отдельную страницу паттерна provenance для SocialDB](/tx/socialdb-proofs). -**Следующая страница при необходимости** +### Расширенный сценарий: какие downstream-контракты затронула эта транзакция? -- [Transactions by Hash](/tx/transactions), чтобы связать квитанцию с исходной транзакцией. -- [Account History](/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. +Используйте этот сценарий, когда у вас уже есть один multi-contract tx hash и следующий вопрос звучит просто: «в какие контракты ушёл этот вызов после top-level action?» -**Остановитесь, когда** +Этот зафиксированный mainnet-якорь по-прежнему хорошо подходит как пример, хотя сама транзакция и относится к `intents.near`: -- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. - -**Переходите дальше, когда** - -- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](/rpc). -- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. - -### Посмотреть недавнюю активность аккаунта - -**Начните здесь** - -- [Account History](/tx/account) для ленты активности по аккаунту. - -**Следующая страница при необходимости** - -- [Transactions by Hash](/tx/transactions) для конкретной транзакции из ленты. -- [Receipt Lookup](/tx/receipt), если фокус смещается на одну квитанцию. - -**Остановитесь, когда** - -- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. - -**Переходите дальше, когда** - -- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](/transfers). -- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](/rpc) или [FastNear API](/api). - -### Восстановить ограниченное окно по блокам - -**Начните здесь** +- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` +- аккаунт `signer` и `receiver`: `intents.near` +- высота включающего блока: `194573310` -- [Blocks](/tx/blocks) для ограниченного просмотра диапазона блоков. -- [Block](/tx/block), когда известен точный блок, который нужно исследовать. +Короткий ответ для этой tx уже полезен: -**Следующая страница при необходимости** +- top-level метод был `execute_intents` +- ранние downstream-receipt затронули `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` +- в более поздних логах были семейства событий вроде `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` -- [Transactions by Hash](/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. -- [Receipt Lookup](/tx/receipt), если одной квитанции достаточно для следующего шага расследования. +Для большинства вопросов достаточно Transactions API: -**Остановитесь, когда** +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 -- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name + }, + downstream_receivers: ( + [.transactions[0].receipts[] | .receipt.receiver_id] + | unique + ), + first_logs: ( + [.transactions[0].receipts[] | .execution_outcome.outcome.logs[]?] + | .[:5] + ) + }' +``` -**Переходите дальше, когда** +Если нужен включающий блок, расширяйтесь один раз до Transactions API [`POST /v0/block`](/tx/block). Если нужен канонический DAG по receipt или сырые логи `EVENT_JSON`, расширяйтесь ещё на один шаг до RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status). Учебная идея здесь общая: начинайте с одного tx hash, перечислите downstream receiver-и и останавливайтесь, пока trace действительно не требует большего. -- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](/rpc). -- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](/neardata). ## Частые ошибки diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx index 2015dfb..839ae69 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx @@ -1,8 +1,8 @@ --- -sidebar_label: OutLayer +sidebar_label: OutLayer Case Study slug: /tx/examples/outlayer -title: "OutLayer: трассировка запроса и разрешения воркером" -description: "Используйте Transactions API, чтобы связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать завершающие receipts." +title: "OutLayer Case Study: трассировка запроса и разрешения воркером" +description: "Case study, который использует Transactions API, чтобы связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать завершающие receipts." displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -17,11 +17,13 @@ keywords: import Link from '@site/src/components/LocalizedLink'; -{/* FASTNEAR_AI_DISCOVERY: Этот пример остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} +{/* FASTNEAR_AI_DISCOVERY: Этот case study остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} -# OutLayer: трассировка запроса и разрешения воркером +# OutLayer Case Study: трассировка запроса и разрешения воркером -Используйте этот сценарий, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» +Используйте этот case study, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» + +Эта страница намеренно остаётся только в пределах публичных chain-данных. Она показывает caller-транзакцию, более позднюю worker-транзакцию и finish-receipts. Она не пытается доказывать внутреннюю TEE-, `yield/resume`- или CKD/MPC-архитектуру OutLayer только по chain-trace. Сначала смотрите на это как на задачу по истории транзакций: @@ -29,29 +31,6 @@ import Link from '@site/src/components/LocalizedLink'; - одна более поздняя worker-side транзакция `submit_execution_output_and_resolve` или `resolve_execution` - переход к receipts только тогда, когда уже важен путь завершения -
-
- Стратегия -

Сначала найдите два хеша, затем раскройте их, а к worker-receipts переходите только тогда, когда нужен finish-путь.

-
-
-

01POST /v0/account по outlayer.* — самый быстрый surface для поиска хешей.

-

02POST /v0/transactions превращает caller-хеш и worker-хеш в читаемые signer, method и log-доказательства.

-

03Разбирайте receipts worker-транзакции только тогда, когда реальный вопрос уже касается callback, списания или возврата средств.

-
-
- -**Цель** - -- Восстановить одну caller-side транзакцию, одну worker-side транзакцию и завершающие receipts, которые относятся к одному и тому же запросу OutLayer. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Поиск хешей | Transactions API [`POST /v0/account`](/tx/account) | Забираем недавние хеши транзакций для `outlayer.near` | Даёт самый быстрый локальный по контракту surface, когда пара хешей ещё не известна | -| Раскрытие транзакций | Transactions API [`POST /v0/transactions`](/tx/transactions) | Раскрываем caller-side и worker-side хеши вместе | Превращает сырые хеши в signer-, receiver-, action- и log-доказательства | -| Разбор finish-пути | Transactions API [`POST /v0/transactions`](/tx/transactions) | Переиспользуем worker-хеш и читаем список его receipts | Показывает, где материализовались callback, списание и возврат средств | -| Необязательная проверка идентичности | RPC [`view_account`](/rpc/account/view-account) | Идём в RPC только если следующий вопрос уже про идентичность контракта, а не про историю транзакций | Держит проверку текущего состояния отдельно от трассировки истории | - ## Проверенный shell-сценарий Эта пара работала 18 апреля 2026 года: @@ -87,14 +66,7 @@ jq '{ }' /tmp/outlayer-pair.json ``` -Что это доказывает: - -- запросная транзакция шла от `solarflux.near` к `outlayer.near` -- в логах запроса фигурировал проект `zavodil.near/near-email` -- worker-транзакция позже пришла от `worker.outlayer.near` к `outlayer.near` -- в логах воркера были `Stored pending output` и `Resolving execution ...` - -Это и есть главный наблюдаемый цикл в терминах NEAR: сначала caller-side запрос, затем более позднее worker-side разрешение. +Это и есть главный наблюдаемый цикл: сначала caller-side запрос, затем более позднее worker-side разрешение, с читаемыми signer-, method- и log-доказательствами для обеих транзакций. ### 2. Читайте worker-receipts только тогда, когда уже важен finish-путь @@ -138,23 +110,12 @@ curl -sS "$TX_BASE_URL/v0/account" \ }' ``` -Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это уже surface, который превращает их в читаемое доказательство. - -## Граница сценария - -Этот сценарий намеренно остаётся в рамках публичных chain-данных, которые FastNear и RPC умеют показывать напрямую: - -- caller-side транзакция запроса -- более поздняя worker-side транзакция разрешения -- finish-receipts и логи - -Он **не** пытается доказывать внутреннюю TEE-модель OutLayer, использование same-account `yield/resume` или путь доверия через CKD/MPC только по публичной трассе транзакций. Это отдельные архитектурные вопросы, и читать их нужно в документации OutLayer, а не считать доказанными этим trace-сценарием. +Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это surface, который превращает их в читаемое доказательство. ## Полезные связанные страницы - Transactions API: история аккаунта - Transactions API: транзакции по хешу - Transactions API: receipt по ID -- RPC: view_account - [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) - [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx new file mode 100644 index 0000000..c63ac12 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx @@ -0,0 +1,198 @@ +--- +slug: /tx/socialdb-proofs +title: Расширенный паттерн provenance для SocialDB +description: Один расширенный паттерн, который начинается с читаемого значения SocialDB и восстанавливает транзакцию записи за ним. +displayed_sidebar: transactionsApiSidebar +page_actions: + - markdown +--- + +# Расширенный паттерн provenance для SocialDB + +Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому provenance. + +Для FastNear-first-задач сначала откройте [Transactions Examples](/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" + +## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` + +Используйте этот сценарий, когда читаемый факт уже звучит как "текущее `profile.name` равно `Mike Purvis`", а остаётся только вопрос, какая запись сделала это поле истинным. + +Здесь достаточно сохранить один важный нюанс SocialDB: для исторического provenance правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. + +Для этого живого якоря: + +- текущее `profile.name`: `Mike Purvis` +- блок записи SocialDB на уровне поля: `78675795` +- receipt ID: `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN` +- хеш исходной транзакции: `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ` +- внешний блок транзакции: `78675794` + +### Shell-сценарий + +1. Прочитайте поле из NEAR Social и сохраните block записи на уровне поля. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +PROFILE_FIELD=profile/name + +PROFILE_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-profile-name.json \ + | jq -r --arg account_id "$ACCOUNT_ID" \ + '.[ $account_id ].profile.name[":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + current_name: .[$account_id].profile.name[""], + field_block_height: .[$account_id].profile.name[":block"], + parent_profile_block_height: .[$account_id].profile[":block"] +}' /tmp/mike-profile-name.json +``` + +2. Переиспользуйте этот block уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. + +```bash +PROFILE_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-profile-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + profile_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-profile-block.json +``` + +3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-profile-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), + description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), + tags: ( + .args + | @base64d + | fromjson + | .data["mike.near"].profile.tags + | keys + ) + } + ) +}' /tmp/mike-profile-transaction.json +``` + +4. Завершите каноническим подтверждением текущего состояния через raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-profile-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + finality: "final", + current_name: ( + .result.result + | implode + | fromjson + | .[$account_id].profile.name + ) +}' /tmp/mike-profile-rpc.json +``` + +Это и есть весь provenance-паттерн: читаемое значение, block уровня поля, мост через receipt, payload транзакции и затем необязательное подтверждение текущего состояния. + +## Тот же паттерн для других ключей SocialDB + +Используйте тот же мост каждый раз, когда у вас уже есть читаемое значение SocialDB и его write-block: + +1. Прочитайте семантическое значение и `:block` из NEAR Social или начните с уже известного блока записи виджета. +2. Используйте Transactions API [`POST /v0/block`](/tx/block), чтобы восстановить receipt `*.near -> social.near` и хеш транзакции. +3. Используйте Transactions API [`POST /v0/transactions`](/tx/transactions), чтобы декодировать payload `social.near set`. +4. Используйте RPC [`query(call_function)`](/rpc/contract/call-function) только если всё ещё нужно каноническое подтверждение текущего состояния. + +### Вариант для связи подписки + +Тот же паттерн работает для читаемой связи подписки: + +- текущая связь: `mike.near -> mob.near` +- блок записи SocialDB: `79574924` +- receipt ID: `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th` +- хеш исходной транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` + +Главное отличие от примера с профилем только в ключе и форме payload: читайте `mike.near/graph/follow/mob.near`, а затем декодируйте и `graph.follow`, и соответствующую запись `index.graph` из payload записи. + +### Вариант для исходника виджета + +Тот же паттерн снова работает, когда читаемый факт относится к ключу с исходником виджета: + +- ключ виджета: `mob.near/widget/Profile` +- блок записи SocialDB: `86494825` +- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` +- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` + +Главное отличие здесь в том, что вы обычно стартуете с уже известного блока записи виджета, а затем декодируете `.data["mob.near"].widget.Profile[""]` из исходного payload `social.near set`. diff --git a/scripts/generate-ai-surfaces.js b/scripts/generate-ai-surfaces.js index a89f67a..32710e6 100644 --- a/scripts/generate-ai-surfaces.js +++ b/scripts/generate-ai-surfaces.js @@ -106,6 +106,8 @@ const AUTHORED_MARKDOWN_LABELS = { en: { aiAndAgents: "AI & Agents", bestFor: "Best for:", + berryClubLiveBoardNote: + "Live board card: fetches `berryclub.ek.near` `get_lines` from mainnet RPC and renders the current 50x50 grid in the docs UI.", berryClubGalleryNote: "Rendered snapshot gallery: launch, mid, and recent checkpoints from checked-in `src/data/berryClubSnapshots.json`.", guidesArchiveTitle: "FastNear Builder Docs Full Documentation Archive", @@ -139,6 +141,8 @@ const AUTHORED_MARKDOWN_LABELS = { ru: { aiAndAgents: "AI и агенты", bestFor: "Лучше всего подходит для:", + berryClubLiveBoardNote: + "Карточка живой доски: запрашивает `berryclub.ek.near` `get_lines` через mainnet RPC и рендерит текущую сетку 50x50 в интерфейсе документации.", berryClubGalleryNote: "Галерея снимков: контрольные точки launch, mid и recent из сохранённого `src/data/berryClubSnapshots.json`.", guidesArchiveTitle: "Полный архив документации FastNear Builder Docs", @@ -662,6 +666,9 @@ function transformSpecialComponents(markdown, locale = DEFAULT_LOCALE) { const labels = getAuthoredMarkdownLabels(locale); return markdown.replace( + //g, + `\n\n${labels.berryClubLiveBoardNote}\n\n` + ).replace( //g, `\n\n${labels.berryClubGalleryNote}\n\n` ); diff --git a/src/components/BerryClubLiveBoard/index.js b/src/components/BerryClubLiveBoard/index.js new file mode 100644 index 0000000..85b9bac --- /dev/null +++ b/src/components/BerryClubLiveBoard/index.js @@ -0,0 +1,225 @@ +import React, { useEffect, useState } from 'react'; + +import berryClubSnapshots from '@site/src/data/berryClubSnapshots.json'; +import styles from './styles.module.css'; + +const RPC_URL = 'https://rpc.mainnet.fastnear.com'; +const CONTRACT_ID = 'berryclub.ek.near'; +const BOARD_SIZE = 50; +const DEFAULT_FALLBACK_SNAPSHOT = + berryClubSnapshots.snapshots.find((snapshot) => snapshot.id === 'recent') || + null; + +function colorToHex(color) { + return `#${Number(color).toString(16).padStart(6, '0').slice(-6)}`; +} + +function encodeArgsBase64(args) { + return btoa(JSON.stringify(args)); +} + +function decodeJsonBytes(bytes) { + return JSON.parse(new TextDecoder().decode(Uint8Array.from(bytes))); +} + +function decodeLine(encodedLine) { + const binary = atob(encodedLine); + const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0)); + const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); + const colors = []; + + for (let offset = 4; offset + 4 <= bytes.length; offset += 8) { + colors.push(view.getUint32(offset, true) & 0xffffff); + } + + while (colors.length < BOARD_SIZE) { + colors.push(0); + } + + return colors.slice(0, BOARD_SIZE); +} + +function decodeBoard(lines) { + const decodedLines = lines.slice(0, BOARD_SIZE).map(decodeLine); + + while (decodedLines.length < BOARD_SIZE) { + decodedLines.push(Array(BOARD_SIZE).fill(0)); + } + + return decodedLines; +} + +async function fetchLiveBoard() { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'berry-live-board', + method: 'query', + params: { + request_type: 'call_function', + finality: 'final', + account_id: CONTRACT_ID, + method_name: 'get_lines', + args_base64: encodeArgsBase64({ + lines: Array.from({ length: BOARD_SIZE }, (_, index) => index), + }), + }, + }), + }); + + if (!response.ok) { + throw new Error(`RPC request failed with ${response.status}`); + } + + const payload = await response.json(); + + if (payload.error) { + throw new Error(payload.error.message || 'RPC returned an error'); + } + + const result = payload.result; + + return { + board: decodeBoard(decodeJsonBytes(result.result)), + blockHeight: result.block_height, + blockHash: result.block_hash, + }; +} + +function Board({ board, title }) { + return ( +
+ {board.flat().map((color, index) => ( +
+ ))} +
+ ); +} + +export default function BerryClubLiveBoard({ + fallbackSnapshot, + title, + uiText = {}, +}) { + const resolvedFallbackSnapshot = fallbackSnapshot || DEFAULT_FALLBACK_SNAPSHOT; + const resolvedUiText = { + title: uiText.title || 'Live Berry Club board', + blockHeightLabel: uiText.blockHeightLabel || 'Block', + blockHashLabel: uiText.blockHashLabel || 'Block hash', + sourceLabel: uiText.sourceLabel || 'Source', + sourceValue: uiText.sourceValue || 'Mainnet RPC `get_lines`', + loadingLabel: uiText.loadingLabel || 'Loading live board...', + refreshingLabel: + uiText.refreshingLabel || 'Refreshing live board from RPC...', + fallbackLabel: + uiText.fallbackLabel || + 'Showing the latest saved snapshot while the live RPC read updates.', + errorLabel: uiText.errorLabel || 'Live RPC read failed.', + emptyLabel: uiText.emptyLabel || 'No board data available yet.', + }; + + const [board, setBoard] = useState(resolvedFallbackSnapshot?.board || null); + const [blockHeight, setBlockHeight] = useState( + resolvedFallbackSnapshot?.blockHeight || null + ); + const [blockHash, setBlockHash] = useState(null); + const [status, setStatus] = useState( + resolvedFallbackSnapshot ? 'refreshing' : 'loading' + ); + const [error, setError] = useState(null); + + useEffect(() => { + let active = true; + + fetchLiveBoard() + .then((result) => { + if (!active) { + return; + } + + setBoard(result.board); + setBlockHeight(result.blockHeight); + setBlockHash(result.blockHash); + setStatus('ready'); + setError(null); + }) + .catch((fetchError) => { + if (!active) { + return; + } + + setError(fetchError.message); + setStatus(resolvedFallbackSnapshot ? 'fallback' : 'error'); + }); + + return () => { + active = false; + }; + }, [resolvedFallbackSnapshot]); + + return ( +
+
+
+

{title || resolvedUiText.title}

+
+
+
{resolvedUiText.sourceLabel}
+
{resolvedUiText.sourceValue}
+
+ {blockHeight ? ( +
+
{resolvedUiText.blockHeightLabel}
+
{blockHeight}
+
+ ) : null} + {blockHash ? ( +
+
{resolvedUiText.blockHashLabel}
+
{blockHash}
+
+ ) : null} +
+
+ +
+ {status === 'loading' ? ( +

{resolvedUiText.loadingLabel}

+ ) : null} + {status === 'refreshing' ? ( + <> +

{resolvedUiText.refreshingLabel}

+

{resolvedUiText.fallbackLabel}

+ + ) : null} + {status === 'fallback' ? ( + <> +

{resolvedUiText.errorLabel}

+

{resolvedUiText.fallbackLabel}

+ + ) : null} + {status === 'error' ? ( + <> +

{resolvedUiText.errorLabel}

+ {error ?

{error}

: null} + + ) : null} +
+
+ + {board ? ( + + ) : ( +
{resolvedUiText.emptyLabel}
+ )} +
+ ); +} diff --git a/src/components/BerryClubLiveBoard/styles.module.css b/src/components/BerryClubLiveBoard/styles.module.css new file mode 100644 index 0000000..cc88701 --- /dev/null +++ b/src/components/BerryClubLiveBoard/styles.module.css @@ -0,0 +1,106 @@ +.card { + background: + linear-gradient(180deg, rgba(255, 248, 240, 0.96), rgba(255, 255, 255, 0.98)); + border: 1px solid rgba(170, 140, 116, 0.35); + border-radius: 16px; + box-shadow: 0 18px 48px rgba(83, 52, 28, 0.12); + margin: 1.5rem 0; + padding: 1rem; +} + +.cardHeader { + display: grid; + gap: 1rem; + margin-bottom: 1rem; +} + +@media (min-width: 780px) { + .cardHeader { + align-items: start; + grid-template-columns: minmax(0, 1fr) minmax(220px, 280px); + } +} + +.cardTitle { + font-size: 1.15rem; + line-height: 1.2; + margin: 0 0 0.75rem; +} + +.metaList { + display: grid; + gap: 0.35rem; + margin: 0; +} + +.metaItem { + align-items: baseline; + display: grid; + gap: 0.35rem; + grid-template-columns: minmax(72px, auto) 1fr; +} + +.metaItem dt { + color: rgba(92, 67, 48, 0.78); + font-size: 0.78rem; + font-weight: 600; + margin: 0; +} + +.metaItem dd { + color: rgba(33, 23, 15, 0.96); + font-family: var(--ifm-font-family-monospace); + font-size: 0.78rem; + margin: 0; + overflow-wrap: anywhere; +} + +.hashValue { + word-break: break-word; +} + +.statusBlock { + display: grid; + gap: 0.35rem; +} + +.status { + color: rgba(92, 67, 48, 0.96); + font-size: 0.88rem; + font-weight: 600; + margin: 0; +} + +.helper { + color: rgba(92, 67, 48, 0.82); + font-size: 0.82rem; + margin: 0; +} + +.board { + background: #0f0d08; + border: 1px solid rgba(22, 16, 10, 0.65); + border-radius: 12px; + display: grid; + grid-template-columns: repeat(50, minmax(0, 1fr)); + overflow: hidden; + width: 100%; +} + +.pixel { + aspect-ratio: 1 / 1; + min-width: 0; +} + +.emptyBoard { + align-items: center; + background: rgba(15, 13, 8, 0.05); + border: 1px dashed rgba(92, 67, 48, 0.35); + border-radius: 12px; + color: rgba(92, 67, 48, 0.82); + display: flex; + justify-content: center; + min-height: 240px; + padding: 1rem; + text-align: center; +} diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index 8708265..8bc884d 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -78,16 +78,16 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. -### У меня обычный стейкинг или liquid staking? +### Есть ли у этого аккаунта прямой стейкинг прямо сейчас? -Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». +Используйте этот сценарий, когда история проста: «скажи, есть ли у аккаунта видимые прямые staking pool прямо сейчас, и покажи, какие именно это пулы». Стратегия - Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. + Один раз прочитайте staking-эндпоинт и превратите видимый список пулов в ответ “да / нет”. - 01GET /v1/account/.../staking находит прямую экспозицию через пулы. - 02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них. - 03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed. + 01GET /v1/account/.../staking возвращает видимые прямые staking-позиции аккаунта. + 02jq превращает ответ в has_direct_staking_now, pool_count и pool_ids. + 03Если массив pools пуст, ответ этой поверхности просто звучит как «прямой стейкинг сейчас не виден». **Сеть** @@ -96,345 +96,154 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Официальные ссылки** - [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) -- [Liquid staking](https://docs.near.org/primitives/liquid-staking) - -Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. **Что вы делаете** - Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. -- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. -- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. +- Печатаете короткий итог “да / нет” и список видимых `pool_id`. +- На этом останавливаетесь, если только следующий вопрос уже не касается `unstake` или `withdraw` в конкретном пуле. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +ACCOUNT_ID=mike.near ``` 1. Получите представление по прямому стейкингу. ```bash curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` + | tee /tmp/account-staking.json >/dev/null -2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null +jq '{ + account_id, + has_direct_staking_now: ((.pools // []) | length > 0), + pool_count: ((.pools // []) | length), + pool_ids: ((.pools // []) | map(.pool_id)) +}' /tmp/account-staking.json ``` -3. Классифицируйте аккаунт на основе этих двух индексированных представлений. - -```bash -jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ - --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens - | { - classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) - }' -``` +На момент написания для `mike.near` здесь возвращались видимые прямые staking-пулы. Если для вашего аккаунта `pool_ids` пуст, этот эндпоинт отвечает: «прямой стейкинг сейчас не виден». **Зачем нужен следующий шаг?** -Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. +Так вопрос остаётся узким и практическим. Если ответ `true`, следующий реальный шаг обычно связан с `unstake` или `withdraw` в конкретном пуле. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. -### Заархивировать версию BOS-виджета как provenance NFT +### Какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает? -Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». +Используйте этот сценарий, когда у wallet-экрана, support-инструмента или агента уже есть `account_id` и нужен быстрый индексированный обзор holdings: FT-балансы плюс NFT-коллекции, из которых этот аккаунт сейчас что-то показывает. Стратегия - Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. - - 01GET /v1/account/.../nft проверяет, появляется ли у получателя уже запись об этой архивной коллекции. - 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. - 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте точные provenance-поля через nft_token. - -**Сети** - -- mainnet для чтения виджета из `social.near` -- testnet для безопасного mint provenance NFT в `nft.examples.testnet` + Сначала прочитайте FT-балансы, затем NFT-коллекции и только потом соберите их в один компактный индексированный инвентарь. -**Официальные ссылки** - -- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) -- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + 01GET /v1/account/.../ft даёт индексированные FT-балансы кошелька. + 02GET /v1/account/.../nft даёт NFT-коллекции, из которых этот кошелёк сейчас показывает holdings. + 03jq превращает эти два индексированных чтения в один wallet-friendly инвентарь. **Что вы делаете** -- Через FastNear API проверяете, появляется ли у получателя уже запись об архивной коллекции. -- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. -- Хешируете исходник виджета и превращаете его в provenance-метаданные. -- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. -- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. +- Читаете FT-балансы аккаунта. +- Читаете NFT-holdings аккаунта на уровне коллекций. +- Печатаете один короткий индексированный инвентарь, который можно переиспользовать в wallet- или support-сценарии. -Зафиксированный исходный виджет: +Этот пример не отвечает на вопросы про нативный баланс, стейкинг, пулы, точные NFT token ID или метаданные. -- аккаунт автора: `mob.near` -- путь виджета: `mob.near/widget/Profile` -- SocialDB-блок уровня виджета: `86494825` +NFT-эндпоинт здесь работает на уровне коллекций. Воспринимайте его как ответ на вопрос «из каких NFT-контрактов этот аккаунт сейчас что-то держит?», а не как полный per-token crawl. ```bash -API_BASE_URL=https://test.api.fastnear.com -MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com -TESTNET_RPC_URL=https://rpc.testnet.fastnear.com -AUTHOR_ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -DESTINATION_COLLECTION_ID=nft.examples.testnet -RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID + +# Пример живого значения, проверенного 19 апреля 2026 года: +# ACCOUNT_ID=mike.near ``` -1. Через FastNear API посмотрите, появляется ли у получателя уже запись об архивной коллекции. +1. Прочитайте FT-балансы аккаунта. ```bash -curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ - | tee /tmp/provenance-account-nfts.json >/dev/null - -jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_collection_entries: [ - .tokens[]? - | select(.contract_id == $destination_collection_id) - | { +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null + +jq '{ + account_id, + ft_contracts: ( + .tokens + | map(select((.balance // "0") != "0") | { contract_id, + balance, last_update_block_height - } - ] -}' /tmp/provenance-account-nfts.json + }) + | .[:10] + ) +}' /tmp/account-ft.json ``` -Это быстрый чек наличия коллекции у получателя, а не точный список токенов. Точный выпущенный токен позже подтверждается через `nft_token`. - -2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. +2. Прочитайте NFT-коллекции для того же аккаунта. ```bash -WIDGET_ARGS_BASE64="$( - jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($author_account_id + "/widget/" + $widget_name)], - options: {with_block_height: true} - }' | base64 | tr -d '\n' -)" +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | tee /tmp/account-nft.json >/dev/null -curl -s "$MAINNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget.json >/dev/null - -jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - widget_path: ($author_account_id + "/widget/" + $widget_name), - socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], - source_preview: ( - .[$author_account_id].widget[$widget_name][""] - | split("\n")[0:8] +jq '{ + account_id, + nft_collections: ( + (.tokens // []) + | map({ + contract_id, + last_update_block_height + }) + | unique_by(.contract_id) + | .[:10] ) -}' /tmp/bos-widget.json +}' /tmp/account-nft.json ``` -3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. +3. Соберите из этих двух чтений один компактный инвентарь. ```bash -jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][""] -' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx - -WIDGET_BLOCK_HEIGHT="$( - jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][":block"] - ' /tmp/bos-widget.json -)" - -SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" -SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" -TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" - -PROVENANCE_METADATA_JSON="$( - jq -nc \ - --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" \ - --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ - --arg block_height "$WIDGET_BLOCK_HEIGHT" \ - --arg source_sha256 "$SOURCE_SHA256" '{ - title: ("BOS widget archive: " + $widget_path), - description: ("Archived from social.near on mainnet at block " + $block_height), - copies: 1, - extra: ({ - author_account_id: $author_account_id, - widget_name: $widget_name, - widget_path: $widget_path, - source_contract_id: "social.near", - source_network: "mainnet", - socialdb_block_height: ($block_height | tonumber), - source_sha256: $source_sha256 - } | @json) +jq -n \ + --slurpfile ft /tmp/account-ft.json \ + --slurpfile nft /tmp/account-nft.json ' + ($ft[0].tokens // []) as $ft_tokens + | ($nft[0].tokens // []) as $nft_tokens + | { + account_id: ($ft[0].account_id // $nft[0].account_id), + ft_contract_count: ( + $ft_tokens + | map(select((.balance // "0") != "0")) + | length + ), + nft_collection_count: ( + $nft_tokens + | map(.contract_id) + | unique + | length + ), + ft_contracts: ( + $ft_tokens + | map(select((.balance // "0") != "0") | { + contract_id, + balance, + last_update_block_height + }) + | .[:10] + ), + nft_collections: ( + $nft_tokens + | map({ + contract_id, + last_update_block_height + }) + | unique_by(.contract_id) + | .[:10] + ) }' -)" - -printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' -``` - -4. Выпустите provenance NFT в testnet. - -```bash -near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ - --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --argjson metadata "$PROVENANCE_METADATA_JSON" '{ - token_id: $token_id, - receiver_id: $receiver_id, - metadata: $metadata - }')" \ - --accountId "$SIGNER_ACCOUNT_ID" \ - --deposit 0.1 \ - --networkId testnet ``` -5. Подтвердите через точное чтение `nft_token`, что выпущенный NFT действительно несёт ожидаемые provenance-поля. - -Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. - -```bash -NFT_TOKEN_ARGS_BASE64="$( - jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ - | base64 | tr -d '\n' -)" - -for attempt in 1 2 3 4 5; do - curl -s "$TESTNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_token", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget-provenance-token.json >/dev/null - - if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then - break - fi - - sleep 1 -done - -jq '{ - token_id, - owner_id, - title: .metadata.title, - provenance: (.metadata.extra | fromjson) -}' /tmp/bos-widget-provenance-token.json -``` +Для `mike.near` на 19 апреля 2026 года эти чтения вернули десятки FT-контрактов и NFT-коллекций. Этого достаточно для частого wallet-вопроса: «какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает?» **Зачем нужен следующий шаг?** -FastNear API даёт быстрый чек наличия коллекции со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). - -## Частые задачи - -### Что этот аккаунт вообще держит прямо сейчас? - -**Начните здесь** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» - -**Следующая страница при необходимости** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» - -**Остановитесь, когда** - -- Сводка уже отвечает на вопрос по активам в одной выдаче. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Определить аккаунты по публичному ключу - -**Начните здесь** - -- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. -- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. - -**Остановитесь, когда** - -- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). -- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? - -**Начните здесь** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. -- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft), когда вопрос конкретно про владение NFT. -- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» - -**Остановитесь, когда** - -- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. - -**Переходите дальше, когда** - -- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). +Переходите к [`GET /v1/account/{account_id}/full`](https://docs.fastnear.com/ru/api/v1/account-full), когда следующий вопрос уже требует ещё и стейкинг, пулы или нативное состояние аккаунта. Переходите к contract-specific чтениям только тогда, когда вопрос меняется на «какие именно идентификаторы NFT-токенов и метаданные мне принадлежат?» ## Частые ошибки diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index 8708265..8bc884d 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -78,16 +78,16 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. -### У меня обычный стейкинг или liquid staking? +### Есть ли у этого аккаунта прямой стейкинг прямо сейчас? -Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». +Используйте этот сценарий, когда история проста: «скажи, есть ли у аккаунта видимые прямые staking pool прямо сейчас, и покажи, какие именно это пулы». Стратегия - Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. + Один раз прочитайте staking-эндпоинт и превратите видимый список пулов в ответ “да / нет”. - 01GET /v1/account/.../staking находит прямую экспозицию через пулы. - 02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них. - 03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed. + 01GET /v1/account/.../staking возвращает видимые прямые staking-позиции аккаунта. + 02jq превращает ответ в has_direct_staking_now, pool_count и pool_ids. + 03Если массив pools пуст, ответ этой поверхности просто звучит как «прямой стейкинг сейчас не виден». **Сеть** @@ -96,345 +96,154 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Официальные ссылки** - [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) -- [Liquid staking](https://docs.near.org/primitives/liquid-staking) - -Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. **Что вы делаете** - Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. -- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. -- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. +- Печатаете короткий итог “да / нет” и список видимых `pool_id`. +- На этом останавливаетесь, если только следующий вопрос уже не касается `unstake` или `withdraw` в конкретном пуле. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +ACCOUNT_ID=mike.near ``` 1. Получите представление по прямому стейкингу. ```bash curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` + | tee /tmp/account-staking.json >/dev/null -2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null +jq '{ + account_id, + has_direct_staking_now: ((.pools // []) | length > 0), + pool_count: ((.pools // []) | length), + pool_ids: ((.pools // []) | map(.pool_id)) +}' /tmp/account-staking.json ``` -3. Классифицируйте аккаунт на основе этих двух индексированных представлений. - -```bash -jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ - --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens - | { - classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) - }' -``` +На момент написания для `mike.near` здесь возвращались видимые прямые staking-пулы. Если для вашего аккаунта `pool_ids` пуст, этот эндпоинт отвечает: «прямой стейкинг сейчас не виден». **Зачем нужен следующий шаг?** -Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. +Так вопрос остаётся узким и практическим. Если ответ `true`, следующий реальный шаг обычно связан с `unstake` или `withdraw` в конкретном пуле. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. -### Заархивировать версию BOS-виджета как provenance NFT +### Какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает? -Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». +Используйте этот сценарий, когда у wallet-экрана, support-инструмента или агента уже есть `account_id` и нужен быстрый индексированный обзор holdings: FT-балансы плюс NFT-коллекции, из которых этот аккаунт сейчас что-то показывает. Стратегия - Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. - - 01GET /v1/account/.../nft проверяет, появляется ли у получателя уже запись об этой архивной коллекции. - 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. - 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте точные provenance-поля через nft_token. - -**Сети** - -- mainnet для чтения виджета из `social.near` -- testnet для безопасного mint provenance NFT в `nft.examples.testnet` + Сначала прочитайте FT-балансы, затем NFT-коллекции и только потом соберите их в один компактный индексированный инвентарь. -**Официальные ссылки** - -- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) -- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + 01GET /v1/account/.../ft даёт индексированные FT-балансы кошелька. + 02GET /v1/account/.../nft даёт NFT-коллекции, из которых этот кошелёк сейчас показывает holdings. + 03jq превращает эти два индексированных чтения в один wallet-friendly инвентарь. **Что вы делаете** -- Через FastNear API проверяете, появляется ли у получателя уже запись об архивной коллекции. -- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. -- Хешируете исходник виджета и превращаете его в provenance-метаданные. -- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. -- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. +- Читаете FT-балансы аккаунта. +- Читаете NFT-holdings аккаунта на уровне коллекций. +- Печатаете один короткий индексированный инвентарь, который можно переиспользовать в wallet- или support-сценарии. -Зафиксированный исходный виджет: +Этот пример не отвечает на вопросы про нативный баланс, стейкинг, пулы, точные NFT token ID или метаданные. -- аккаунт автора: `mob.near` -- путь виджета: `mob.near/widget/Profile` -- SocialDB-блок уровня виджета: `86494825` +NFT-эндпоинт здесь работает на уровне коллекций. Воспринимайте его как ответ на вопрос «из каких NFT-контрактов этот аккаунт сейчас что-то держит?», а не как полный per-token crawl. ```bash -API_BASE_URL=https://test.api.fastnear.com -MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com -TESTNET_RPC_URL=https://rpc.testnet.fastnear.com -AUTHOR_ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -DESTINATION_COLLECTION_ID=nft.examples.testnet -RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID + +# Пример живого значения, проверенного 19 апреля 2026 года: +# ACCOUNT_ID=mike.near ``` -1. Через FastNear API посмотрите, появляется ли у получателя уже запись об архивной коллекции. +1. Прочитайте FT-балансы аккаунта. ```bash -curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ - | tee /tmp/provenance-account-nfts.json >/dev/null - -jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_collection_entries: [ - .tokens[]? - | select(.contract_id == $destination_collection_id) - | { +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null + +jq '{ + account_id, + ft_contracts: ( + .tokens + | map(select((.balance // "0") != "0") | { contract_id, + balance, last_update_block_height - } - ] -}' /tmp/provenance-account-nfts.json + }) + | .[:10] + ) +}' /tmp/account-ft.json ``` -Это быстрый чек наличия коллекции у получателя, а не точный список токенов. Точный выпущенный токен позже подтверждается через `nft_token`. - -2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. +2. Прочитайте NFT-коллекции для того же аккаунта. ```bash -WIDGET_ARGS_BASE64="$( - jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($author_account_id + "/widget/" + $widget_name)], - options: {with_block_height: true} - }' | base64 | tr -d '\n' -)" +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | tee /tmp/account-nft.json >/dev/null -curl -s "$MAINNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget.json >/dev/null - -jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - widget_path: ($author_account_id + "/widget/" + $widget_name), - socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], - source_preview: ( - .[$author_account_id].widget[$widget_name][""] - | split("\n")[0:8] +jq '{ + account_id, + nft_collections: ( + (.tokens // []) + | map({ + contract_id, + last_update_block_height + }) + | unique_by(.contract_id) + | .[:10] ) -}' /tmp/bos-widget.json +}' /tmp/account-nft.json ``` -3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. +3. Соберите из этих двух чтений один компактный инвентарь. ```bash -jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][""] -' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx - -WIDGET_BLOCK_HEIGHT="$( - jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][":block"] - ' /tmp/bos-widget.json -)" - -SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" -SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" -TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" - -PROVENANCE_METADATA_JSON="$( - jq -nc \ - --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" \ - --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ - --arg block_height "$WIDGET_BLOCK_HEIGHT" \ - --arg source_sha256 "$SOURCE_SHA256" '{ - title: ("BOS widget archive: " + $widget_path), - description: ("Archived from social.near on mainnet at block " + $block_height), - copies: 1, - extra: ({ - author_account_id: $author_account_id, - widget_name: $widget_name, - widget_path: $widget_path, - source_contract_id: "social.near", - source_network: "mainnet", - socialdb_block_height: ($block_height | tonumber), - source_sha256: $source_sha256 - } | @json) +jq -n \ + --slurpfile ft /tmp/account-ft.json \ + --slurpfile nft /tmp/account-nft.json ' + ($ft[0].tokens // []) as $ft_tokens + | ($nft[0].tokens // []) as $nft_tokens + | { + account_id: ($ft[0].account_id // $nft[0].account_id), + ft_contract_count: ( + $ft_tokens + | map(select((.balance // "0") != "0")) + | length + ), + nft_collection_count: ( + $nft_tokens + | map(.contract_id) + | unique + | length + ), + ft_contracts: ( + $ft_tokens + | map(select((.balance // "0") != "0") | { + contract_id, + balance, + last_update_block_height + }) + | .[:10] + ), + nft_collections: ( + $nft_tokens + | map({ + contract_id, + last_update_block_height + }) + | unique_by(.contract_id) + | .[:10] + ) }' -)" - -printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' -``` - -4. Выпустите provenance NFT в testnet. - -```bash -near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ - --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --argjson metadata "$PROVENANCE_METADATA_JSON" '{ - token_id: $token_id, - receiver_id: $receiver_id, - metadata: $metadata - }')" \ - --accountId "$SIGNER_ACCOUNT_ID" \ - --deposit 0.1 \ - --networkId testnet ``` -5. Подтвердите через точное чтение `nft_token`, что выпущенный NFT действительно несёт ожидаемые provenance-поля. - -Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. - -```bash -NFT_TOKEN_ARGS_BASE64="$( - jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ - | base64 | tr -d '\n' -)" - -for attempt in 1 2 3 4 5; do - curl -s "$TESTNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_token", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget-provenance-token.json >/dev/null - - if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then - break - fi - - sleep 1 -done - -jq '{ - token_id, - owner_id, - title: .metadata.title, - provenance: (.metadata.extra | fromjson) -}' /tmp/bos-widget-provenance-token.json -``` +Для `mike.near` на 19 апреля 2026 года эти чтения вернули десятки FT-контрактов и NFT-коллекций. Этого достаточно для частого wallet-вопроса: «какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает?» **Зачем нужен следующий шаг?** -FastNear API даёт быстрый чек наличия коллекции со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). - -## Частые задачи - -### Что этот аккаунт вообще держит прямо сейчас? - -**Начните здесь** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» - -**Следующая страница при необходимости** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» - -**Остановитесь, когда** - -- Сводка уже отвечает на вопрос по активам в одной выдаче. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Определить аккаунты по публичному ключу - -**Начните здесь** - -- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. -- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. - -**Остановитесь, когда** - -- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). -- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? - -**Начните здесь** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. -- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft), когда вопрос конкретно про владение NFT. -- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» - -**Остановитесь, когда** - -- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. - -**Переходите дальше, когда** - -- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). +Переходите к [`GET /v1/account/{account_id}/full`](https://docs.fastnear.com/ru/api/v1/account-full), когда следующий вопрос уже требует ещё и стейкинг, пулы или нативное состояние аккаунта. Переходите к contract-specific чтениям только тогда, когда вопрос меняется на «какие именно идентификаторы NFT-токенов и метаданные мне принадлежат?» ## Частые ошибки diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index 8d0122c..b201047 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -2,18 +2,24 @@ ## Быстрый старт -Если контракт, predecessor и точное имя строки уже известны, сначала прочитайте именно эту строку. +Если точные FastData-ключи уже известны, читайте их напрямую. ```bash KV_BASE_URL=https://kv.test.fastnear.com CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=YOUR_TESTNET_ACCOUNT -FASTDATA_ROW=value +PREDECESSOR_ID=kv.gork-agent.testnet -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_ROW" \ +curl -s "$KV_BASE_URL/v0/multi" \ + -H 'content-type: application/json' \ + --data '{ + "keys": [ + "kv.gork-agent.testnet/kv.gork-agent.testnet/key", + "kv.gork-agent.testnet/kv.gork-agent.testnet/value" + ] + }' \ | jq '{ - latest_row: ( - .entries[0] + entries: [ + .entries[] | { current_account_id, predecessor_id, @@ -21,143 +27,102 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_RO key, value } - ) + ] }' ``` -Это самое короткое чтение FastData на странице. Полный walkthrough ниже добавляет управляемую запись, историю точного ключа и привязку к транзакции. +Это самое короткое полезное чтение FastData на странице: один запрос и сразу две точные строки. ## Готовое расследование -### Сделать одну testnet-запись FastData и проверить точные индексированные ключи +### Прочитать одну индексированную настройку, а затем привязать её к записи -Используйте это расследование, когда нужно доказать, какие именно строки FastData породила одна запись, подтвердить точную историю одного из этих ключей, а затем привязать индексированную строку к исходной транзакции. +Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки, менялось ли оно раньше и какая транзакция его создала?» Стратегия - Сделайте одну управляемую запись FastData, проверьте точные строки ключей, которые она эмитировала, а затем гидратируйте транзакцию, которая их породила. + Сначала читайте точные строки настройки, расширяйтесь до метаданных предшественника только если нужна provenance-цепочка, и переходите к Transactions API только для финального доказательства. - 01near call __fastdata_kv создаёт одну управляемую запись на testnet от вашего собственного аккаунта-предшественника. - 02get-latest-key и get-history-key проверяют точные строки FastData, которые породил этот вызов. - 03latest-by-predecessor с метаданными плюс POST /v0/transactions привязывают эти индексированные строки к исходному вызову. + 01multi или get-latest-key читают точные индексированные строки настройки. + 02get-history-key показывает, менялось ли это индексированное значение позже. + 03latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки. **Цель** -- Доказать, какие именно строки FastData породила запись, и показать, как проследить эти строки обратно до создавшей их транзакции. +- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта, подтвердить историю точного ключа для одной строки и восстановить транзакцию, которая создала обе строки. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Управляемая запись | CLI `near` | Отправляем один testnet-вызов `__fastdata_kv` с уникальным значением | Даёт запись, которую можно сразу проверить, не полагаясь на чужие старые данные | -| Точная индексированная строка | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Читаем точную строку `value`, а затем точную строку `key`, внутри одного предшественника и контракта | Доказывает точные строки FastData, которые сейчас индексированы для этой записи | -| История точного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) | Забираем историю того же точного ключа `value` | Показывает, менялась ли эта точная строка снова после записи | -| Более широкий паттерн + метаданные | KV FastData [`latest-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) | Список последних строк для того же предшественника с `include_metadata: true` | Возвращает обе эмитированные строки вместе с `tx_hash` и `receipt_id`, которые их породили | -| Гидратация транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем payload из `FunctionCall.args` | Доказывает, какой именно вызов создал индексированные строки FastData | +| Чтение точной настройки | KV FastData [`multi`](https://docs.fastnear.com/ru/fastdata/kv/multi) | Читаем известные строки `key` и `value` одним запросом | Это самое узкое полезное чтение, когда точные индексированные строки настройки уже известны | +| Чтение точной строки | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Повторно читаем одну точную строку по path-маршруту | Полезно, когда вопрос только про одну строку, а не про всю пару настройки | +| История точного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) | Проверяем историю точной строки `value` | Показывает, менялось ли именно это индексированное значение в нескольких записях | +| Необязательный мост к provenance | KV FastData [`latest-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) | Восстанавливаем `tx_hash` и `receipt_id` для индексированных строк только если provenance действительно важна | Это необязательный мост от индексированных строк обратно к одной записи | +| Необязательная гидратация транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем исходные args только когда нужно это доказательство | Финальное необязательное доказательство того, что обе строки создал один вызов | **Что должен включать полезный ответ** -- какие именно `current_account_id`, `predecessor_id` и `key` были исследованы -- как выглядит последняя индексированная строка и история этого же точного ключа -- какой `tx_hash` или `receipt_id` породил строку, если важна provenance-цепочка -- остаётся ли вопрос про строки FastData или уже перешёл к контрактно-специфичному состоянию в цепочке +- какие именно `current_account_id`, `predecessor_id` и индексированные строки настройки исследовались +- как выглядят последние индексированные строки и история точного ключа для одной из них +- какой `tx_hash` или `receipt_id` создал эти строки, только если важна provenance-цепочка +- остаётся ли вопрос про индексированные строки FastData или уже перешёл к каноническому состоянию контракта + +### Проверенный read-only testnet shell-сценарий -### Проверенный testnet shell-сценарий +Используйте этот сценарий, когда нужен полностью read-only пример на стабильных sample-данных в `kv.gork-agent.testnet`. -Используйте этот сценарий, когда у вас уже настроен testnet-аккаунт в `near` CLI и нужен один воспроизводимый сценарий записи, который можно проверить от начала до конца. +Этот минимальный контракт ведёт себя как крошечное хранилище настроек: одна запись эмитирует две индексированные строки, `key` и `value`. Сейчас sample-настройка выглядит как `test=hello`, и этого достаточно, чтобы показать shape FastData без притворства, будто перед нами более богатый прикладной объект. +Этот sample-контракт индексирует собственные записи, поэтому в этом walkthrough `CURRENT_ACCOUNT_ID` и `PREDECESSOR_ID` намеренно совпадают. **Что вы делаете** -- Пишете одну свежую запись FastData в `kv.gork-agent.testnet`. -- Ждёте, пока KV FastData проиндексирует эту транзакцию. -- Читаете точную строку `value` и точную строку `key`, которые эмитировал контракт. -- Забираете историю точного ключа `value`. -- Расширяетесь до области предшественника с метаданными, чтобы получить индексированный `tx_hash`. -- Гидратируете эту транзакцию и декодируете исходные аргументы вызова `__fastdata_kv`. +- Читаете точные индексированные строки настройки вместе. +- Повторно читаете те же строки по отдельности, чтобы был понятен shape exact-key маршрута. +- Забираете историю точного ключа для строки `value` этой настройки. +- Останавливаетесь на этом, если provenance дальше не нужна. ```bash KV_BASE_URL=https://kv.test.fastnear.com TX_BASE_URL=https://tx.test.fastnear.com CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -SIGNER_ID=YOUR_TESTNET_ACCOUNT -PREDECESSOR_ID="$SIGNER_ID" -FASTDATA_FIELD=verification -FASTDATA_VALUE="verify-$(date -u +%Y%m%dT%H%M%SZ)" - -near call "$CURRENT_ACCOUNT_ID" __fastdata_kv \ - "$(jq -nc --arg key "$FASTDATA_FIELD" --arg value "$FASTDATA_VALUE" '{key: $key, value: $value}')" \ - --accountId "$SIGNER_ID" \ - --networkId testnet \ - --gas 30000000000000 \ - | tee /tmp/kv-fastdata-call.txt - -CLI_TX_HASH="$( - awk '/Transaction Id/{print $3}' /tmp/kv-fastdata-call.txt -)" +PREDECESSOR_ID=kv.gork-agent.testnet -ATTEMPTS=0 -until curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ +curl -s "$KV_BASE_URL/v0/multi" \ -H 'content-type: application/json' \ - --data '{"include_metadata": true, "limit": 20}' \ - | tee /tmp/kv-predecessor-latest.json \ - | jq -e --arg tx_hash "$CLI_TX_HASH" ' - .entries - | map(select(.tx_hash == $tx_hash)) - | length > 0 - ' >/dev/null -do - ATTEMPTS=$((ATTEMPTS + 1)) - if [ "$ATTEMPTS" -ge 30 ]; then - echo "Timed out waiting for KV FastData to index $CLI_TX_HASH" >&2 - exit 1 - fi - sleep 2 -done - -INDEXED_TX_HASH="$( - jq -r --arg tx_hash "$CLI_TX_HASH" ' - first(.entries[] | select(.tx_hash == $tx_hash) | .tx_hash) - ' /tmp/kv-predecessor-latest.json -)" - -test "$CLI_TX_HASH" = "$INDEXED_TX_HASH" \ - && echo "CLI tx hash matches indexed metadata" - -jq --arg tx_hash "$CLI_TX_HASH" '{ - tx_hashes: ([.entries[] | select(.tx_hash == $tx_hash) | .tx_hash] | unique), - receipt_ids: ([.entries[] | select(.tx_hash == $tx_hash) | .receipt_id] | unique), + --data '{ + "keys": [ + "kv.gork-agent.testnet/kv.gork-agent.testnet/key", + "kv.gork-agent.testnet/kv.gork-agent.testnet/value" + ] + }' \ + | jq '{ entries: [ .entries[] - | select(.tx_hash == $tx_hash) | { + current_account_id, + predecessor_id, block_height, key, - value, - tx_hash, - receipt_id + value } ] - }' /tmp/kv-predecessor-latest.json - -jq '{ - latest_value_row: ( - .entries[0] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ) -}' <( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" -) + }' curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ | jq '{ latest_key_row: ( .entries[0] | { - current_account_id, - predecessor_id, + block_height, + key, + value + } + ) + }' + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ + | jq '{ + latest_value_row: ( + .entries[0] + | { block_height, key, value @@ -177,6 +142,39 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ } ] }' +``` + +На этом основной read-path заканчивается: точные строки, их exact latest-чтение и история точного ключа для той же индексированной настройки. + +### Необязательное расширение до provenance + +Переходите сюда только тогда, когда следующий вопрос уже звучит как «какая запись создала эти строки?» + +```bash + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ + -H 'content-type: application/json' \ + --data '{"include_metadata": true, "limit": 10}' \ + | tee /tmp/kv-predecessor-latest.json >/dev/null + +jq '{ + entries: [ + .entries[] + | { + block_height, + key, + value, + tx_hash, + receipt_id + } + ] +}' /tmp/kv-predecessor-latest.json + +INDEXED_TX_HASH="$( + jq -r ' + first(.entries[] | select(.key == "value") | .tx_hash) + ' /tmp/kv-predecessor-latest.json +)" curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ @@ -191,126 +189,28 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | @base64d | fromjson ), - first_receipt_id: .transactions[0].execution_outcome.outcome.receipt_ids[0] + receipt_ids: .transactions[0].execution_outcome.outcome.receipt_ids }' ``` **Зачем нужен следующий шаг?** -Этот контракт эмитирует две строки FastData из одного вызова: строку `key`, в которой лежит логическое имя поля, и строку `value`, в которой лежит само значение. Маршруты точного ключа напрямую доказывают наличие этих строк. Lookup по предшественнику — это мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, породившие эти строки. Гидратация транзакции доказывает, что индексированные строки возникли из одного вызова `__fastdata_kv` с теми же декодированными аргументами. - -Именно здесь проходит важная граница этой поверхности: строки FastData — это индексированный вывод FastData, а не обещание, что сырое RPC `view_state` покажет те же ключи. Поскольку это индексированная поверхность, свежая запись может появиться не мгновенно; дождитесь, пока в индексе появится ваш `tx_hash`, прежде чем считать latest-строки окончательными. Если вопрос пользователя меняется с «какие строки FastData были эмитированы?» на «как выглядит каноническое on-chain-состояние контракта?», переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную вам layout-структуру хранилища. - -## Частые задачи - -### Посмотреть один точный ключ FastData прямо сейчас - -**Начните здесь** - -- [Последнее по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), когда точный ключ уже известен. - -**Следующая страница при необходимости** - -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если вопрос превращается в «как менялся этот ключ?» - -**Остановитесь, когда** - -- Последняя индексированная запись уже отвечает на вопрос по FastData. - -**Переходите дальше, когда** - -- Пользователя больше не интересуют индексированные строки FastData. Переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только если знаете, какой raw-state layout нужен. - -### Превратить один точный ключ FastData в историю изменений - -**Начните здесь** - -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для path-based истории внутри одного контракта и одного предшественника. -- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) только тогда, когда вы специально хотите искать один и тот же текст ключа по всем контрактам и предшественникам. - -**Следующая страница при необходимости** - -- Возвращайтесь к [Последнему по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. - -**Остановитесь, когда** - -- Уже можно объяснить, как ключ менялся со временем. - -**Переходите дальше, когда** - -- Теперь нужно каноническое состояние контракта, а не только индексированная история FastData. Используйте собственный read-метод контракта или [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только если форма raw-state уже известна. - -### Проследить записи от одного `predecessor_id` - -**Начните здесь** - -- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. -- [Последнее по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor), когда нужны строки для одного контракта и одного предшественника, при необходимости вместе с метаданными. -- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. - -**Следующая страница при необходимости** - -- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования, или переходите к [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если главным становится provenance-вопрос. - -**Остановитесь, когда** - -- Уже можно ответить, что именно этот предшественник изменил и где. - -**Переходите дальше, когда** - -- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. - -### Привязать одну строку FastData обратно к транзакции - -**Начните здесь** - -- [Последнее по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) с `include_metadata: true`, чтобы получить `tx_hash` и `receipt_id`. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы гидратировать исходный вызов и декодировать его аргументы. - -**Следующая страница при необходимости** - -- Переходите к [Receipt by Id](https://docs.fastnear.com/ru/tx/receipt), если следующий вопрос уже не про исходный вызов, а про конкретный downstream receipt. - -**Остановитесь, когда** - -- Уже можно объяснить, какой вызов породил индексированную строку FastData. - -**Переходите дальше, когда** - -- Пользователю нужна каноническая семантика исполнения или точный статус на уровне executor. Тогда переходите к RPC transaction status. - -### Пакетно проверить несколько известных ключей - -**Начните здесь** - -- [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. - -**Следующая страница при необходимости** - -- Переведите один интересный ключ в [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. - -**Остановитесь, когда** - -- Пакетный ответ уже показывает, какие ключи действительно важны. - -**Переходите дальше, когда** +Этот sample-контракт эмитирует две индексированные строки из одной записи: `key=test` и `value=hello`. Рассматривайте их как одну индексированную настройку. Exact-key маршруты напрямую доказывают эти строки. Lookup по предшественнику с метаданными — это необязательный мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, которые создали эти строки. Гидратация транзакции доказывает, что эти индексированные строки произошли из одного вызова `__fastdata_kv` с декодированными args `{ "key": "test", "value": "hello" }`. -- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. +Именно здесь проходит важная граница этой поверхности: KV FastData отвечает на вопросы про индексированные строки FastData. Если вопрос меняется на каноническое состояние контракта, переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную layout-структуру хранилища. ## Частые ошибки -- Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. -- Путать [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) с [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key). Первый маршрут остаётся внутри одного контракта и предшественника, второй ищет одинаковый текст ключа глобально. -- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. -- Путать индексированные строки FastData с точным каноническим состоянием контракта. -- Предполагать, что ключ FastData можно напрямую запросить через raw RPC `view_state`. -- Предполагать, что свежая запись будет проиндексирована синхронно с включением в блокчейн. -- Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. +- Начинать с широких выборок по предшественнику, когда точные строки FastData уже известны. +- Считать [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) тем же самым, что и [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). Первый маршрут глобальный по строке ключа, второй остаётся внутри одного контракта и predecessor. +- Использовать KV FastData, когда настоящий вопрос про балансы, holdings или account summaries. +- Путать индексированные строки FastData с каноническим on-chain-состоянием контракта. +- Предполагать, что для каждого FastData-расследования сначала обязательно нужна новая запись. ## Полезные связанные страницы - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv) +- [Transactions API](https://docs.fastnear.com/ru/tx) - [RPC Reference](https://docs.fastnear.com/ru/rpc) -- [FastNear API](https://docs.fastnear.com/ru/api) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index 8d0122c..b201047 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -2,18 +2,24 @@ ## Быстрый старт -Если контракт, predecessor и точное имя строки уже известны, сначала прочитайте именно эту строку. +Если точные FastData-ключи уже известны, читайте их напрямую. ```bash KV_BASE_URL=https://kv.test.fastnear.com CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=YOUR_TESTNET_ACCOUNT -FASTDATA_ROW=value +PREDECESSOR_ID=kv.gork-agent.testnet -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_ROW" \ +curl -s "$KV_BASE_URL/v0/multi" \ + -H 'content-type: application/json' \ + --data '{ + "keys": [ + "kv.gork-agent.testnet/kv.gork-agent.testnet/key", + "kv.gork-agent.testnet/kv.gork-agent.testnet/value" + ] + }' \ | jq '{ - latest_row: ( - .entries[0] + entries: [ + .entries[] | { current_account_id, predecessor_id, @@ -21,143 +27,102 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_RO key, value } - ) + ] }' ``` -Это самое короткое чтение FastData на странице. Полный walkthrough ниже добавляет управляемую запись, историю точного ключа и привязку к транзакции. +Это самое короткое полезное чтение FastData на странице: один запрос и сразу две точные строки. ## Готовое расследование -### Сделать одну testnet-запись FastData и проверить точные индексированные ключи +### Прочитать одну индексированную настройку, а затем привязать её к записи -Используйте это расследование, когда нужно доказать, какие именно строки FastData породила одна запись, подтвердить точную историю одного из этих ключей, а затем привязать индексированную строку к исходной транзакции. +Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки, менялось ли оно раньше и какая транзакция его создала?» Стратегия - Сделайте одну управляемую запись FastData, проверьте точные строки ключей, которые она эмитировала, а затем гидратируйте транзакцию, которая их породила. + Сначала читайте точные строки настройки, расширяйтесь до метаданных предшественника только если нужна provenance-цепочка, и переходите к Transactions API только для финального доказательства. - 01near call __fastdata_kv создаёт одну управляемую запись на testnet от вашего собственного аккаунта-предшественника. - 02get-latest-key и get-history-key проверяют точные строки FastData, которые породил этот вызов. - 03latest-by-predecessor с метаданными плюс POST /v0/transactions привязывают эти индексированные строки к исходному вызову. + 01multi или get-latest-key читают точные индексированные строки настройки. + 02get-history-key показывает, менялось ли это индексированное значение позже. + 03latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки. **Цель** -- Доказать, какие именно строки FastData породила запись, и показать, как проследить эти строки обратно до создавшей их транзакции. +- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта, подтвердить историю точного ключа для одной строки и восстановить транзакцию, которая создала обе строки. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Управляемая запись | CLI `near` | Отправляем один testnet-вызов `__fastdata_kv` с уникальным значением | Даёт запись, которую можно сразу проверить, не полагаясь на чужие старые данные | -| Точная индексированная строка | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Читаем точную строку `value`, а затем точную строку `key`, внутри одного предшественника и контракта | Доказывает точные строки FastData, которые сейчас индексированы для этой записи | -| История точного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) | Забираем историю того же точного ключа `value` | Показывает, менялась ли эта точная строка снова после записи | -| Более широкий паттерн + метаданные | KV FastData [`latest-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) | Список последних строк для того же предшественника с `include_metadata: true` | Возвращает обе эмитированные строки вместе с `tx_hash` и `receipt_id`, которые их породили | -| Гидратация транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем payload из `FunctionCall.args` | Доказывает, какой именно вызов создал индексированные строки FastData | +| Чтение точной настройки | KV FastData [`multi`](https://docs.fastnear.com/ru/fastdata/kv/multi) | Читаем известные строки `key` и `value` одним запросом | Это самое узкое полезное чтение, когда точные индексированные строки настройки уже известны | +| Чтение точной строки | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Повторно читаем одну точную строку по path-маршруту | Полезно, когда вопрос только про одну строку, а не про всю пару настройки | +| История точного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) | Проверяем историю точной строки `value` | Показывает, менялось ли именно это индексированное значение в нескольких записях | +| Необязательный мост к provenance | KV FastData [`latest-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) | Восстанавливаем `tx_hash` и `receipt_id` для индексированных строк только если provenance действительно важна | Это необязательный мост от индексированных строк обратно к одной записи | +| Необязательная гидратация транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем исходные args только когда нужно это доказательство | Финальное необязательное доказательство того, что обе строки создал один вызов | **Что должен включать полезный ответ** -- какие именно `current_account_id`, `predecessor_id` и `key` были исследованы -- как выглядит последняя индексированная строка и история этого же точного ключа -- какой `tx_hash` или `receipt_id` породил строку, если важна provenance-цепочка -- остаётся ли вопрос про строки FastData или уже перешёл к контрактно-специфичному состоянию в цепочке +- какие именно `current_account_id`, `predecessor_id` и индексированные строки настройки исследовались +- как выглядят последние индексированные строки и история точного ключа для одной из них +- какой `tx_hash` или `receipt_id` создал эти строки, только если важна provenance-цепочка +- остаётся ли вопрос про индексированные строки FastData или уже перешёл к каноническому состоянию контракта + +### Проверенный read-only testnet shell-сценарий -### Проверенный testnet shell-сценарий +Используйте этот сценарий, когда нужен полностью read-only пример на стабильных sample-данных в `kv.gork-agent.testnet`. -Используйте этот сценарий, когда у вас уже настроен testnet-аккаунт в `near` CLI и нужен один воспроизводимый сценарий записи, который можно проверить от начала до конца. +Этот минимальный контракт ведёт себя как крошечное хранилище настроек: одна запись эмитирует две индексированные строки, `key` и `value`. Сейчас sample-настройка выглядит как `test=hello`, и этого достаточно, чтобы показать shape FastData без притворства, будто перед нами более богатый прикладной объект. +Этот sample-контракт индексирует собственные записи, поэтому в этом walkthrough `CURRENT_ACCOUNT_ID` и `PREDECESSOR_ID` намеренно совпадают. **Что вы делаете** -- Пишете одну свежую запись FastData в `kv.gork-agent.testnet`. -- Ждёте, пока KV FastData проиндексирует эту транзакцию. -- Читаете точную строку `value` и точную строку `key`, которые эмитировал контракт. -- Забираете историю точного ключа `value`. -- Расширяетесь до области предшественника с метаданными, чтобы получить индексированный `tx_hash`. -- Гидратируете эту транзакцию и декодируете исходные аргументы вызова `__fastdata_kv`. +- Читаете точные индексированные строки настройки вместе. +- Повторно читаете те же строки по отдельности, чтобы был понятен shape exact-key маршрута. +- Забираете историю точного ключа для строки `value` этой настройки. +- Останавливаетесь на этом, если provenance дальше не нужна. ```bash KV_BASE_URL=https://kv.test.fastnear.com TX_BASE_URL=https://tx.test.fastnear.com CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -SIGNER_ID=YOUR_TESTNET_ACCOUNT -PREDECESSOR_ID="$SIGNER_ID" -FASTDATA_FIELD=verification -FASTDATA_VALUE="verify-$(date -u +%Y%m%dT%H%M%SZ)" - -near call "$CURRENT_ACCOUNT_ID" __fastdata_kv \ - "$(jq -nc --arg key "$FASTDATA_FIELD" --arg value "$FASTDATA_VALUE" '{key: $key, value: $value}')" \ - --accountId "$SIGNER_ID" \ - --networkId testnet \ - --gas 30000000000000 \ - | tee /tmp/kv-fastdata-call.txt - -CLI_TX_HASH="$( - awk '/Transaction Id/{print $3}' /tmp/kv-fastdata-call.txt -)" +PREDECESSOR_ID=kv.gork-agent.testnet -ATTEMPTS=0 -until curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ +curl -s "$KV_BASE_URL/v0/multi" \ -H 'content-type: application/json' \ - --data '{"include_metadata": true, "limit": 20}' \ - | tee /tmp/kv-predecessor-latest.json \ - | jq -e --arg tx_hash "$CLI_TX_HASH" ' - .entries - | map(select(.tx_hash == $tx_hash)) - | length > 0 - ' >/dev/null -do - ATTEMPTS=$((ATTEMPTS + 1)) - if [ "$ATTEMPTS" -ge 30 ]; then - echo "Timed out waiting for KV FastData to index $CLI_TX_HASH" >&2 - exit 1 - fi - sleep 2 -done - -INDEXED_TX_HASH="$( - jq -r --arg tx_hash "$CLI_TX_HASH" ' - first(.entries[] | select(.tx_hash == $tx_hash) | .tx_hash) - ' /tmp/kv-predecessor-latest.json -)" - -test "$CLI_TX_HASH" = "$INDEXED_TX_HASH" \ - && echo "CLI tx hash matches indexed metadata" - -jq --arg tx_hash "$CLI_TX_HASH" '{ - tx_hashes: ([.entries[] | select(.tx_hash == $tx_hash) | .tx_hash] | unique), - receipt_ids: ([.entries[] | select(.tx_hash == $tx_hash) | .receipt_id] | unique), + --data '{ + "keys": [ + "kv.gork-agent.testnet/kv.gork-agent.testnet/key", + "kv.gork-agent.testnet/kv.gork-agent.testnet/value" + ] + }' \ + | jq '{ entries: [ .entries[] - | select(.tx_hash == $tx_hash) | { + current_account_id, + predecessor_id, block_height, key, - value, - tx_hash, - receipt_id + value } ] - }' /tmp/kv-predecessor-latest.json - -jq '{ - latest_value_row: ( - .entries[0] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ) -}' <( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" -) + }' curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ | jq '{ latest_key_row: ( .entries[0] | { - current_account_id, - predecessor_id, + block_height, + key, + value + } + ) + }' + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ + | jq '{ + latest_value_row: ( + .entries[0] + | { block_height, key, value @@ -177,6 +142,39 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ } ] }' +``` + +На этом основной read-path заканчивается: точные строки, их exact latest-чтение и история точного ключа для той же индексированной настройки. + +### Необязательное расширение до provenance + +Переходите сюда только тогда, когда следующий вопрос уже звучит как «какая запись создала эти строки?» + +```bash + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ + -H 'content-type: application/json' \ + --data '{"include_metadata": true, "limit": 10}' \ + | tee /tmp/kv-predecessor-latest.json >/dev/null + +jq '{ + entries: [ + .entries[] + | { + block_height, + key, + value, + tx_hash, + receipt_id + } + ] +}' /tmp/kv-predecessor-latest.json + +INDEXED_TX_HASH="$( + jq -r ' + first(.entries[] | select(.key == "value") | .tx_hash) + ' /tmp/kv-predecessor-latest.json +)" curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ @@ -191,126 +189,28 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | @base64d | fromjson ), - first_receipt_id: .transactions[0].execution_outcome.outcome.receipt_ids[0] + receipt_ids: .transactions[0].execution_outcome.outcome.receipt_ids }' ``` **Зачем нужен следующий шаг?** -Этот контракт эмитирует две строки FastData из одного вызова: строку `key`, в которой лежит логическое имя поля, и строку `value`, в которой лежит само значение. Маршруты точного ключа напрямую доказывают наличие этих строк. Lookup по предшественнику — это мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, породившие эти строки. Гидратация транзакции доказывает, что индексированные строки возникли из одного вызова `__fastdata_kv` с теми же декодированными аргументами. - -Именно здесь проходит важная граница этой поверхности: строки FastData — это индексированный вывод FastData, а не обещание, что сырое RPC `view_state` покажет те же ключи. Поскольку это индексированная поверхность, свежая запись может появиться не мгновенно; дождитесь, пока в индексе появится ваш `tx_hash`, прежде чем считать latest-строки окончательными. Если вопрос пользователя меняется с «какие строки FastData были эмитированы?» на «как выглядит каноническое on-chain-состояние контракта?», переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную вам layout-структуру хранилища. - -## Частые задачи - -### Посмотреть один точный ключ FastData прямо сейчас - -**Начните здесь** - -- [Последнее по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), когда точный ключ уже известен. - -**Следующая страница при необходимости** - -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если вопрос превращается в «как менялся этот ключ?» - -**Остановитесь, когда** - -- Последняя индексированная запись уже отвечает на вопрос по FastData. - -**Переходите дальше, когда** - -- Пользователя больше не интересуют индексированные строки FastData. Переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только если знаете, какой raw-state layout нужен. - -### Превратить один точный ключ FastData в историю изменений - -**Начните здесь** - -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для path-based истории внутри одного контракта и одного предшественника. -- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) только тогда, когда вы специально хотите искать один и тот же текст ключа по всем контрактам и предшественникам. - -**Следующая страница при необходимости** - -- Возвращайтесь к [Последнему по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. - -**Остановитесь, когда** - -- Уже можно объяснить, как ключ менялся со временем. - -**Переходите дальше, когда** - -- Теперь нужно каноническое состояние контракта, а не только индексированная история FastData. Используйте собственный read-метод контракта или [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только если форма raw-state уже известна. - -### Проследить записи от одного `predecessor_id` - -**Начните здесь** - -- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. -- [Последнее по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor), когда нужны строки для одного контракта и одного предшественника, при необходимости вместе с метаданными. -- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. - -**Следующая страница при необходимости** - -- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования, или переходите к [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если главным становится provenance-вопрос. - -**Остановитесь, когда** - -- Уже можно ответить, что именно этот предшественник изменил и где. - -**Переходите дальше, когда** - -- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. - -### Привязать одну строку FastData обратно к транзакции - -**Начните здесь** - -- [Последнее по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) с `include_metadata: true`, чтобы получить `tx_hash` и `receipt_id`. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы гидратировать исходный вызов и декодировать его аргументы. - -**Следующая страница при необходимости** - -- Переходите к [Receipt by Id](https://docs.fastnear.com/ru/tx/receipt), если следующий вопрос уже не про исходный вызов, а про конкретный downstream receipt. - -**Остановитесь, когда** - -- Уже можно объяснить, какой вызов породил индексированную строку FastData. - -**Переходите дальше, когда** - -- Пользователю нужна каноническая семантика исполнения или точный статус на уровне executor. Тогда переходите к RPC transaction status. - -### Пакетно проверить несколько известных ключей - -**Начните здесь** - -- [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. - -**Следующая страница при необходимости** - -- Переведите один интересный ключ в [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. - -**Остановитесь, когда** - -- Пакетный ответ уже показывает, какие ключи действительно важны. - -**Переходите дальше, когда** +Этот sample-контракт эмитирует две индексированные строки из одной записи: `key=test` и `value=hello`. Рассматривайте их как одну индексированную настройку. Exact-key маршруты напрямую доказывают эти строки. Lookup по предшественнику с метаданными — это необязательный мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, которые создали эти строки. Гидратация транзакции доказывает, что эти индексированные строки произошли из одного вызова `__fastdata_kv` с декодированными args `{ "key": "test", "value": "hello" }`. -- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. +Именно здесь проходит важная граница этой поверхности: KV FastData отвечает на вопросы про индексированные строки FastData. Если вопрос меняется на каноническое состояние контракта, переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную layout-структуру хранилища. ## Частые ошибки -- Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. -- Путать [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) с [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key). Первый маршрут остаётся внутри одного контракта и предшественника, второй ищет одинаковый текст ключа глобально. -- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. -- Путать индексированные строки FastData с точным каноническим состоянием контракта. -- Предполагать, что ключ FastData можно напрямую запросить через raw RPC `view_state`. -- Предполагать, что свежая запись будет проиндексирована синхронно с включением в блокчейн. -- Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. +- Начинать с широких выборок по предшественнику, когда точные строки FastData уже известны. +- Считать [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) тем же самым, что и [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). Первый маршрут глобальный по строке ключа, второй остаётся внутри одного контракта и predecessor. +- Использовать KV FastData, когда настоящий вопрос про балансы, holdings или account summaries. +- Путать индексированные строки FastData с каноническим on-chain-состоянием контракта. +- Предполагать, что для каждого FastData-расследования сначала обязательно нужна новая запись. ## Полезные связанные страницы - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv) +- [Transactions API](https://docs.fastnear.com/ru/tx) - [RPC Reference](https://docs.fastnear.com/ru/rpc) -- [FastNear API](https://docs.fastnear.com/ru/api) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index 5b0bd4d..c500ab8 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -13,7 +13,7 @@ - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных FastData-ключей, чтения истории точного ключа и привязки индексированных строк к исходной транзакции. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для чтения точных строк FastData, проверки истории точного ключа и привязки индексированной строки к исходной транзакции. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. - [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. @@ -29,14 +29,15 @@ ## Другие гайды -- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга. +- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и классификации стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для polling по оптимистичным и финализированным блокам и перехода к RPC, когда это нужно. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных хешей для дальнейшего разбора. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. - [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. -- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования и подробные разборы для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents. -- [Berry Club: как восстанавливать исторические доски](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Используйте Transactions API, RPC get_lines и проигрывание draw-вызовов, чтобы восстанавливать доски Berry Club по историческим эпохам. -- [OutLayer: трассировка запроса и разрешения воркером](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать завершающие receipts. +- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика, а затем для более глубоких case study, когда они действительно нужны. +- [Berry Club Case Study: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Case study, который начинается с живой доски Berry Club через RPC get_lines, а затем использует Transactions API, чтобы восстановить одну более раннюю эпоху. +- [OutLayer Case Study: трассировка запроса и разрешения воркером](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Case study, который использует Transactions API, чтобы связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать завершающие receipts. +- [Расширенный паттерн provenance для SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs.md): Один расширенный паттерн, который начинается с читаемого значения SocialDB и восстанавливает транзакцию записи за ним. ## Снапшоты diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 46fe899..85ae2ca 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -1072,16 +1072,16 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. -### У меня обычный стейкинг или liquid staking? +### Есть ли у этого аккаунта прямой стейкинг прямо сейчас? -Используйте этот сценарий, когда история звучит так: «покажи, связан ли этот кошелёк с прямыми staking pool, liquid staking token или и с тем и с другим». +Используйте этот сценарий, когда история проста: «скажи, есть ли у аккаунта видимые прямые staking pool прямо сейчас, и покажи, какие именно это пулы». Стратегия - Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. + Один раз прочитайте staking-эндпоинт и превратите видимый список пулов в ответ “да / нет”. - 01GET /v1/account/.../staking находит прямую экспозицию через пулы. - 02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них. - 03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed. + 01GET /v1/account/.../staking возвращает видимые прямые staking-позиции аккаунта. + 02jq превращает ответ в has_direct_staking_now, pool_count и pool_ids. + 03Если массив pools пуст, ответ этой поверхности просто звучит как «прямой стейкинг сейчас не виден». **Сеть** @@ -1090,345 +1090,154 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Официальные ссылки** - [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) -- [Liquid staking](https://docs.near.org/primitives/liquid-staking) - -Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. **Что вы делаете** - Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. -- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. -- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. +- Печатаете короткий итог “да / нет” и список видимых `pool_id`. +- На этом останавливаетесь, если только следующий вопрос уже не касается `unstake` или `withdraw` в конкретном пуле. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +ACCOUNT_ID=mike.near ``` 1. Получите представление по прямому стейкингу. ```bash curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` + | tee /tmp/account-staking.json >/dev/null -2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null +jq '{ + account_id, + has_direct_staking_now: ((.pools // []) | length > 0), + pool_count: ((.pools // []) | length), + pool_ids: ((.pools // []) | map(.pool_id)) +}' /tmp/account-staking.json ``` -3. Классифицируйте аккаунт на основе этих двух индексированных представлений. - -```bash -jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ - --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens - | { - classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) - }' -``` +На момент написания для `mike.near` здесь возвращались видимые прямые staking-пулы. Если для вашего аккаунта `pool_ids` пуст, этот эндпоинт отвечает: «прямой стейкинг сейчас не виден». **Зачем нужен следующий шаг?** -Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. +Так вопрос остаётся узким и практическим. Если ответ `true`, следующий реальный шаг обычно связан с `unstake` или `withdraw` в конкретном пуле. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. -### Заархивировать версию BOS-виджета как provenance NFT +### Какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает? -Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». +Используйте этот сценарий, когда у wallet-экрана, support-инструмента или агента уже есть `account_id` и нужен быстрый индексированный обзор holdings: FT-балансы плюс NFT-коллекции, из которых этот аккаунт сейчас что-то показывает. Стратегия - Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. - - 01GET /v1/account/.../nft проверяет, появляется ли у получателя уже запись об этой архивной коллекции. - 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. - 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте точные provenance-поля через nft_token. - -**Сети** + Сначала прочитайте FT-балансы, затем NFT-коллекции и только потом соберите их в один компактный индексированный инвентарь. -- mainnet для чтения виджета из `social.near` -- testnet для безопасного mint provenance NFT в `nft.examples.testnet` - -**Официальные ссылки** - -- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) -- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + 01GET /v1/account/.../ft даёт индексированные FT-балансы кошелька. + 02GET /v1/account/.../nft даёт NFT-коллекции, из которых этот кошелёк сейчас показывает holdings. + 03jq превращает эти два индексированных чтения в один wallet-friendly инвентарь. **Что вы делаете** -- Через FastNear API проверяете, появляется ли у получателя уже запись об архивной коллекции. -- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. -- Хешируете исходник виджета и превращаете его в provenance-метаданные. -- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. -- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. +- Читаете FT-балансы аккаунта. +- Читаете NFT-holdings аккаунта на уровне коллекций. +- Печатаете один короткий индексированный инвентарь, который можно переиспользовать в wallet- или support-сценарии. -Зафиксированный исходный виджет: +Этот пример не отвечает на вопросы про нативный баланс, стейкинг, пулы, точные NFT token ID или метаданные. -- аккаунт автора: `mob.near` -- путь виджета: `mob.near/widget/Profile` -- SocialDB-блок уровня виджета: `86494825` +NFT-эндпоинт здесь работает на уровне коллекций. Воспринимайте его как ответ на вопрос «из каких NFT-контрактов этот аккаунт сейчас что-то держит?», а не как полный per-token crawl. ```bash -API_BASE_URL=https://test.api.fastnear.com -MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com -TESTNET_RPC_URL=https://rpc.testnet.fastnear.com -AUTHOR_ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -DESTINATION_COLLECTION_ID=nft.examples.testnet -RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID + +# Пример живого значения, проверенного 19 апреля 2026 года: +# ACCOUNT_ID=mike.near ``` -1. Через FastNear API посмотрите, появляется ли у получателя уже запись об архивной коллекции. +1. Прочитайте FT-балансы аккаунта. ```bash -curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ - | tee /tmp/provenance-account-nfts.json >/dev/null +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null -jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_collection_entries: [ - .tokens[]? - | select(.contract_id == $destination_collection_id) - | { +jq '{ + account_id, + ft_contracts: ( + .tokens + | map(select((.balance // "0") != "0") | { contract_id, + balance, last_update_block_height - } - ] -}' /tmp/provenance-account-nfts.json + }) + | .[:10] + ) +}' /tmp/account-ft.json ``` -Это быстрый чек наличия коллекции у получателя, а не точный список токенов. Точный выпущенный токен позже подтверждается через `nft_token`. - -2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. +2. Прочитайте NFT-коллекции для того же аккаунта. ```bash -WIDGET_ARGS_BASE64="$( - jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($author_account_id + "/widget/" + $widget_name)], - options: {with_block_height: true} - }' | base64 | tr -d '\n' -)" +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | tee /tmp/account-nft.json >/dev/null -curl -s "$MAINNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget.json >/dev/null - -jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - widget_path: ($author_account_id + "/widget/" + $widget_name), - socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], - source_preview: ( - .[$author_account_id].widget[$widget_name][""] - | split("\n")[0:8] +jq '{ + account_id, + nft_collections: ( + (.tokens // []) + | map({ + contract_id, + last_update_block_height + }) + | unique_by(.contract_id) + | .[:10] ) -}' /tmp/bos-widget.json +}' /tmp/account-nft.json ``` -3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. +3. Соберите из этих двух чтений один компактный инвентарь. ```bash -jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][""] -' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx - -WIDGET_BLOCK_HEIGHT="$( - jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][":block"] - ' /tmp/bos-widget.json -)" - -SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" -SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" -TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" - -PROVENANCE_METADATA_JSON="$( - jq -nc \ - --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" \ - --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ - --arg block_height "$WIDGET_BLOCK_HEIGHT" \ - --arg source_sha256 "$SOURCE_SHA256" '{ - title: ("BOS widget archive: " + $widget_path), - description: ("Archived from social.near on mainnet at block " + $block_height), - copies: 1, - extra: ({ - author_account_id: $author_account_id, - widget_name: $widget_name, - widget_path: $widget_path, - source_contract_id: "social.near", - source_network: "mainnet", - socialdb_block_height: ($block_height | tonumber), - source_sha256: $source_sha256 - } | @json) +jq -n \ + --slurpfile ft /tmp/account-ft.json \ + --slurpfile nft /tmp/account-nft.json ' + ($ft[0].tokens // []) as $ft_tokens + | ($nft[0].tokens // []) as $nft_tokens + | { + account_id: ($ft[0].account_id // $nft[0].account_id), + ft_contract_count: ( + $ft_tokens + | map(select((.balance // "0") != "0")) + | length + ), + nft_collection_count: ( + $nft_tokens + | map(.contract_id) + | unique + | length + ), + ft_contracts: ( + $ft_tokens + | map(select((.balance // "0") != "0") | { + contract_id, + balance, + last_update_block_height + }) + | .[:10] + ), + nft_collections: ( + $nft_tokens + | map({ + contract_id, + last_update_block_height + }) + | unique_by(.contract_id) + | .[:10] + ) }' -)" - -printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' -``` - -4. Выпустите provenance NFT в testnet. - -```bash -near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ - --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --argjson metadata "$PROVENANCE_METADATA_JSON" '{ - token_id: $token_id, - receiver_id: $receiver_id, - metadata: $metadata - }')" \ - --accountId "$SIGNER_ACCOUNT_ID" \ - --deposit 0.1 \ - --networkId testnet ``` -5. Подтвердите через точное чтение `nft_token`, что выпущенный NFT действительно несёт ожидаемые provenance-поля. - -Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. - -```bash -NFT_TOKEN_ARGS_BASE64="$( - jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ - | base64 | tr -d '\n' -)" - -for attempt in 1 2 3 4 5; do - curl -s "$TESTNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_token", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget-provenance-token.json >/dev/null - - if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then - break - fi - - sleep 1 -done - -jq '{ - token_id, - owner_id, - title: .metadata.title, - provenance: (.metadata.extra | fromjson) -}' /tmp/bos-widget-provenance-token.json -``` +Для `mike.near` на 19 апреля 2026 года эти чтения вернули десятки FT-контрактов и NFT-коллекций. Этого достаточно для частого wallet-вопроса: «какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает?» **Зачем нужен следующий шаг?** -FastNear API даёт быстрый чек наличия коллекции со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. Точное чтение `nft_token` в testnet подтверждает, что mint действительно превратил это в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). - -## Частые задачи - -### Что этот аккаунт вообще держит прямо сейчас? - -**Начните здесь** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» - -**Следующая страница при необходимости** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» - -**Остановитесь, когда** - -- Сводка уже отвечает на вопрос по активам в одной выдаче. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Определить аккаунты по публичному ключу - -**Начните здесь** - -- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. -- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. - -**Остановитесь, когда** - -- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). -- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? - -**Начните здесь** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. -- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft), когда вопрос конкретно про владение NFT. -- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» - -**Остановитесь, когда** - -- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. - -**Переходите дальше, когда** - -- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). +Переходите к [`GET /v1/account/{account_id}/full`](https://docs.fastnear.com/ru/api/v1/account-full), когда следующий вопрос уже требует ещё и стейкинг, пулы или нативное состояние аккаунта. Переходите к contract-specific чтениям только тогда, когда вопрос меняется на «какие именно идентификаторы NFT-токенов и метаданные мне принадлежат?» ## Частые ошибки @@ -1639,18 +1448,24 @@ https://kv.test.fastnear.com ## Быстрый старт -Если контракт, predecessor и точное имя строки уже известны, сначала прочитайте именно эту строку. +Если точные FastData-ключи уже известны, читайте их напрямую. ```bash KV_BASE_URL=https://kv.test.fastnear.com CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=YOUR_TESTNET_ACCOUNT -FASTDATA_ROW=value +PREDECESSOR_ID=kv.gork-agent.testnet -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_ROW" \ +curl -s "$KV_BASE_URL/v0/multi" \ + -H 'content-type: application/json' \ + --data '{ + "keys": [ + "kv.gork-agent.testnet/kv.gork-agent.testnet/key", + "kv.gork-agent.testnet/kv.gork-agent.testnet/value" + ] + }' \ | jq '{ - latest_row: ( - .entries[0] + entries: [ + .entries[] | { current_account_id, predecessor_id, @@ -1658,143 +1473,102 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$FASTDATA_RO key, value } - ) + ] }' ``` -Это самое короткое чтение FastData на странице. Полный walkthrough ниже добавляет управляемую запись, историю точного ключа и привязку к транзакции. +Это самое короткое полезное чтение FastData на странице: один запрос и сразу две точные строки. ## Готовое расследование -### Сделать одну testnet-запись FastData и проверить точные индексированные ключи +### Прочитать одну индексированную настройку, а затем привязать её к записи -Используйте это расследование, когда нужно доказать, какие именно строки FastData породила одна запись, подтвердить точную историю одного из этих ключей, а затем привязать индексированную строку к исходной транзакции. +Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки, менялось ли оно раньше и какая транзакция его создала?» Стратегия - Сделайте одну управляемую запись FastData, проверьте точные строки ключей, которые она эмитировала, а затем гидратируйте транзакцию, которая их породила. + Сначала читайте точные строки настройки, расширяйтесь до метаданных предшественника только если нужна provenance-цепочка, и переходите к Transactions API только для финального доказательства. - 01near call __fastdata_kv создаёт одну управляемую запись на testnet от вашего собственного аккаунта-предшественника. - 02get-latest-key и get-history-key проверяют точные строки FastData, которые породил этот вызов. - 03latest-by-predecessor с метаданными плюс POST /v0/transactions привязывают эти индексированные строки к исходному вызову. + 01multi или get-latest-key читают точные индексированные строки настройки. + 02get-history-key показывает, менялось ли это индексированное значение позже. + 03latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки. **Цель** -- Доказать, какие именно строки FastData породила запись, и показать, как проследить эти строки обратно до создавшей их транзакции. +- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта, подтвердить историю точного ключа для одной строки и восстановить транзакцию, которая создала обе строки. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Управляемая запись | CLI `near` | Отправляем один testnet-вызов `__fastdata_kv` с уникальным значением | Даёт запись, которую можно сразу проверить, не полагаясь на чужие старые данные | -| Точная индексированная строка | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Читаем точную строку `value`, а затем точную строку `key`, внутри одного предшественника и контракта | Доказывает точные строки FastData, которые сейчас индексированы для этой записи | -| История точного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) | Забираем историю того же точного ключа `value` | Показывает, менялась ли эта точная строка снова после записи | -| Более широкий паттерн + метаданные | KV FastData [`latest-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) | Список последних строк для того же предшественника с `include_metadata: true` | Возвращает обе эмитированные строки вместе с `tx_hash` и `receipt_id`, которые их породили | -| Гидратация транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем payload из `FunctionCall.args` | Доказывает, какой именно вызов создал индексированные строки FastData | +| Чтение точной настройки | KV FastData [`multi`](https://docs.fastnear.com/ru/fastdata/kv/multi) | Читаем известные строки `key` и `value` одним запросом | Это самое узкое полезное чтение, когда точные индексированные строки настройки уже известны | +| Чтение точной строки | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Повторно читаем одну точную строку по path-маршруту | Полезно, когда вопрос только про одну строку, а не про всю пару настройки | +| История точного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) | Проверяем историю точной строки `value` | Показывает, менялось ли именно это индексированное значение в нескольких записях | +| Необязательный мост к provenance | KV FastData [`latest-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) | Восстанавливаем `tx_hash` и `receipt_id` для индексированных строк только если provenance действительно важна | Это необязательный мост от индексированных строк обратно к одной записи | +| Необязательная гидратация транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем исходные args только когда нужно это доказательство | Финальное необязательное доказательство того, что обе строки создал один вызов | **Что должен включать полезный ответ** -- какие именно `current_account_id`, `predecessor_id` и `key` были исследованы -- как выглядит последняя индексированная строка и история этого же точного ключа -- какой `tx_hash` или `receipt_id` породил строку, если важна provenance-цепочка -- остаётся ли вопрос про строки FastData или уже перешёл к контрактно-специфичному состоянию в цепочке +- какие именно `current_account_id`, `predecessor_id` и индексированные строки настройки исследовались +- как выглядят последние индексированные строки и история точного ключа для одной из них +- какой `tx_hash` или `receipt_id` создал эти строки, только если важна provenance-цепочка +- остаётся ли вопрос про индексированные строки FastData или уже перешёл к каноническому состоянию контракта + +### Проверенный read-only testnet shell-сценарий -### Проверенный testnet shell-сценарий +Используйте этот сценарий, когда нужен полностью read-only пример на стабильных sample-данных в `kv.gork-agent.testnet`. -Используйте этот сценарий, когда у вас уже настроен testnet-аккаунт в `near` CLI и нужен один воспроизводимый сценарий записи, который можно проверить от начала до конца. +Этот минимальный контракт ведёт себя как крошечное хранилище настроек: одна запись эмитирует две индексированные строки, `key` и `value`. Сейчас sample-настройка выглядит как `test=hello`, и этого достаточно, чтобы показать shape FastData без притворства, будто перед нами более богатый прикладной объект. +Этот sample-контракт индексирует собственные записи, поэтому в этом walkthrough `CURRENT_ACCOUNT_ID` и `PREDECESSOR_ID` намеренно совпадают. **Что вы делаете** -- Пишете одну свежую запись FastData в `kv.gork-agent.testnet`. -- Ждёте, пока KV FastData проиндексирует эту транзакцию. -- Читаете точную строку `value` и точную строку `key`, которые эмитировал контракт. -- Забираете историю точного ключа `value`. -- Расширяетесь до области предшественника с метаданными, чтобы получить индексированный `tx_hash`. -- Гидратируете эту транзакцию и декодируете исходные аргументы вызова `__fastdata_kv`. +- Читаете точные индексированные строки настройки вместе. +- Повторно читаете те же строки по отдельности, чтобы был понятен shape exact-key маршрута. +- Забираете историю точного ключа для строки `value` этой настройки. +- Останавливаетесь на этом, если provenance дальше не нужна. ```bash KV_BASE_URL=https://kv.test.fastnear.com TX_BASE_URL=https://tx.test.fastnear.com CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -SIGNER_ID=YOUR_TESTNET_ACCOUNT -PREDECESSOR_ID="$SIGNER_ID" -FASTDATA_FIELD=verification -FASTDATA_VALUE="verify-$(date -u +%Y%m%dT%H%M%SZ)" - -near call "$CURRENT_ACCOUNT_ID" __fastdata_kv \ - "$(jq -nc --arg key "$FASTDATA_FIELD" --arg value "$FASTDATA_VALUE" '{key: $key, value: $value}')" \ - --accountId "$SIGNER_ID" \ - --networkId testnet \ - --gas 30000000000000 \ - | tee /tmp/kv-fastdata-call.txt - -CLI_TX_HASH="$( - awk '/Transaction Id/{print $3}' /tmp/kv-fastdata-call.txt -)" +PREDECESSOR_ID=kv.gork-agent.testnet -ATTEMPTS=0 -until curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ +curl -s "$KV_BASE_URL/v0/multi" \ -H 'content-type: application/json' \ - --data '{"include_metadata": true, "limit": 20}' \ - | tee /tmp/kv-predecessor-latest.json \ - | jq -e --arg tx_hash "$CLI_TX_HASH" ' - .entries - | map(select(.tx_hash == $tx_hash)) - | length > 0 - ' >/dev/null -do - ATTEMPTS=$((ATTEMPTS + 1)) - if [ "$ATTEMPTS" -ge 30 ]; then - echo "Timed out waiting for KV FastData to index $CLI_TX_HASH" >&2 - exit 1 - fi - sleep 2 -done - -INDEXED_TX_HASH="$( - jq -r --arg tx_hash "$CLI_TX_HASH" ' - first(.entries[] | select(.tx_hash == $tx_hash) | .tx_hash) - ' /tmp/kv-predecessor-latest.json -)" - -test "$CLI_TX_HASH" = "$INDEXED_TX_HASH" \ - && echo "CLI tx hash matches indexed metadata" - -jq --arg tx_hash "$CLI_TX_HASH" '{ - tx_hashes: ([.entries[] | select(.tx_hash == $tx_hash) | .tx_hash] | unique), - receipt_ids: ([.entries[] | select(.tx_hash == $tx_hash) | .receipt_id] | unique), + --data '{ + "keys": [ + "kv.gork-agent.testnet/kv.gork-agent.testnet/key", + "kv.gork-agent.testnet/kv.gork-agent.testnet/value" + ] + }' \ + | jq '{ entries: [ .entries[] - | select(.tx_hash == $tx_hash) | { + current_account_id, + predecessor_id, block_height, key, - value, - tx_hash, - receipt_id + value } ] - }' /tmp/kv-predecessor-latest.json - -jq '{ - latest_value_row: ( - .entries[0] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ) -}' <( - curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" -) + }' curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ | jq '{ latest_key_row: ( .entries[0] | { - current_account_id, - predecessor_id, + block_height, + key, + value + } + ) + }' + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ + | jq '{ + latest_value_row: ( + .entries[0] + | { block_height, key, value @@ -1814,6 +1588,39 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ } ] }' +``` + +На этом основной read-path заканчивается: точные строки, их exact latest-чтение и история точного ключа для той же индексированной настройки. + +### Необязательное расширение до provenance + +Переходите сюда только тогда, когда следующий вопрос уже звучит как «какая запись создала эти строки?» + +```bash + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ + -H 'content-type: application/json' \ + --data '{"include_metadata": true, "limit": 10}' \ + | tee /tmp/kv-predecessor-latest.json >/dev/null + +jq '{ + entries: [ + .entries[] + | { + block_height, + key, + value, + tx_hash, + receipt_id + } + ] +}' /tmp/kv-predecessor-latest.json + +INDEXED_TX_HASH="$( + jq -r ' + first(.entries[] | select(.key == "value") | .tx_hash) + ' /tmp/kv-predecessor-latest.json +)" curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ @@ -1828,127 +1635,29 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | @base64d | fromjson ), - first_receipt_id: .transactions[0].execution_outcome.outcome.receipt_ids[0] + receipt_ids: .transactions[0].execution_outcome.outcome.receipt_ids }' ``` **Зачем нужен следующий шаг?** -Этот контракт эмитирует две строки FastData из одного вызова: строку `key`, в которой лежит логическое имя поля, и строку `value`, в которой лежит само значение. Маршруты точного ключа напрямую доказывают наличие этих строк. Lookup по предшественнику — это мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, породившие эти строки. Гидратация транзакции доказывает, что индексированные строки возникли из одного вызова `__fastdata_kv` с теми же декодированными аргументами. - -Именно здесь проходит важная граница этой поверхности: строки FastData — это индексированный вывод FastData, а не обещание, что сырое RPC `view_state` покажет те же ключи. Поскольку это индексированная поверхность, свежая запись может появиться не мгновенно; дождитесь, пока в индексе появится ваш `tx_hash`, прежде чем считать latest-строки окончательными. Если вопрос пользователя меняется с «какие строки FastData были эмитированы?» на «как выглядит каноническое on-chain-состояние контракта?», переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную вам layout-структуру хранилища. - -## Частые задачи - -### Посмотреть один точный ключ FastData прямо сейчас - -**Начните здесь** - -- [Последнее по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), когда точный ключ уже известен. - -**Следующая страница при необходимости** - -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если вопрос превращается в «как менялся этот ключ?» - -**Остановитесь, когда** - -- Последняя индексированная запись уже отвечает на вопрос по FastData. - -**Переходите дальше, когда** - -- Пользователя больше не интересуют индексированные строки FastData. Переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только если знаете, какой raw-state layout нужен. - -### Превратить один точный ключ FastData в историю изменений - -**Начните здесь** - -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для path-based истории внутри одного контракта и одного предшественника. -- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) только тогда, когда вы специально хотите искать один и тот же текст ключа по всем контрактам и предшественникам. - -**Следующая страница при необходимости** - -- Возвращайтесь к [Последнему по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. - -**Остановитесь, когда** - -- Уже можно объяснить, как ключ менялся со временем. - -**Переходите дальше, когда** - -- Теперь нужно каноническое состояние контракта, а не только индексированная история FastData. Используйте собственный read-метод контракта или [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только если форма raw-state уже известна. - -### Проследить записи от одного `predecessor_id` - -**Начните здесь** - -- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. -- [Последнее по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor), когда нужны строки для одного контракта и одного предшественника, при необходимости вместе с метаданными. -- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. - -**Следующая страница при необходимости** - -- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования, или переходите к [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если главным становится provenance-вопрос. - -**Остановитесь, когда** - -- Уже можно ответить, что именно этот предшественник изменил и где. - -**Переходите дальше, когда** +Этот sample-контракт эмитирует две индексированные строки из одной записи: `key=test` и `value=hello`. Рассматривайте их как одну индексированную настройку. Exact-key маршруты напрямую доказывают эти строки. Lookup по предшественнику с метаданными — это необязательный мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, которые создали эти строки. Гидратация транзакции доказывает, что эти индексированные строки произошли из одного вызова `__fastdata_kv` с декодированными args `{ "key": "test", "value": "hello" }`. -- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. - -### Привязать одну строку FastData обратно к транзакции - -**Начните здесь** - -- [Последнее по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) с `include_metadata: true`, чтобы получить `tx_hash` и `receipt_id`. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы гидратировать исходный вызов и декодировать его аргументы. - -**Следующая страница при необходимости** - -- Переходите к [Receipt by Id](https://docs.fastnear.com/ru/tx/receipt), если следующий вопрос уже не про исходный вызов, а про конкретный downstream receipt. - -**Остановитесь, когда** - -- Уже можно объяснить, какой вызов породил индексированную строку FastData. - -**Переходите дальше, когда** - -- Пользователю нужна каноническая семантика исполнения или точный статус на уровне executor. Тогда переходите к RPC transaction status. - -### Пакетно проверить несколько известных ключей - -**Начните здесь** - -- [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. - -**Следующая страница при необходимости** - -- Переведите один интересный ключ в [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. - -**Остановитесь, когда** - -- Пакетный ответ уже показывает, какие ключи действительно важны. - -**Переходите дальше, когда** - -- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. +Именно здесь проходит важная граница этой поверхности: KV FastData отвечает на вопросы про индексированные строки FastData. Если вопрос меняется на каноническое состояние контракта, переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную layout-структуру хранилища. ## Частые ошибки -- Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. -- Путать [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) с [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key). Первый маршрут остаётся внутри одного контракта и предшественника, второй ищет одинаковый текст ключа глобально. -- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. -- Путать индексированные строки FastData с точным каноническим состоянием контракта. -- Предполагать, что ключ FastData можно напрямую запросить через raw RPC `view_state`. -- Предполагать, что свежая запись будет проиндексирована синхронно с включением в блокчейн. -- Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. +- Начинать с широких выборок по предшественнику, когда точные строки FastData уже известны. +- Считать [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) тем же самым, что и [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). Первый маршрут глобальный по строке ключа, второй остаётся внутри одного контракта и predecessor. +- Использовать KV FastData, когда настоящий вопрос про балансы, holdings или account summaries. +- Путать индексированные строки FastData с каноническим on-chain-состоянием контракта. +- Предполагать, что для каждого FastData-расследования сначала обязательно нужна новая запись. ## Полезные связанные страницы - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv) +- [Transactions API](https://docs.fastnear.com/ru/tx) - [RPC Reference](https://docs.fastnear.com/ru/rpc) -- [FastNear API](https://docs.fastnear.com/ru/api) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) @@ -2289,79 +1998,91 @@ https://testnet.neardata.xyz ## Быстрый старт -Начните с двух helper-маршрутов, которые показывают, что изменилось прямо сейчас. +Начните с одного недавнего финализированного блока и сначала запросите самую маленькую возможную touch-сводку. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID -curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print "optimistic:", $2}' \ - | tr -d '\r' +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" -curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print "final:", $2}' \ - | tr -d '\r' +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | jq --arg target "$TARGET_ACCOUNT_ID" '{ + height: .block.header.height, + hash: .block.header.hash, + direct_tx_count: ([.shards[].chunk.transactions[]? + | select((.transaction.receiver_id // .receiver_id) == $target)] | length), + incoming_receipt_count: ([.shards[].chunk.receipts[]? + | select(.receiver_id == $target)] | length), + outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? + | select( + (.receipt.receiver_id // "") == $target + or (.execution_outcome.outcome.executor_id // "") == $target + )] | length), + state_change_count: ([.shards[].state_changes[]? + | select((.change.account_id // "") == $target)] | length) + } | . + { + touched: ( + (.direct_tx_count > 0) + or (.incoming_receipt_count > 0) + or (.outcome_hit_count > 0) + or (.state_change_count > 0) + ) + }' ``` -Это даёт текущие optimistic и final redirect target до того, как вы запрашиваете полные документы блоков. +Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. ## Готовое расследование -### Поймать новый блок как можно раньше, а затем подтвердить его после finality +### Был ли мой контракт затронут в последнем финализированном блоке? -Используйте это расследование, когда нужно заметить новый блок как можно раньше, но финальный ответ всё равно должен опираться на финализированный блок и иногда на точное чтение через RPC. +Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC. Стратегия - Пусть NEAR Data сначала скажет, что что-то изменилось, а затем переиспользуйте то же семейство блоков для стабильного подтверждения. + Зафиксируйтесь на одном финализированном блоке, просканируйте всё семейство блока по целевому аккаунту, а затем оставьте только компактную сводку и идентификаторы, которые действительно стоит разбирать дальше. - 01block-optimistic или last-block-optimistic дают самый ранний полезный сигнал. - 02block или last-block-final подтверждают, что то же наблюдение дошло до финализированной истории. - 03RPC block нужен только в самом конце, когда уже известна точная высота или хеш. + 01last-block-final даёт одну стабильную высоту блока без угадывания. + 02block — это главный read: он уже содержит транзакции, receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» + 03Только если ответ «да», расширяйтесь дальше: сохраняйте найденные shard id, tx hash и receipt id, а затем передавайте именно эти идентификаторы в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). **Цель** -- Как можно раньше заметить одно свежее изменение в семействе блоков, а затем подтвердить, какой финализированный блок его догнал. +- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только shard id, счётчики и sample-идентификаторы для следующего шага. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](https://docs.fastnear.com/ru/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | -| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](https://docs.fastnear.com/ru/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | -| Стабильное подтверждение | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) или [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | -| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | -| Точный разбор через RPC | RPC [Блок по ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Блок по высоте](https://docs.fastnear.com/ru/rpc/block/block-by-height) | Получаем точный блок, как только понятно, какой именно блок важен | Здесь уже имеет смысл RPC, если нужен тот самый блок-объект, который вернул бы сам протокол | +| Последняя стабильная точка | NEAR Data [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Получаем высоту одного финализированного блока без угадывания | Даёт стабильную отправную точку для всего вопроса | +| Всё семейство блока | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) | Сканируем транзакции, receipts, результаты исполнения receipts и изменения состояния по целевому аккаунту | Это главная поверхность ответа на вопрос «был ли затронут мой контракт?» | +| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Используем, когда нужны только высота, хеш, время или заголовки чанков | Позволяет не тянуть более широкий payload блока, когда фильтрация по контракту не нужна | +| Необязательный follow-up по шарду | NEAR Data [`block-chunk`](https://docs.fastnear.com/ru/neardata/block-chunk) или [`block-shard`](https://docs.fastnear.com/ru/neardata/block-shard) | Повторно открываем только затронутый шард, если нужен более глубокий payload | Полезно, когда вы уже знаете, какой шард mattered | +| Точные поверхности для продолжения | [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc) | Переиспользуем найденные tx hash или receipt id только если нужна полная история исполнения | NEAR Data позволяет сначала понять, нужен ли вообще переход дальше | **Что должен включать полезный ответ** -- какой redirect target и какой разрешённый оптимистичный блок впервые запустили расследование -- когда helper для finality догнал его и в какой блок он разрешился -- изменил ли точный разбор через RPC интерпретацию +- финализированную высоту и хеш +- ответ “затронут / не затронут” +- счётчики прямых транзакций, входящих receipts, outcome-hit и state changes +- по одному sample tx hash или receipt id на категорию, когда он есть -### Shell-сценарий от оптимистичного сигнала к финализированному подтверждению +### Shell-сценарий от финализированного блока к ответу по контракту -Используйте этот сценарий, когда нужно сразу заметить свежее изменение в семействе блоков, а затем доказать, какой финализированный блок его догнал, и подтвердить именно эту высоту через RPC. +Используйте этот сценарий, когда целевой аккаунт уже известен и нужен один свежий финализированный ответ, а не длинный polling-цикл. **Что вы делаете** -- Смотрите redirect, который возвращает `GET /v0/last_block/optimistic`. -- Загружаете разрешённый оптимистичный блок и сохраняете его высоту и хеш. -- Смотрите redirect, который возвращает `GET /v0/last_block/final`, и сохраняете финализированный counterpart. -- Сравниваете оптимистичное и финализированное наблюдения, а затем переиспользуете финализированную высоту в RPC `block` по высоте. +- Получаете redirect target для последнего финализированного блока. +- Один раз загружаете полный документ блока. +- Собираете один компактный ответ по одному `TARGET_ACCOUNT_ID`. +- Получаете ответ “да / нет” плюс минимально полезные счётчики и sample-идентификаторы. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -RPC_URL=https://rpc.mainnet.fastnear.com - -OPTIMISTIC_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -printf 'Optimistic redirect target: %s\n' "$OPTIMISTIC_LOCATION" - -curl -s "$NEARDATA_BASE_URL$OPTIMISTIC_LOCATION" \ - | tee /tmp/neardata-optimistic-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' +TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -2369,135 +2090,140 @@ FINAL_LOCATION="$( | tr -d '\r' )" +BLOCK_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | sed -E 's#.*/([0-9]+)$#\1#')" + printf 'Final redirect target: %s\n' "$FINAL_LOCATION" +printf 'Final block height: %s\n' "$BLOCK_HEIGHT" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' - -jq -n \ - --slurpfile optimistic /tmp/neardata-optimistic-block.json \ - --slurpfile final /tmp/neardata-final-block.json '{ - optimistic: { - height: $optimistic[0].block.header.height, - hash: $optimistic[0].block.header.hash - }, - final: { - height: $final[0].block.header.height, - hash: $final[0].block.header.hash - }, - same_height: ( - $optimistic[0].block.header.height - == $final[0].block.header.height - ) - }' - -BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "block", - params: { - block_id: ($block_height | tonumber) + | tee /tmp/neardata-block.json >/dev/null + +jq --arg target "$TARGET_ACCOUNT_ID" ' + ( + [ + .shards[] + | .chunk.transactions[]? + | select((.transaction.receiver_id // .receiver_id) == $target) + | (.transaction.hash // .hash) + ] + ) as $txs + | ( + [ + .shards[] + | .chunk.receipts[]? + | select(.receiver_id == $target) + | .receipt_id + ] + ) as $receipts + | ( + [ + .shards[] + | .receipt_execution_outcomes[]? + | select( + (.receipt.receiver_id // "") == $target + or (.execution_outcome.outcome.executor_id // "") == $target + ) + | .tx_hash + | select(. != null) + ] + | unique + ) as $outcomes + | ( + [ + .shards[] + | .state_changes[]? + | select((.change.account_id // "") == $target) + | .type + ] + ) as $state_changes + | { + height: .block.header.height, + hash: .block.header.hash, + touched: ( + ($txs | length) > 0 + or ($receipts | length) > 0 + or ($outcomes | length) > 0 + or ($state_changes | length) > 0 + ), + direct_tx_count: ($txs | length), + incoming_receipt_count: ($receipts | length), + outcome_hit_count: ($outcomes | length), + state_change_count: ($state_changes | length), + sample_direct_tx: ($txs[0] // null), + sample_incoming_receipt: ($receipts[0] // null), + sample_outcome_tx_hash: ($outcomes[0] // null) } - }')" \ - | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json ``` -**Зачем нужен следующий шаг?** - -Так вы получаете обе стороны истории: самый ранний оптимистичный якорь и более поздний финализированный якорь. Как только helper для finality сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. - -## Частые задачи - -### Отслеживать последний оптимистичный блок - -**Начните здесь** - -- [Оптимистичный блок](https://docs.fastnear.com/ru/neardata/block-optimistic) для самого свежего чтения по семейству блоков. - -**Следующая страница при необходимости** - -- [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic), если нужен маршрут перенаправления, который всегда ведёт к самому новому оптимистичному блоку. - -**Остановитесь, когда** - -- Уже можно сообщить о последнем оптимистичном блоке или зафиксировать отставание по свежести. - -**Переходите дальше, когда** - -- Нужна finalized-стабильность вместо максимальной свежести. Переходите к [Финализированному блоку по высоте](https://docs.fastnear.com/ru/neardata/block) или [Перенаправлению на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final). - -### Безопасно отслеживать ход финализации блоков - -**Начните здесь** - -- [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block), когда уже известна нужная высота. -- [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers), когда достаточно чтения заголовков. - -**Следующая страница при необходимости** - -- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final), когда клиент должен следовать за самым новым финализированным блоком без предварительного вычисления высоты. - -**Остановитесь, когда** - -- Уже можно показывать движение финализированных блоков без перехода к более глубоким протокольным деталям. - -**Переходите дальше, когда** - -- Пользователю нужны точные поля блока или семантика транзакций. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). - -### Использовать маршруты перенаправления в клиенте опроса - -**Начните здесь** - -- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) или [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) в зависимости от требуемой свежести. - -**Следующая страница при необходимости** - -- Следуйте по URL блока, который вернул маршрут перенаправления, и уже там читайте нужные данные. - -**Остановитесь, когда** - -- Клиент надёжно проходит по маршруту перенаправления и получает нужный ресурс блока. - -**Переходите дальше, когда** - -- Само перенаправление мешает клиенту. Тогда переходите на прямые маршруты блоков. - -### Перейти от опроса свежих блоков к точному RPC-разбору - -**Начните здесь** +Если позже понадобятся более богатые списки или разбор по шардам, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. -- Используйте подходящий маршрут NEAR Data, чтобы найти недавний блок или событие в семействе блоков, которое нужно исследовать. +Необязательное расширение: если всё же нужны `touched_shards`, их можно вычислить из того же сохранённого блока, не утяжеляя основной ответ: -**Следующая страница при необходимости** +```bash +jq --arg target "$TARGET_ACCOUNT_ID" ' + [ + .shards[] + | .shard_id as $shard_id + | select( + ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) + or ([.chunk.receipts[]? | .receiver_id] | index($target)) + or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) + or ([.state_changes[]? | .change.account_id] | index($target)) + ) + | $shard_id + ] | unique +' /tmp/neardata-block.json +``` -- [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или другой RPC-метод, как только станет понятно, какой именно блок или следующий объект для проверки нужен. +Если в этом ответе `touched: true` и нужен один follow-up на уровне шарда, откройте только первый затронутый шард: -**Остановитесь, когда** +```bash +TOUCHED_SHARD_ID="$( + jq -r --arg target "$TARGET_ACCOUNT_ID" ' + first( + .shards[] + | .shard_id as $shard_id + | select( + ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) + or ([.chunk.receipts[]? | .receiver_id] | index($target)) + or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) + or ([.state_changes[]? | .change.account_id] | index($target)) + ) + | $shard_id + ) // empty + ' /tmp/neardata-block.json +)" -- Уже можно чётко назвать недавний блок, который заслуживает проверки через RPC. +if [ -n "$TOUCHED_SHARD_ID" ]; then + curl -s "$NEARDATA_BASE_URL/v0/block/$BLOCK_HEIGHT/chunk/$TOUCHED_SHARD_ID" \ + | jq '{ + shard_id: .header.shard_id, + chunk_hash: .header.chunk_hash, + tx_hashes: ([.transactions[]? | (.transaction.hash // .hash)] | .[:5]), + receipt_ids: ([.receipts[]? | .receipt_id] | .[:5]), + receipt_receivers: ([.receipts[]? | .receiver_id] | .[:5]) + }' +fi +``` -**Переходите дальше, когда** +**Зачем нужен следующий шаг?** -- Пользователь просит точную структуру данных в терминах протокола, а не просто свежее чтение. +Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один из sample-идентификаторов уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. ## Частые ошибки -- Воспринимать NEAR Data как push-стрим, а не как API для опроса. -- Начинать с RPC, когда настоящая задача — мониторинг свежих блоков. -- Забывать, что невалидный ключ может вернуть `401` ещё до перенаправления, а сами перенаправления подходят не каждому HTTP-клиенту. -- Оставаться на NEAR Data после того, как пользователь уже попросил точные протокольные детали блока. +- Воспринимать NEAR Data как push-стрим, а не как polling- или point-read API. +- Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. +- Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. +- Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. +- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data точные shard id, tx hash и receipt id. ## Полезные связанные страницы - [NEAR Data API](https://docs.fastnear.com/ru/neardata) -- [RPC Reference](https://docs.fastnear.com/ru/rpc) - [Transactions API](https://docs.fastnear.com/ru/tx) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) @@ -2694,89 +2420,31 @@ curl -s "$RPC_URL" \ ## Отправка и отслеживание транзакции -Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» +### Двухчастный паттерн: отправить транзакцию или отследить уже известный tx hash до финального исполнения + +Базовый паттерн: + +- `broadcast_tx_async` для отправки +- `tx` с `wait_until: "FINAL"` для отслеживания +- `EXPERIMENTAL_tx_status` только если следующий вопрос уже про receipts -### Отправить транзакцию и затем проследить её от хеша до финального исполнения +Этот walkthrough намеренно разбит на две части: -Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. +- отправить новую подписанную транзакцию и сохранить возвращённый хеш +- отследить один известный исторический tx hash с воспроизводимым выводом -Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: +Для части про отслеживание используется одна зафиксированная историческая транзакция, поэтому status-lookup идёт через архивный хост: - хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - signer: `mike.near` - receiver: `social.near` -- высота блока включения: `79574923` -- высота блока исполнения receipt для записи в SocialDB: `79574924` - -Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. - - Стратегия - Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. - - 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. - 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. - 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. - -**Что вы здесь решаете** - -- какой эндпоинт отправки брать первым -- что опрашивать после того, как у вас появился tx hash -- как `wait_until` связан с included-, optimistic- и final-гарантиями -- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` - -```mermaid -flowchart LR - S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] - A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] - T --> F["Транзакция полностью завершена"] - T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] - F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] -``` - -| Метод | Когда использовать | Что вернётся | Роль здесь | -| --- | --- | --- | --- | -| [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | -| [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | -| [`broadcast_tx_commit`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | -| [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | -| [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | - -**Карта статусов и ожидания** - -Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. - -| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | -| --- | --- | --- | -| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | -| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | -| `INCLUDED_FINAL` | блок включения уже финален | `tx` | -| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | -| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | +- `https://archival-rpc.mainnet.fastnear.com` -Практическое различие очень простое: - -- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash -- используйте `tx` как обычный цикл опроса -- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу - -**Что вы делаете** - -- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. -- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. -- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. -- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. +1. Отправьте новую подписанную транзакцию и сохраните возвращённый хеш. ```bash RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near -RECEIVER_ID=social.near -``` -1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. - -```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data '{ @@ -2788,40 +2456,16 @@ curl -s "$RPC_URL" \ | jq . ``` -В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. +Этот первый шаг нужен только для формы отправки. Именно возвращённый хеш вы потом будете отслеживать для своей живой транзакции. -2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. +2. Отслеживайте один известный tx hash, пока не получите самый простой финальный ответ. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "INCLUDED_FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' +RPC_URL=https://archival-rpc.mainnet.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near ``` -Что здесь важно заметить: - -- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality -- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения -- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt - -3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. - ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ @@ -2839,18 +2483,12 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - status: .result.status, + transaction_status: .result.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Что здесь важно заметить: - -- для исторической tx этот вызов тоже возвращается сразу -- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» -- для многих приложений именно здесь и стоит остановиться - -4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. +3. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда для этого известного tx уже нужен уровень receipts. ```bash curl -s "$RPC_URL" \ @@ -2869,82 +2507,49 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - status: .result.status, transaction_handoff: .result.transaction_outcome.outcome.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». - -5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transaction_handoff: .transactions[0].transaction_outcome.outcome.status - }' -``` - -Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» - -**Рекомендуемый паттерн** - -- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. -- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. -- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. +Если вы хотите, чтобы узел ждал за вас, используйте [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Но базовый паттерн на этой странице остаётся таким: отправка через `broadcast_tx_async`, затем отслеживание хеша через `tx`. ## Механика аккаунтов и ключей Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. -### Проверить и удалить старые function-call-ключи Near Social +### Может ли этот access key прямо сейчас вызвать этот контракт? -Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. +Используйте этот сценарий, когда у вас уже есть аккаунт, один public key и целевой контракт, а вам нужен простой ответ да или нет до того, как вы начнёте что-то подписывать. Стратегия - Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. + Сначала отфильтруйте ключи аккаунта, затем прочитайте точный ключ и только потом классифицируйте его права. - 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. - 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. - 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. + 01RPC view_access_key_list сужает список до ключей, которые вообще могут относиться к целевому контракту. + 02RPC view_access_key даёт точный permission-object для того public key, которым вы реально можете подписывать. + 03jq превращает этот permission-object в full_access, function_call_match, receiver_mismatch или method_not_allowed. **Что вы делаете** -- Через сам RPC получаете полный список access key аккаунта. -- Сужаете этот список до function-call-ключей, привязанных к `social.near`. -- Точно проверяете один выбранный ключ перед удалением. -- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. - -Сразу важны два ограничения: - -- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. -- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». +- Получаете access key аккаунта и сужаете список до нужного контракта. +- Точно проверяете тот ключ, которым собираетесь подписывать. +- Решаете, может ли он вызвать этот receiver и method, не выходя за пределы RPC. ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export SOCIAL_RECEIVER_ID=social.near -export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' -export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' -export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +TARGET_CONTRACT_ID=crossword.puzzle.near +TARGET_METHOD_NAME=new_puzzle +TARGET_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_WANT_TO_CHECK' + +# Пример живых значений, проверенных 19 апреля 2026 года: +# ACCOUNT_ID=mike.near +# TARGET_CONTRACT_ID=crossword.puzzle.near +# TARGET_METHOD_NAME=new_puzzle +# TARGET_PUBLIC_KEY='ed25519:otwaB1X88ocpmUdC1B5XaifucfDLmLKaonb26KqTj96' ``` -1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. +1. Получите ключи аккаунта и сузьте их до целевого контракта. ```bash curl -s "$RPC_URL" \ @@ -2959,32 +2564,35 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/fastnear-access-keys.json >/dev/null - -jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' - .result.keys[] - | select((.access_key.permission | type) == "object") - | select(.access_key.permission.FunctionCall.receiver_id == $receiver) - | { - public_key, - nonce: .access_key.nonce, - receiver_id: .access_key.permission.FunctionCall.receiver_id, - method_names: .access_key.permission.FunctionCall.method_names, - allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") - } -' /tmp/fastnear-access-keys.json + | tee /tmp/access-key-list.json >/dev/null + +jq --arg target_contract_id "$TARGET_CONTRACT_ID" '{ + candidate_keys: [ + .result.keys[] + | select( + .access_key.permission == "FullAccess" + or ( + (.access_key.permission | type) == "object" + and .access_key.permission.FunctionCall.receiver_id == $target_contract_id + ) + ) + | { + public_key, + nonce: .access_key.nonce, + permission: .access_key.permission + } + ] +}' /tmp/access-key-list.json ``` -Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. - -2. Ещё раз проверьте конкретный ключ перед удалением. +2. Прочитайте точное состояние того ключа, который хотите оценить. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$DELETE_PUBLIC_KEY" '{ + --arg public_key "$TARGET_PUBLIC_KEY" '{ jsonrpc: "2.0", id: "fastnear", method: "query", @@ -2995,432 +2603,103 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | jq '{nonce: .result.nonce, permission: .result.permission}' -``` - -3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. + | tee /tmp/exact-access-key.json >/dev/null -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - is_function_call: true, - limit: 10 - }')" \ - | jq '{ - account_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_success - } - ] - }' +jq '{nonce: .result.nonce, permission: .result.permission}' /tmp/exact-access-key.json ``` -Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. - -4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. - -Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. +3. Превратите этот permission-object в ответ да или нет для этого контракта и метода. ```bash -SIGNED_TX_BASE64="$( - node --input-type=module <<'EOF' - -const { - ACCOUNT_ID, - NETWORK_ID = 'mainnet', - RPC_URL = 'https://rpc.mainnet.fastnear.com', - DELETE_PUBLIC_KEY, - FULL_ACCESS_PUBLIC_KEY, - FULL_ACCESS_PRIVATE_KEY, -} = process.env; - -for (const name of [ - 'ACCOUNT_ID', - 'DELETE_PUBLIC_KEY', - 'FULL_ACCESS_PUBLIC_KEY', - 'FULL_ACCESS_PRIVATE_KEY', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); -const derivedPublicKey = keyPair.getPublicKey().toString(); - -if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { - throw new Error( - `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: ACCOUNT_ID, - public_key: FULL_ACCESS_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const transaction = transactions.createTransaction( - ACCOUNT_ID, - utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), - ACCOUNT_ID, - BigInt(accessKey.nonce) + 1n, - [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -)" +jq -n \ + --slurpfile key /tmp/exact-access-key.json \ + --arg target_contract_id "$TARGET_CONTRACT_ID" \ + --arg target_method_name "$TARGET_METHOD_NAME" ' + ($key[0].result.permission) as $permission + | if $permission == "FullAccess" then + { + can_call_now: true, + reason: "full_access" + } + elif $permission.FunctionCall.receiver_id != $target_contract_id then + { + can_call_now: false, + reason: "receiver_mismatch", + receiver_id: $permission.FunctionCall.receiver_id + } + elif ( + ($permission.FunctionCall.method_names | length) == 0 + or ($permission.FunctionCall.method_names | index($target_method_name)) + ) then + { + can_call_now: true, + reason: ( + if ($permission.FunctionCall.method_names | length) == 0 + then "function_call_any_method" + else "function_call_method_match" + end + ), + allowance: ($permission.FunctionCall.allowance // "unlimited") + } + else + { + can_call_now: false, + reason: "method_not_allowed", + allowed_methods: $permission.FunctionCall.method_names + } + end' ``` -5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. +Для примерного ключа `mike.near` выше на 19 апреля 2026 года ответ получается `can_call_now: true`: это function-call-key для `crossword.puzzle.near`, а `method_names: ["new_puzzle"]` явно разрешает тот метод, который мы проверяем. -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` +**Зачем нужен следующий шаг?** -6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. +`view_access_key_list` — самый быстрый фильтр на уровне контракта. `view_access_key` — точная проверка полномочий для того public key, которым вы действительно хотите пользоваться. Если ответ `false`, вам нужен другой ключ или другая схема permissions, а не более глубокая историческая трассировка. -```bash -if curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } - }')" \ - | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' - .result.keys[] - | select(.public_key == $public_key) - ' >/dev/null; then - echo "Key is still present: $DELETE_PUBLIC_KEY" -else - echo "Key deleted: $DELETE_PUBLIC_KEY" -fi -``` +### Нужно ли этому получателю сначала зарегистрировать FT storage? -**Зачем нужен следующий шаг?** +Используйте этот сценарий, когда история звучит так: «я собираюсь отправить FT-токен и хочу получить простой ответ “нужен ли сначала `storage_deposit`?”». -Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. + Стратегия + Сначала прочитайте storage-состояние получателя и остановитесь, как только станет понятно, может ли `ft_transfer` уже пройти. -### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? + 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. + 02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит. + 03jq превращает эти два чтения в один ответ: «перевод уже может идти» или «сначала нужен `storage_deposit`». -Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. +**Сеть** - Стратегия - Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. +- testnet - 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. - 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. - 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. +**Официальные ссылки** -**Что вы делаете** +- [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) +- [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) -- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. -- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. -- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. -- Подтягиваете кандидата по транзакциям и различаете три разных ключа: - - ключ, который был добавлен - - public key верхнеуровневого signer - - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` +В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Здесь важен именно read-only-ответ: нужен ли сначала `storage_deposit`, или путь перевода уже может продолжаться. -Сразу важны три детали про nonce: +**Что вы делаете** -- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. -- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. -- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. +- Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. +- Получаете точный минимальный размер storage deposit на этом же контракте. +- Останавливаетесь, как только понимаете: `ft_transfer` уже может идти или сначала нужен `storage_deposit`. ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' - -# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: -# export ACCOUNT_ID=mike.near -# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet ``` -1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. +1. Проверьте, зарегистрирован ли получатель на FT-контракте. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$TARGET_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } - }')" \ - | tee /tmp/key-origin-view.json >/dev/null - -CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" -ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" -SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" -SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" - -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_public_key "$TARGET_PUBLIC_KEY" \ - --argjson current_nonce "$CURRENT_NONCE" \ - --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ - --argjson search_from "$SEARCH_FROM" \ - --argjson search_to "$SEARCH_TO" \ - --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ - account_id: $account_id, - target_public_key: $target_public_key, - current_nonce: $current_nonce, - estimated_receipt_block: $estimated_receipt_block, - search_from_tx_block_height: $search_from, - search_to_tx_block_height: $search_to, - permission: ($permission | fromjson) - }' -``` - -Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. - -2. Ищите историю аккаунта только внутри этого диапазона блоков. - -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_tx_block_height "$SEARCH_FROM" \ - --argjson to_tx_block_height "$SEARCH_TO" '{ - account_id: $account_id, - is_real_signer: true, - from_tx_block_height: $from_tx_block_height, - to_tx_block_height: $to_tx_block_height, - desc: false, - limit: 50 - }')" \ - | tee /tmp/key-origin-candidates.json >/dev/null - -jq '{ - txs_count, - candidate_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_signer, - is_real_signer, - is_predecessor, - is_receiver - } - ] -}' /tmp/key-origin-candidates.json -``` - -Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. - -3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. - -```bash -TX_HASHES_JSON="$( - jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json -)" - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ - | tee /tmp/key-origin-transactions.json >/dev/null - -jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "direct", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $tx.transaction.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }), - ($tx.transaction.actions[]? - | .Delegate? - | .delegate_action as $delegate - | $delegate.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "delegated", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $delegate.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - tx_block_hash: $tx.execution_outcome.block_hash, - receiver_id: $tx.transaction.receiver_id - } + . -' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json -``` - -Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. - -Для примерного ключа `mike.near` выше совпадение оказывается делегированным: - -- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` -- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` -- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` -- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` - -4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. - -```bash -ADD_KEY_RECEIPT_ID="$( - jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | .receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) - | .receipt.receipt_id - ' /tmp/key-origin-transactions.json | head -n 1 -)" - -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receipt_block_height: .receipt.block_height, - tx_block_height: .receipt.tx_block_height, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - transaction_hash: .receipt.transaction_hash - }' -``` - -Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. - -**Зачем нужен следующий шаг?** - -Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. - -### Проверить регистрацию FT storage и затем перевести токены - -Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». - - Стратегия - Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. - - 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. - 02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит. - 03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог. - -**Сеть** - -- testnet - -**Официальные ссылки** - -- [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) -- [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) - -В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. - -**Что вы делаете** - -- Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. -- При необходимости получаете минимальный размер storage deposit. -- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. -- Подтверждаете баланс получателя тем же view-методом самого контракта. - -```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet -export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' -export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' -export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' -``` - -1. Проверьте, зарегистрирован ли получатель на FT-контракте. - -```bash -STORAGE_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - +STORAGE_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ @@ -3445,7 +2724,7 @@ jq '{ }' /tmp/ft-storage-balance.json ``` -2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. +2. Получите минимальный storage deposit на этом же контракте. ```bash MIN_STORAGE_YOCTO="$( @@ -3470,214 +2749,39 @@ MIN_STORAGE_YOCTO="$( printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" ``` -3. Определите одну переиспользуемую функцию подписи для function-call к контракту. - -Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. - -```bash -sign_function_call() { - METHOD_NAME="$1" \ - ARGS_JSON="$2" \ - DEPOSIT_YOCTO="$3" \ - GAS_TGAS="$4" \ - node --input-type=module <<'EOF' - -const { - NETWORK_ID = 'testnet', - RPC_URL = 'https://rpc.testnet.fastnear.com', - TOKEN_CONTRACT_ID, - SENDER_ACCOUNT_ID, - SENDER_PUBLIC_KEY, - SENDER_PRIVATE_KEY, - METHOD_NAME, - ARGS_JSON, - DEPOSIT_YOCTO = '0', - GAS_TGAS = '100', -} = process.env; - -for (const name of [ - 'TOKEN_CONTRACT_ID', - 'SENDER_ACCOUNT_ID', - 'SENDER_PUBLIC_KEY', - 'SENDER_PRIVATE_KEY', - 'METHOD_NAME', - 'ARGS_JSON', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); -const signer = await InMemorySigner.fromKeyPair( - NETWORK_ID, - SENDER_ACCOUNT_ID, - keyPair -); - -const derivedPublicKey = keyPair.getPublicKey().toString(); -if (derivedPublicKey !== SENDER_PUBLIC_KEY) { - throw new Error( - `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: SENDER_ACCOUNT_ID, - public_key: SENDER_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const action = transactions.functionCall( - METHOD_NAME, - Buffer.from(ARGS_JSON), - BigInt(GAS_TGAS) * 10n ** 12n, - BigInt(DEPOSIT_YOCTO) -); - -const transaction = transactions.createTransaction( - SENDER_ACCOUNT_ID, - utils.PublicKey.fromString(SENDER_PUBLIC_KEY), - TOKEN_CONTRACT_ID, - BigInt(accessKey.nonce) + 1n, - [action], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - SENDER_ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -} -``` - -4. При необходимости сначала зарегистрируйте storage для получателя. - -```bash -if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then - SIGNED_TX_BASE64="$( - sign_function_call \ - storage_deposit \ - "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id, - registration_only: true - }')" \ - "$MIN_STORAGE_YOCTO" \ - 100 - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash - }' -fi -``` - -5. После готовности storage переведите FT. - -```bash -SIGNED_TX_BASE64="$( - sign_function_call \ - ft_transfer \ - "$(jq -nc \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ - receiver_id: $receiver_id, - amount: $amount, - memo: "FastNear RPC example" - }')" \ - 1 \ - 100 -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Подтвердите FT-баланс получателя тем же view-методом контракта. +3. Превратите эти два чтения в один ответ о готовности перевода. ```bash -RECEIVER_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - receiver_balance: (.result.result | implode | fromjson) +jq -n \ + --slurpfile balance /tmp/ft-storage-balance.json \ + --slurpfile bounds /tmp/ft-storage-bounds.json \ + --arg receiver_account_id "$RECEIVER_ACCOUNT_ID" ' + ( + $balance[0].result.result + | if length == 0 then null else (implode | fromjson) end + ) as $storage + | ( + $bounds[0].result.result + | implode + | fromjson + ) as $bounds + | { + receiver_account_id: $receiver_account_id, + receiver_registered: ($storage != null), + current_storage: $storage, + minimum_storage_deposit_yocto: $bounds.min, + next_step: ( + if $storage != null + then "получатель уже зарегистрирован; ft_transfer может продолжаться" + else "сначала отправьте storage_deposit, потом делайте ft_transfer" + end + ) }' ``` **Зачем нужен следующий шаг?** -Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +Это чистый RPC-вопрос в этом workflow: «зарегистрирован ли уже получатель и какой минимальный депозит потребует контракт, если нет?» Подписанный write-path зависит уже от вашего wallet, CLI или backend-интеграции, поэтому в самый маленький core RPC-пример он не входит. ## Чтения контракта и сырое состояние @@ -3685,42 +2789,12 @@ curl -s "$RPC_URL" \ ### Прочитать счётчик прямо из состояния контракта, а потом подтвердить его через view-метод -Используйте этот сценарий, когда история простая: «я знаю, что этот контракт держит счётчик, но можно ли прочитать это число напрямую из storage, не вызывая код контракта?» - -В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: оба чтения должны совпасть в тот момент, когда вы их запускаете: - -- `view_state` читает сырой ключ `STATE` прямо из storage контракта -- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API - - Стратегия - Сначала прочитайте raw storage, затем декодируйте байты, а потом дайте контракту подтвердить тот же ответ через view-метод. - - 01RPC view_state читает сырой ключ STATE, не запуская код контракта. - 02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта. - 03RPC call_function get_num — это удобная перепроверка того, что raw-state-чтение и view-метод по-прежнему дают один и тот же ответ. - -Здесь важнее ментальная модель, чем сам счётчик: +Используйте этот сценарий, когда вы уже знаете точное семейство storage-ключей и хотите увидеть самый короткий контраст между raw state и публичным методом чтения контракта. -- `view_state` — это прямое чтение storage из trie -- `call_function` исполняет read-only-метод контракта -- оба способа могут ответить на один и тот же вопрос, но делают разную работу +Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`: -```mermaid -flowchart LR - S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] - R --> D["Декодировать base64 + Borsh"] - D --> N["Знаковое значение счётчика"] - C["RPC call_function get_num"] --> J["JSON-результат метода"] - N --> X["Сравнить"] - J --> X - X --> A["Одно и то же текущее значение"] -``` - -**Что вы делаете** - -- Читаете сырой ключ `STATE` из storage контракта. -- Декодируете возвращённые байты в текущее знаковое значение счётчика. -- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. +- `view_state` читает сырой ключ `STATE` напрямую +- `call_function get_num` спрашивает у контракта то же текущее число ```bash export NETWORK_ID=testnet @@ -3729,7 +2803,7 @@ export CONTRACT_ID=counter.near-examples.testnet export STATE_PREFIX_BASE64=U1RBVEU= ``` -1. Сначала прочитайте сырое состояние контракта. +1. Сначала прочитайте сырой ключ `STATE`. ```bash curl -s "$RPC_URL" \ @@ -3750,46 +2824,28 @@ curl -s "$RPC_URL" \ | tee /tmp/counter-view-state.json >/dev/null jq '{ - block_height: .result.block_height, - key_base64: .result.values[0].key, + key: (.result.values[0].key | @base64d), value_base64: .result.values[0].value }' /tmp/counter-view-state.json - -jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json ``` -Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. +Здесь должен появиться `key: "STATE"`. Это и есть тот случай, когда `view_state` уместен: семейство ключей вам уже известно заранее. -2. Декодируйте байты значения в знаковое число счётчика. +2. Декодируйте сырые байты. ```bash RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" -python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . +python3 - "$RAW_VALUE_BASE64" <<'PY' raw = base64.b64decode(sys.argv[1]) - -print(json.dumps({ - "value_base64": sys.argv[1], - "bytes": list(raw), - "hex": raw.hex(), - "signed_i8": int.from_bytes(raw, "little", signed=True), - "unsigned_u8": int.from_bytes(raw, "little", signed=False), -})) +print(int.from_bytes(raw, "little", signed=True)) PY ``` -Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. - -Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. - -Переиспользуемый рецепт здесь короткий: +Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть сложнее, но правило то же: сначала байты, потом схема. -- `view_state` возвращает сырые байты в base64 -- вы декодируете эти байты по известной схеме хранения контракта -- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема - -3. Теперь спросите контракт более привычным способом и сравните. +3. Теперь спросите контракт привычным способом и сравните. ```bash curl -s "$RPC_URL" \ @@ -3814,7 +2870,7 @@ jq '{ }' /tmp/counter-call-function.json ``` -4. Сравните оба ответа напрямую. +4. Сравните оба ответа. ```bash RAW_STATE_NUMBER="$( @@ -3838,35 +2894,24 @@ jq -n \ }' ``` -Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: - -- `view_state` ответил на вопрос, прочитав storage напрямую -- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта - **Зачем нужен следующий шаг?** -Используйте `view_state`, когда настоящий вопрос относится к точному storage и вы уже знаете семейство ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). +Используйте `view_state`, когда вы уже знаете точное семейство storage-ключей и хотите raw bytes. Используйте `call_function`, когда вам нужен публичный метод чтения самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда уже стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). -## Точные чтения NEAR Social и BOS +## Точные чтения SocialDB -Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. +Оставайтесь на точных чтениях через `call_function get`, когда вы уже знаете нужный ключ SocialDB. На обычном RPC raw `view_state` для `social.near` не подходит как обучающий путь, потому что состояние контракта слишком велико для прямого чтения. -### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? +### Прочитать один пост SocialDB ровно в том виде, как он хранится сейчас -Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». +Используйте этот сценарий, когда продукту, support-инструменту или агенту уже известен аккаунт и нужен живой payload поста из SocialDB без перехода в историю транзакций. Стратегия - Спросите у social.near ровно о двух вещах, которые важны до подписи. - - 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. - 02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near. - 03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer. + Сначала прочитайте текущий ключ поста, затем получите точный payload этого поста из social.near. -Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: - -- есть ли у целевого аккаунта storage на `social.near`? -- если есть, осталось ли там ещё место? -- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? + 01RPC call_function get по mike.near/index/post показывает, какой ключ поста сейчас активен. + 02RPC call_function get по mike.near/post/main возвращает точный сохранённый payload поста. + 03Если следующий вопрос становится «какая транзакция это записала?», переключайтесь на [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). **Официальные ссылки** @@ -3874,52 +2919,23 @@ jq -n \ **Что вы делаете** -- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. -- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. -- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. -- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». +- Читаете текущий указатель поста под `mike.near/index/post`. +- Используете этот ключ, чтобы получить точный payload по `mike.near/post/`. +- Останавливаетесь на точном JSON и расширяетесь в историю только тогда, когда действительно нужна provenance. ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com export SOCIAL_CONTRACT_ID=social.near export ACCOUNT_ID=mike.near -export SIGNER_ACCOUNT_ID=mike.near -``` - -1. Сначала проверьте сам аккаунт signer. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } - }')" \ - | tee /tmp/social-publish-signer.json >/dev/null - -jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - signer_account_id: $signer_account_id, - amount: .result.amount, - locked: .result.locked, - storage_usage: .result.storage_usage -}' /tmp/social-publish-signer.json ``` -Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. - -2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. +1. Сначала прочитайте текущий указатель поста. ```bash -SOCIAL_STORAGE_ARGS_BASE64="$( +INDEX_POST_ARGS_BASE64="$( jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id + keys: [($account_id + "/index/post")] }' | base64 | tr -d '\n' )" @@ -3927,208 +2943,53 @@ curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + --arg args_base64 "$INDEX_POST_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { request_type: "call_function", account_id: $account_id, - method_name: "get_account_storage", + method_name: "get", args_base64: $args_base64, finality: "final" } }')" \ - | tee /tmp/social-account-storage.json >/dev/null + | tee /tmp/social-index-post.json >/dev/null -jq --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - storage: (.result.result | implode | fromjson), - storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) -}' /tmp/social-account-storage.json +jq --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | { + account_id: $account_id, + index_entry: (.[$account_id].index.post | fromjson), + current_post_key: (.[$account_id].index.post | fromjson | .key) + } +' /tmp/social-index-post.json ``` -Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. +На момент написания текущим ключом поста для `mike.near` был `main`. -3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. +2. Прочитайте точный payload этого поста. ```bash -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - jq -n --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - signer_matches_target: true, - permission_granted: true, - reason: "owner write" - }' -else - WRITE_PERMISSION_ARGS_BASE64="$( - jq -nc \ - --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ - --arg key "$ACCOUNT_ID" '{ - predecessor_id: $predecessor_id, - key: $key - }' | base64 | tr -d '\n' - )" +POST_KEY="$( + jq -r --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].index.post + | fromjson + | .key + ' /tmp/social-index-post.json +)" - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - signer_matches_target: false, - permission_granted: (.result.result | implode | fromjson) - }' -fi -``` - -4. Сведите проверку storage и разрешения в один читаемый итог. - -```bash -AVAILABLE_BYTES="$( - jq -r ' - .result.result - | if length == 0 then "0" - else (implode | fromjson | .available_bytes // 0 | tostring) - end - ' /tmp/social-account-storage.json -)" - -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - PERMISSION_GRANTED=true -else - PERMISSION_GRANTED="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq -r '.result.result | implode | fromjson' - )" -fi - -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ - --argjson available_bytes "$AVAILABLE_BYTES" \ - --argjson permission_granted "$PERMISSION_GRANTED" '{ - account_id: $account_id, - signer_account_id: $signer_account_id, - storage_ready: ($available_bytes > 0), - permission_ready: $permission_granted, - ready_to_publish_now: (($available_bytes > 0) and $permission_granted) - }' -``` - -Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. - -**Зачем нужен следующий шаг?** - -Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. - -### Что прямо сейчас содержит `mob.near/widget/Profile`? - -Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». - - Стратегия - Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. - - 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. - 02RPC call_function get читает точный исходник widget/Profile. - 03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples. - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Что вы делаете** - -- Спрашиваете у `social.near` каталог виджетов под `mob.near`. -- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. -- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. -- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mob.near -export WIDGET_NAME=Profile -``` - -1. Получите каталог виджетов и сохраните высоты блоков последней записи. - -```bash -WIDGET_KEYS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/widget/*")], - options: {return_type: "BlockHeight"} - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "keys", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-widget-keys.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | .[$account_id].widget - | to_entries - | sort_by(.value * -1) - | map({ - widget_name: .key, - last_write_block: .value - }) - | .[0:20] -' /tmp/social-widget-keys.json -``` - -2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. - -```bash -WIDGET_GET_ARGS_BASE64="$( +POST_ARGS_BASE64="$( jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] + --arg post_key "$POST_KEY" '{ + keys: [($account_id + "/post/" + $post_key)] }' | base64 | tr -d '\n' )" @@ -4136,7 +2997,7 @@ curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + --arg args_base64 "$POST_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", @@ -4148,131 +3009,25 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/social-widget-source.json >/dev/null - -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:25] - | join("\n") - ' /tmp/social-widget-source.json -``` - -3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. + | tee /tmp/social-post-main.json >/dev/null -```bash -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - ' /tmp/social-widget-keys.json \ - | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +jq --arg account_id "$ACCOUNT_ID" --arg post_key "$POST_KEY" ' + .result.result + | implode + | fromjson + | { + account_id: $account_id, + post_key: $post_key, + post: (.[$account_id].post[$post_key] | fromjson) + } +' /tmp/social-post-main.json ``` -На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. +Так вы получаете точный JSON, который хранится для текущего поста, включая поля вроде `type`, `text` и `image`. **Зачем нужен следующий шаг?** -Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. - -## Частые задачи - -### Проверить точное состояние аккаунта или ключа доступа - -**Начните здесь** - -- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для точных полей аккаунта. -- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. - -**Следующая страница при необходимости** - -- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» - -**Остановитесь, когда** - -- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. - -**Переходите дальше, когда** - -- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. -- Пользователя интересует не текущее состояние, а недавняя история активности. - -### Проверить один точный блок или снимок состояния протокола - -**Начните здесь** - -- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. -- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» -- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. - -**Следующая страница при необходимости** - -- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. -- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» - -**Остановитесь, когда** - -- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. - -**Переходите дальше, когда** - -- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). -- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Что этот контракт возвращает прямо сейчас? - -**Начните здесь** - -- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» -- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. -- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. -- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» - -**Следующая страница при необходимости** - -- [FastNear API](https://docs.fastnear.com/ru/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. -- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» - -**Остановитесь, когда** - -- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. - -**Переходите дальше, когда** - -- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. -- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» - -### Отправить транзакцию и подтвердить результат - -**Начните здесь** - -- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. -- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. -- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. -- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» - -**Остановитесь, когда** - -- У вас уже есть результат отправки и нужный финальный статус. - -**Переходите дальше, когда** - -- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. -- Нужен уже не единичный статус, а более широкий сценарий расследования. +Это чистый RPC-паттерн для SocialDB: спросите у контракта один точный ключ, декодируйте возвращённый JSON и остановитесь. Если вопрос превращается в «кто и когда это записал?», переходите к примерам по транзакциям, а не пытайтесь brute-force читать raw state `social.near`. ## Частые ошибки @@ -4368,6 +3123,8 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. +Эти helper-скрипты поддерживаются FastNear и оптимизированы под скорость восстановления. Если в вашей среде нужен review изменений, сначала скачайте скрипт, проверьте его и только потом запускайте, вместо прямого piping в `bash`. + ```bash DATA_PATH=~/.near/data @@ -4459,80 +3216,6 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. -## Частые задачи - -### Поднять optimized `fast-rpc`-узел в mainnet - -**Начните здесь** - -- Используйте якорь с optimized mainnet `fast-rpc` выше. - -**Следующая страница при необходимости** - -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), если нужно дополнительно настроить `THREADS`, `BWLIMIT` или кастомный `DATA_PATH`. - -**Остановитесь, когда** - -- Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. - -**Переходите дальше, когда** - -- На самом деле требуется архивное хранение, а не просто быстрый запуск. - -### Восстановить обычный RPC-узел в стандартный каталог nearcore - -**Начните здесь** - -- Используйте якорь со стандартным mainnet RPC выше. - -**Следующая страница при необходимости** - -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), если нужно дополнительно настроить `DATA_PATH`, `THREADS` или ограничения по пропускной способности. - -**Остановитесь, когда** - -- Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. - -**Переходите дальше, когда** - -- Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. - -### Правильно поднять архивные hot- и cold-данные mainnet - -**Начните здесь** - -- Используйте архивный walkthrough выше. - -**Следующая страница при необходимости** - -- Сначала получите последнюю высоту архивного снапшота, затем запускайте отдельные загрузки hot- и cold-данных с правильными путями. - -**Остановитесь, когда** - -- План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. - -**Переходите дальше, когда** - -- Оператору нужны уже общие гайды nearcore по bootstrap, а не только FastNear snapshots. - -### Поднять архивные hot-данные в testnet - -**Начните здесь** - -- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet), раздел архивного режима. - -**Следующая страница при необходимости** - -- Получите последнюю высоту архивного снапшота testnet перед шагом загрузки. - -**Остановитесь, когда** - -- Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. - -**Переходите дальше, когда** - -- Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. - ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. @@ -4906,22 +3589,22 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ ## Готовый сценарий -### Найти один подозрительный перевод, а затем пройти по его receipt +### Найти один исходящий перевод и при необходимости перейти к деталям исполнения -Используйте этот сценарий, когда история звучит так: «я вижу, что средства двигались, но хочу получить точную опорную точку исполнения для этого движения, не затягивая сразу всю историю аккаунта». +Используйте этот сценарий, когда история звучит так: «я знаю, что этот аккаунт отправлял средства в этом окне, и мне может понадобиться точная опорная точка исполнения для одной строки, но я не хочу сразу тянуть всю историю аккаунта». Стратегия - Сначала оставайтесь на узкой истории движения, а затем один раз переключайтесь в историю исполнения. + Сначала оставайтесь на узкой истории движения, а затем переходите в историю исполнения только если строки перевода уже недостаточно. 01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять. - 02jq поднимает один receipt_id, не затягивая остальную историю аккаунта. - 03POST /v0/receipt превращает это движение в опорную точку исполнения, которую уже можно продолжать в /tx. + 02Сначала выведите строки, а затем явно выберите один transfer_index перед тем, как поднимать его receipt_id. + 03POST /v0/receipt — это необязательное расширение, когда уже нужны детали исполнения именно за этим переводом. **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Выделяете один перевод, который действительно похож на нужное вам движение. -- Переиспользуете его `receipt_id` в Transactions API, чтобы перейти от движения актива к истории исполнения. +- Выделяете одну строку перевода, которая действительно похожа на нужное вам движение. +- Переиспользуете его `receipt_id` в Transactions API только если нужно перейти от движения актива к истории исполнения. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com @@ -4929,113 +3612,65 @@ TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=YOUR_ACCOUNT_ID FROM_TIMESTAMP_MS=1711929600000 TO_TIMESTAMP_MS=1712016000000 +TRANSFER_INDEX=0 -RECEIPT_ID="$( - curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ - account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, - desc: true, - limit: 10 - }')" \ - | tee /tmp/transfers-window.json \ - | jq -r '.transfers[0].receipt_id' -)" +curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | tee /tmp/transfers-window.json >/dev/null jq '{ resume_token, transfers: [ - .transfers[] + .transfers + | to_entries[] | { - transaction_id, - receipt_id, - asset_id, - amount, - other_account_id, - block_height + transfer_index: .key, + transaction_id: .value.transaction_id, + receipt_id: .value.receipt_id, + asset_id: .value.asset_id, + amount: .value.amount, + other_account_id: .value.other_account_id, + block_height: .value.block_height } ] }' /tmp/transfers-window.json -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' -``` - -**Зачем нужен следующий шаг?** - -Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` даёт точную опорную точку в исполнении, не затягивая вас сразу в полную историю аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. - -## Частые задачи - -### Найти исходящие переводы одного аккаунта в узком окне времени - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. - -**Следующая страница при необходимости** - -- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. - -**Остановитесь, когда** - -- Уже можно ответить, кто что отправил, когда и в каком активе. - -**Переходите дальше, когда** - -- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Листать ленту переводов дальше и не потерять своё место - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. - -**Следующая страница при необходимости** - -- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. -- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. - -**Остановитесь, когда** - -- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. - -**Переходите дальше, когда** - -- Пользователь просит метаданные транзакции сверх самих переводов. -- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). - -### Перейти от истории переводов к полному расследованию транзакции - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. - -**Следующая страница при необходимости** - -- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. +RECEIPT_ID="$( + jq -r --argjson transfer_index "$TRANSFER_INDEX" \ + '.transfers[$transfer_index].receipt_id // empty' \ + /tmp/transfers-window.json +)" -**Остановитесь, когда** +printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" +printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" -- Уже определено правильное событие перевода и понятно, какой API открывать следующим. +if [ -n "$RECEIPT_ID" ]; then + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' +fi +``` -**Переходите дальше, когда** +**Зачем нужен следующий шаг?** -- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. +Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный следующий шаг, когда самой строки перевода уже недостаточно и нужна опорная точка в истории исполнения. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. ## Частые ошибки @@ -5157,7 +3792,7 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. -Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. +Если нужен более развёрнутый case study на той же поверхности, переходите к [Berry Club case study](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer case study](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. ## С чего начать @@ -5304,39 +3939,168 @@ curl -s "$TX_BASE_URL/v0/receipt" \ `POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. -### Превратить один страшный receipt ID из логов в понятную человеческую историю +### Какая receipt выдала этот лог или event? -Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. +Используйте это расследование, когда история звучит так: «у меня есть один tx hash и один фрагмент лога, и мне нужно точно понять, какая именно receipt его выдала». -Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. +Это другой вопрос, чем более поздний сценарий «дошёл ли callback?». Здесь цель проще: привязать одну наблюдаемую строку лога к одному точному `receipt_id`, одному методу и одному исполнителю. Стратегия - Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой. + Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога. - 01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt. - 02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий. - 03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола». + 01POST /v0/transactions даёт полный индексированный список receipt для одного tx hash, включая receipt-логи. + 02jq сужает этот список до receipt, в логах которых встречается нужный вам фрагмент. + 03Как только совпадение осталось одно, сохраняйте его receipt_id, executor и имя метода как точный ответ. **Цель** -- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. +- Начать с одного mainnet tx hash и одного фрагмента лога и определить точную receipt, которая выдала этот лог. -Для этого зафиксированного примера «страшный receipt ID из логов» такой: +Для этого зафиксированного mainnet-примера используйте: -- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` -- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- высота блока транзакции: `194263342` -- высота блока исполнения receipt: `194263343` +- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` +- фрагмент лога: `Refund` +- ожидаемый matching `receipt_id`: `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` +- ожидаемый executor: `wrap.near` +- ожидаемый метод: `ft_resolve_transfer` -Человеческая история за этим receipt простая: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. +Эта транзакция полезна тем, что в ней есть две разные logged receipt внутри одной истории: + +- ранний лог `Transfer ...` на receipt с `ft_transfer_call` +- более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` ```mermaid flowchart LR - L["Один страшный receipt ID
5GhZcpfK..."] --> R["Ищем receipt"] - R --> T["Восстанавливаем tx hash
AdgNifPY..."] - T --> S["Читаем действия транзакции"] + T["Один tx hash
2KhhB1uD..."] --> L["Читаем все receipt-логи"] + L --> X["Ищем фрагмент:
Refund"] + X --> R["Точная receipt
9sLHQpaG..."] + R --> A["Ответ:
wrap.near / ft_resolve_transfer"] +``` + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Атрибуция лога | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | +| Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | + +**Что должен включать полезный ответ** + +- какой `receipt_id` выдал лог +- какой контракт исполнил эту receipt +- какой метод там выполнился +- точную строку лога, которая совпала +- одно простое предложение вроде «лог `Refund` пришёл из `wrap.near` в receipt с методом `ft_resolve_transfer`» + +#### Shell-сценарий атрибуции лога + +Используйте этот сценарий, когда у вас уже есть tx hash и следующий вопрос звучит как «какая receipt это сказала?» + +**Что вы делаете** + +- Один раз получаете транзакцию и сохраняете список её receipt. +- Фильтруете receipt по одному фрагменту лога. +- Останавливаетесь, как только у вас есть один точный `receipt_id`, один executor и одно имя метода. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +LOG_FRAGMENT=Refund +``` + +1. Получите транзакцию и сохраните список receipt. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/log-attribution-transaction.json >/dev/null +``` + +2. Отфильтруйте список receipt до логов, которые содержат нужный вам фрагмент. + +```bash +jq --arg fragment "$LOG_FRAGMENT" '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id + }, + matching_receipts: [ + .transactions[0].receipts[] + | select(any(.execution_outcome.outcome.logs[]?; contains($fragment))) + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + block_height: .execution_outcome.block_height, + logs: .execution_outcome.outcome.logs + } + ] +}' /tmp/log-attribution-transaction.json + +# На что смотреть: +# - фрагмент `Refund` совпадает ровно с одной receipt +# - это receipt 9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w +# - receipt исполнилась на wrap.near +# - имя метода — ft_resolve_transfer +``` + +3. Если хотите увидеть все logged receipt рядом, распечатайте только те receipt, где вообще были логи. + +```bash +jq '{ + logged_receipts: [ + .transactions[0].receipts[] + | select((.execution_outcome.outcome.logs | length) > 0) + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + logs: .execution_outcome.outcome.logs + } + ] +}' /tmp/log-attribution-transaction.json +``` + +Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. + +**Зачем нужен следующий шаг?** + +Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. + +### Превратить один страшный receipt ID из логов в понятную человеческую историю + +Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. + +Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. + + Стратегия + Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой. + + 01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt. + 02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий. + 03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола». + +**Цель** + +- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. + +Для этого зафиксированного примера «страшный receipt ID из логов» такой: + +- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` +- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- высота блока транзакции: `194263342` +- высота блока исполнения receipt: `194263343` + +Человеческая история за этим receipt простая: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. + +```mermaid +flowchart LR + L["Один страшный receipt ID
5GhZcpfK..."] --> R["Ищем receipt"] + R --> T["Восстанавливаем tx hash
AdgNifPY..."] + T --> S["Читаем действия транзакции"] S --> H["Человеческая история:
mike.near отправил 5 NEAR в global-counter.mike.near"] ``` @@ -5356,19 +4120,6 @@ flowchart LR #### Shell-сценарий: от страшного receipt ID к человеческой истории -## Ошибки и async - -Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и порядок callback-ов. - -Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. - -**Что вы делаете** - -- Сначала разрешаете receipt. -- Извлекаете `receipt.transaction_hash` через `jq`. -- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. -- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. - ```bash TX_BASE_URL=https://tx.main.fastnear.com RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' @@ -5442,6 +4193,12 @@ jq -r ' `POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. +## Ошибки и async + +Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. + +Используйте этот раздел, когда уже понятно, что транзакция жила дольше одной receipt, и следующий вопрос относится уже к форме исполнения, а не к простому поиску объекта. + ### Доказать, что одно неудачное действие сорвало весь пакет Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? @@ -5787,922 +4544,259 @@ jq \ Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. -### Проследить асинхронную promise-цепочку и доказать порядок callback-ов +### Дошёл ли callback вообще? -Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» +Используйте это расследование, когда одна транзакция запустила downstream-работу на другом контракте, а настоящий вопрос звучит не как «успешно ли отработал receiver?», а как «вернулся ли callback обратно в исходный контракт?» + +Это самый короткий полезный сценарий про callback на странице: + +- стартуйте с одного tx hash +- найдите downstream-receipt на другом контракте +- найдите более поздний callback-receipt, который вернулся в исходный контракт +- остановитесь, как только доказаны сам факт callback и его результат Стратегия - Смотрите на два хеша как на одну async-историю: докажите, что работа была жива, восстановите запрошенный порядок и сравните его с видимым downstream-state. + Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а. - 01RPC call_function на view отложенной работы доказывает, что promise-работа действительно уже была жива до resume-шага. - 02POST /v0/transactions даёт оба block-anchor и точный порядок, который запросила resume-транзакция. - 03RPC EXPERIMENTAL_tx_status вместе с downstream-view доказывают, где callback-и реально выполнились и в каком видимом порядке. + 01POST /v0/transactions показывает downstream-вызов и более поздний receipt, который возвращается в исходный контракт. + 02jq сужает этот список receipt до одного downstream-вызова и одного callback-receipt. + 03RPC EXPERIMENTAL_tx_status нужен только как дополнительное подтверждение, если вам важны канонический результат callback-а и его логи. **Цель** -- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. +- Доказать на одном фиксированном mainnet-примере, что `wrap.near` отправил `ft_transfer_call` в `v2.ref-finance.near`, receiver выполнил `ft_on_transfer`, а затем `wrap.near` получил callback `ft_resolve_transfer` обратно. -Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: +Этот фиксированный mainnet-пример с callback был замечен **19 апреля 2026 года**: -- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом -- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке -- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и -- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору +- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` +- аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` +- исходный контракт: `wrap.near` +- downstream-receiver: `v2.ref-finance.near` +- верхнеуровневый метод: `ft_transfer_call` +- downstream-метод: `ft_on_transfer` +- callback-метод: `ft_resolve_transfer` +- блок транзакции: `194692298` +- блок downstream-receipt: `194692300` +- блок callback-receipt: `194692301` ```mermaid flowchart LR - Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] - H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] - R --> C["Async cross-contract callback-и"] - C --> B["Recorder state
beta"] - B --> A["Recorder state
alpha"] - A --> G["Recorder state
gamma"] - Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] - R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] - G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] + T["Одна mainnet-транзакция
ft_transfer_call на wrap.near"] --> D["Downstream-receipt
v2.ref-finance.near.ft_on_transfer"] + D --> F["Receiver упал
E51: contract paused"] + F --> C["Callback-receipt обратно в wrap.near
ft_resolve_transfer"] + C --> R["Лог refund на wrap.near"] ``` -Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. - -Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | -| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | -| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | -| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | -| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | -| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | -| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | -| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | - -**Что должен включать полезный ответ** - -- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» -- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования -- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался -- в каких блоках стали видны изменения состояния -- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования - -## Доказательства по SocialDB - -Эти примеры стартуют с читаемого состояния в NEAR Social и откатываются назад к точной записи, которая это состояние создала. - -### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB - -Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». - - Стратегия - Начните с читаемого значения поля, а затем превратите его field-level block в один receipt и одну транзакцию записи. - - 01NEAR Social POST /get даёт текущее значение profile.name и field-level :block. - 02POST /v0/block превращает этот блок в конкретный receipt и хеш транзакции mike.near -> social.near. - 03POST /v0/transactions доказывает payload записи, а RPC call_function get подтверждает, что поле и сейчас разрешается так же. - -**Цель** - -- Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. - -**Официальные ссылки** - -- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) -- [Живая поверхность чтения NEAR Social](https://api.near.social) - -Этот сценарий следует тому же рецепту доказательства, что и расследование по подписке, но добавляет ещё один важный нюанс SocialDB: для исторического доказательства `:block` на уровне конкретного поля обычно точнее, чем `:block` у родительского объекта. В этом живом примере `mike.near/profile/name` был записан на блоке `78675795`, тогда как более широкий объект `mike.near/profile` позже сдвинулся на другой блок из-за изменений в соседних полях. Роль FastNear в этом сценарии — превратить этот блок уровня поля в receipt, затем в транзакцию и потом в читаемый payload записи. - -Для этого живого примера текущее значение `profile.name` равно `Mike Purvis`, блок записи SocialDB на уровне поля равен `78675795`, ID receipt — `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN`, хеш исходной транзакции — `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ`, а внешний блок транзакции — `78675794`. +Здесь хорошо видна одна полезная деталь NEAR: downstream-сбой не означает, что callback исчез. В этом случае `v2.ref-finance.near` уронил свой `ft_on_transfer`, но `wrap.near` всё равно позже получил `ft_resolve_transfer` и залогировал refund. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Семантическое чтение поля | NEAR Social `POST /get` | Читаем `mike.near/profile/name` с включёнными метаданными блока | Даёт читаемое значение поля и опорный `:block` SocialDB на уровне поля, где это значение было записано | -| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем блок уровня поля из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи уровня поля в конкретный receipt и хеш исходной транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который нёс `profile.name` и окружающие поля профиля в одном payload | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что поле и сейчас имеет это значение, хотя предыдущие шаги уже доказали конкретную историческую запись | +| Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | +| Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | **Что должен включать полезный ответ** -- разрешается ли `mike.near/profile/name` сейчас в `Mike Purvis` -- высоту блока записи SocialDB на уровне поля (`78675795`) и объяснение, почему для этого вопроса этот якорь лучше, чем блок родительского профиля -- конкретный ID receipt и хеш исходной транзакции за этой записью -- доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload -- различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) +- какой контракт получил downstream-вызов +- какой метод выполнился на downstream-контракте +- вернулся ли более поздний receipt в исходный контракт +- какой callback-метод там выполнился и в каком блоке +- одно простое предложение вроде «receiver упал, но исходный контракт всё равно получил callback и завершил перевод» -#### Shell-сценарий доказательства поля профиля в NEAR Social +#### Shell-сценарий проверки callback-а -Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. +Используйте этот сценарий, когда нужен один конкретный proof callback-а без превращения страницы в полный курс по теории promises. **Что вы делаете** -- Читаете текущее поле `profile.name` из NEAR Social и сохраняете блок записи SocialDB на уровне поля. -- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`, несущей `profile.name`. -- Завершаете каноническим RPC-подтверждением того, что поле всё ещё разрешается в то же значение на `final`. +- Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. +- Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. +- Останавливаетесь сразу, как только можете сказать, вернулся ли callback и что он сделал. ```bash -SOCIAL_API_BASE_URL=https://api.near.social TX_BASE_URL=https://tx.main.fastnear.com RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near -PROFILE_FIELD=profile/name +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +SENDER_ACCOUNT_ID=7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72 +ORIGIN_CONTRACT_ID=wrap.near +DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near ``` -1. Прочитайте поле профиля из NEAR Social и сохраните блок записи SocialDB на уровне поля. +1. Получите транзакцию и распечатайте downstream-receipt вместе с callback-receipt. ```bash -PROFILE_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-profile-name.json \ - | jq -r --arg account_id "$ACCOUNT_ID" \ - '.[ $account_id ].profile.name[":block"]' +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/callback-check-transaction.json >/dev/null + +CALLBACK_RECEIPT_ID="$( + jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' + first( + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + | .receipt.receipt_id + ) + ' /tmp/callback-check-transaction.json )" -jq --arg account_id "$ACCOUNT_ID" '{ - current_name: .[$account_id].profile.name[""], - field_block_height: .[$account_id].profile.name[":block"], - parent_profile_block_height: .[$account_id].profile[":block"] -}' /tmp/mike-profile-name.json +jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + tx_block_height: .transactions[0].execution_outcome.block_height + }, + downstream_receipt: ( + first( + .transactions[0].receipts[] + | select(.receipt.receiver_id == $downstream) + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + status: .execution_outcome.outcome.status, + block_height: .execution_outcome.block_height + } + ) + ), + callback_receipt: ( + first( + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: .receipt.receipt.Action.actions[0].FunctionCall.method_name, + logs: .execution_outcome.outcome.logs, + status: .execution_outcome.outcome.status, + block_height: .execution_outcome.block_height + } + ) + ), + callback_ran: ( + first( + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + | true + ) // false + ) +}' /tmp/callback-check-transaction.json -# Ожидаемое current_name: "Mike Purvis" -# Ожидаемая высота блока уровня поля: 78675795 +# На что смотреть: +# - downstream-receipt выполнил ft_on_transfer на v2.ref-finance.near +# - более поздний callback-receipt выполнил ft_resolve_transfer на wrap.near +# - callback_ran равно true, даже несмотря на downstream-сбой ``` -2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. +2. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. ```bash -PROFILE_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg sender_account_id "$SENDER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $sender_account_id, + wait_until: "FINAL" + } }')" \ - | tee /tmp/mike-profile-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" + | tee /tmp/callback-check-rpc.json >/dev/null -jq --arg account_id "$ACCOUNT_ID" '{ - profile_receipt: ( +jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ + top_level_status: .result.status, + callback_receipt: ( first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") + .result.receipts_outcome[] + | select(.id == $callback_receipt_id) | { - receipt_id, - transaction_hash, - block_height, - tx_block_height + receipt_id: .id, + executor_id: .outcome.executor_id, + logs: .outcome.logs, + status: .outcome.status } ) ) -}' /tmp/mike-profile-block.json +}' /tmp/callback-check-rpc.json -# Ожидаемый receipt ID: 2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN -# Ожидаемый хеш транзакции: 6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ +# На что смотреть: +# - downstream ft_on_transfer receipt упал на v2.ref-finance.near +# - wrap.near всё равно позже получил ft_resolve_transfer +# - лог callback-а показывает refund обратно отправителю ``` -3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. +**Зачем нужен следующий шаг?** -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-profile-transaction.json >/dev/null +Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), - description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), - tags: ( - .args - | @base64d - | fromjson - | .data["mike.near"].profile.tags - | keys - ) - } - ) -}' /tmp/mike-profile-transaction.json -``` +## Расширенные сценарии и case study -4. Завершите каноническим подтверждением текущего состояния через raw RPC. +Примеры ниже всё ещё полезны, но они длиннее или более специализированы, чем основные start-here-сценарии выше. `Berry Club` и `OutLayer` вынесены в отдельные case-study-страницы, паттерн provenance для SocialDB теперь живёт на отдельной расширенной странице, а последний пример здесь оставляет только компактный multi-contract follow-up. -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)] - } | @base64' -)" +### Расширенный паттерн provenance для SocialDB -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-profile-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - finality: "final", - current_name: ( - .result.result - | implode - | fromjson - | .[$account_id].profile.name - ) -}' /tmp/mike-profile-rpc.json -``` - -Этот последний шаг подтверждает, что поле и сейчас разрешается в `Mike Purvis`. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись установила это поле и какая транзакция несла эту запись. - -**Зачем нужен следующий шаг?** - -NEAR Social даёт семантическое значение поля. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемый payload профиля. RPC даёт каноническое подтверждение текущего состояния. - -### Доказать, что `mike.near` подписался на `mob.near`, а затем восстановить транзакцию записи в SocialDB - -Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». - - Стратегия - Начните с семантической связи подписки, а затем используйте блок записи как мост назад к одному receipt и одной транзакции. - - 01NEAR Social POST /get даёт читаемую связь подписки и SocialDB :block, где она была записана. - 02POST /v0/block превращает этот блок записи в конкретный receipt и хеш транзакции за этой связью. - 03POST /v0/transactions доказывает payload с graph.follow и index.graph, а RPC call_function get подтверждает, что связь и сейчас существует. - -**Цель** - -- Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. - -**Официальные ссылки** - -- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) -- [Живая поверхность чтения NEAR Social](https://api.near.social) - -Читаемая связь подписки приходит из данных NEAR Social, а не из FastNear. Ключевой мост здесь — метаданные SocialDB `:block`: они указывают на блок, в котором исполнился receipt, записавший это значение. Этот блок не совпадает с блоком, в который была включена внешняя транзакция. Роль FastNear в этом сценарии — превратить эту высоту блока в receipt, затем в транзакцию и, наконец, в читаемую историю исполнения. - -Для этого живого примера текущая связь выглядит как `mike.near -> mob.near`, блок записи SocialDB равен `79574924`, ID receipt — `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th`, хеш исходной транзакции — `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb`, а внешний блок транзакции — `79574923`. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Семантическое чтение связи | NEAR Social `POST /get` | Читаем `mike.near/graph/follow/mob.near` с включёнными метаданными блока | Даёт читаемую связь подписки и опорный `:block` из SocialDB, где это значение было записано | -| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем высоту блока из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи SocialDB в конкретный receipt и хеш исходной транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который записал и `graph.follow`, и записи `index.graph` | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что связь подписки существует и сейчас, хотя предыдущие шаги уже доказали конкретную историческую запись | - -**Что должен включать полезный ответ** - -- существует ли сейчас связь подписки `mike.near -> mob.near` -- высоту блока записи SocialDB (`79574924`) и объяснение, почему это блок исполнения receipt -- конкретный ID receipt и хеш исходной транзакции за этой записью -- доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` -- различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) - -#### Shell-сценарий доказательства подписки в NEAR Social - -Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. - -**Что вы делаете** - -- Читаете текущую связь подписки из NEAR Social и сохраняете блок записи SocialDB. -- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`. -- Завершаете каноническим RPC-подтверждением того, что связь всё ещё существует на `final`. - -```bash -SOCIAL_API_BASE_URL=https://api.near.social -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near -TARGET_ACCOUNT_ID=mob.near -``` - -1. Прочитайте связь подписки из NEAR Social и сохраните блок записи SocialDB. - -```bash -FOLLOW_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - keys: [($account_id + "/graph/follow/" + $target_account_id)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-follow-edge.json \ - | jq -r --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" \ - '.[ $account_id ].graph.follow[ $target_account_id ][":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - follow_edge: .[$account_id].graph.follow[$target_account_id][""], - follow_block_height: .[$account_id].graph.follow[$target_account_id][":block"] -}' /tmp/mike-follow-edge.json - -# Ожидаемая высота блока записи: 79574924 -``` - -2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. - -```bash -FOLLOW_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$FOLLOW_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-follow-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - follow_receipt: ( - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | { - receipt_id, - transaction_hash, - block_height, - tx_block_height - } - ) - ) -}' /tmp/mike-follow-block.json - -# Ожидаемый receipt ID: UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th -# Ожидаемый хеш транзакции: FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -``` - -3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$FOLLOW_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-follow-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - follow_edge: (.args | @base64d | fromjson | .data["mike.near"].graph.follow["mob.near"]), - index_graph: ( - .args - | @base64d - | fromjson - | .data["mike.near"].index.graph - | fromjson - | map(select(.value.accountId == "mob.near")) - ) - } - ) -}' /tmp/mike-follow-transaction.json -``` - -4. Завершите каноническим подтверждением текущего состояния через raw RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - keys: [($account_id + "/graph/follow/" + $target_account_id)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-follow-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - finality: "final", - current_follow_edge: ( - .result.result - | implode - | fromjson - | .[$account_id].graph.follow[$target_account_id] - ) -}' /tmp/mike-follow-rpc.json -``` - -Этот последний шаг подтверждает, что связь подписки существует и сейчас. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись создала эту связь и какая транзакция несла эту запись. - -**Зачем нужен следующий шаг?** - -NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. - -### Какая транзакция записала `mob.near/widget/Profile`? - -Используйте это расследование, когда вопрос звучит так: «я уже знаю, что `mob.near/widget/Profile` существует прямо сейчас. Какая именно транзакция записала ту версию виджета, на которую я смотрю?» - -Это естественное tx-продолжение к более лёгкому RPC-сценарию про виджет и к provenance-NFT-сценарию. Задача здесь прямолинейная: - -- стартуем с собственного SocialDB-блока виджета -- превращаем этот блок в один `mob.near -> social.near` receipt -- восстанавливаем исходную транзакцию -- декодируем payload `set` и доказываем, что он действительно нёс исходник виджета - - Стратегия - Смотрите на write-block виджета как на весь мост сразу: блок в receipt, receipt в транзакцию, транзакцию в исходник. - - 01POST /v0/block начинает с блока виджета и сужает его до одного receipt mob.near -> social.near. - 02POST /v0/transactions превращает этот receipt в один читаемый payload set с исходником виджета. - 03RPC call_function get — это финальное подтверждение текущего состояния, что виджет и сейчас существует. - -**Цель** - -- Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -Для этого живого якоря: - -- аккаунт: `mob.near` -- виджет: `Profile` -- блок записи в SocialDB: `86494825` -- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` -- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` -- внешний блок транзакции: `86494824` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `86494825` с `with_receipts: true`, а затем фильтруем его обратно до `mob.near -> social.near` | Превращает блок записи виджета в один конкретный receipt и один конкретный хеш транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `mob.near/widget/Profile` | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | - -**Что должен включать полезный ответ** - -- высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции -- конкретный receipt ID и хеш исходной транзакции за этой записью виджета -- доказательство, что payload записи был `set` с `mob.near/widget/Profile` -- одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» - -#### Shell-сценарий доказательства записи виджета в NEAR Social - -Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. - -**Что вы делаете** - -- Стартуете с блока последней записи виджета. -- Переиспользуете эту высоту в FastNear block receipts, чтобы получить receipt и мост к транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы декодировать записанный исходник виджета. -- Завершаете сырым RPC-подтверждением, что виджет всё ещё существует сейчас. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -WIDGET_BLOCK_HEIGHT=86494825 -``` - -1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. - -```bash -WIDGET_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$WIDGET_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mob-widget-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - widget_write_receipt: ( - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | { - receipt_id, - transaction_hash, - block_height, - tx_block_height - } - ) - ) -}' /tmp/mob-widget-block.json - -# Ожидаемый receipt ID: CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6 -# Ожидаемый хеш транзакции: 9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia -``` - -2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mob-widget-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].transaction.actions[0].FunctionCall - | { - method_name, - widget_source_head: ( - .args - | @base64d - | fromjson - | .data["mob.near"].widget.Profile[""] - | split("\n")[0:12] - ) - } - ) -}' /tmp/mob-widget-transaction.json -``` +Если читаемый факт уже приходит из `api.near.social`, держите follow-up маленьким: сначала семантическое значение, затем `:block`, потом lookup по блоку и транзакции в FastNear. Для одного канонического примера такого сценария используйте [отдельную страницу паттерна provenance для SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs). -Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `9QDup...` вызвала `social.near set` и пронесла в `args` настоящий исходник `mob.near/widget/Profile`. +### Расширенный сценарий: какие downstream-контракты затронула эта транзакция? -3. Завершите каноническим подтверждением текущего состояния через сырой RPC. +Используйте этот сценарий, когда у вас уже есть один multi-contract tx hash и следующий вопрос звучит просто: «в какие контракты ушёл этот вызов после top-level action?» -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mob-widget-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - finality: "final", - current_widget_head: ( - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:5] - ) -}' /tmp/mob-widget-rpc.json -``` - -Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. - -**Зачем нужен следующий шаг?** - -Блок записи виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. - -### Проследить один расчёт NEAR Intents и показать, что именно произошло - -Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». - - Стратегия - Смотрите на один расчёт как на читаемую трассу, а не как на теорию протокола с первой строки. - - 01POST /v0/transactions даёт каркас расчёта: входную точку, первые downstream-контракты и ранние логи. - 02POST /v0/block переиспользует тот же якорь, когда нужен контекст включающего блока вокруг этого расчёта. - 03RPC EXPERIMENTAL_tx_status нужен там, где уже требуется канонический DAG по receipt и имена событий, которые доказывают реальное движение активов. - -**Цель** - -- Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. - -**Официальные ссылки** - -- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) -- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) -- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) - -Для живой трассировки ниже используйте этот фиксированный якорь расчёта из mainnet, зафиксированный **18 апреля 2026 года**: +Этот зафиксированный mainnet-якорь по-прежнему хорошо подходит как пример, хотя сама транзакция и относится к `intents.near`: - хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` - аккаунт `signer` и `receiver`: `intents.near` - высота включающего блока: `194573310` -Быстрая полезная модель здесь простая: +Короткий ответ для этой tx уже полезен: -- `intents.near` выполняет входную точку расчёта -- последующие receipt расходятся по контрактам, которые реально переводят или выводят активы -- журналы событий показывают, какие действия расчёта случились, через имена вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` +- top-level метод был `execute_intents` +- ранние downstream-receipt затронули `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` +- в более поздних логах были семейства событий вроде `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` -Для этого конкретного расчёта короткий человеческий ответ уже неплохой: - -- `intents.near` вызвал `execute_intents` -- downstream receipt ушли в `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` -- трасса выдала события `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` - -Если нужен протокольный фон, базовая форма сопоставления здесь — это двухсторонний `token_diff` intent: одна сторона подписывает желаемую разницу по активам, вторая сторона подписывает противоположную разницу, а затем совпавшая пара отправляется на расчёт. Но для рабочего расследования обычно понятнее начать с одной реальной транзакции расчёта и читать доказательства прямо с цепочки. - -```mermaid -flowchart LR - T["Одна mainnet-транзакция
4cfei8p4..."] --> I["intents.near
execute_intents"] - I --> R["Последующие receipt"] - R --> C["Подключаются другие контракты"] - R --> E["Появляются журналы событий"] - E --> TD["token_diff"] - E --> IE["intents_executed"] - E --> MT["mt_transfer / mt_withdraw"] -``` - -Публичных FastNear-поверхностей уже достаточно, чтобы ответить на практический вопрос: - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Каркас расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и печатаем основную транзакцию плюс первые последующие receipt | Даёт самый быстрый читаемый ответ на вопрос «во что этот расчёт пошёл дальше?» | -| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Показывает, в какой блок попал расчёт и какие receipt этой транзакции видны в блоке | -| Каноническое доказательство по receipt и событиям | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` плюс логи `EVENT_JSON` | Даёт протокольно-канонический DAG, `executor_id` и имена событий, которые объясняют, что реально сделал расчёт | - -**Что должен включать полезный ответ** - -- какую входную точку расчёта вы увидели на `intents.near` -- какие downstream-контракты и методы появились сразу после неё -- какие семейства событий выпустила трассировка -- одно итоговое предложение простым языком о том, что произошло - -Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. - -#### Shell-сценарий расчёта NEAR Intents - -Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. - -**Что вы делаете** - -- Получаете читаемую историю расчёта через Transactions API. -- Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. -- Подтверждаете канонический DAG по receipt и семейства событий через `EXPERIMENTAL_tx_status`. +Для большинства вопросов достаточно Transactions API: ```bash TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 -INTENTS_SIGNER_ID=intents.near -``` +TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 -1. Начните с самой транзакции расчёта и восстановите первый читаемый поток последующих receipt. - -```bash -INTENTS_BLOCK_HASH="$( - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$INTENTS_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/intents-transaction.json \ - | jq -r '.transactions[0].execution_outcome.block_hash' -)" - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - receipt_flow: [ - .transactions[0].receipts[:6][] - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - block_height: .execution_outcome.block_height, - methods: ( - [.receipt.receipt.Action.actions[]?.FunctionCall.method_name] - | map(select(. != null)) - ), - first_log: (.execution_outcome.outcome.logs[0] // null) - } - ] -}' /tmp/intents-transaction.json -``` - -Этот первый шаг уже даёт сильный операторский ответ: `intents.near` выполнил транзакцию расчёта, а ранние последующие receipt показывают, какие контракты вошли в каскад сразу после этого. - -2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. - -```bash -curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_id "$INTENTS_BLOCK_HASH" '{ - block_id: $block_id, - with_receipts: true, - with_transactions: false - }')" \ - | tee /tmp/intents-block.json >/dev/null - -jq --arg tx_hash "$INTENTS_TX_HASH" '{ - block_height: .block.block_height, - block_hash: .block.block_hash, - tx_receipts: [ - .block_receipts[] - | select(.transaction_hash == $tx_hash) - | { - receipt_id, - predecessor_id, - receiver_id, - block_height - } - ] -}' /tmp/intents-block.json -``` - -3. Подтвердите канонический DAG по receipt и извлеките семейства событий через RPC. - -```bash -curl -s "$RPC_URL" \ +curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$INTENTS_TX_HASH" \ - --arg sender_account_id "$INTENTS_SIGNER_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $sender_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/intents-rpc.json >/dev/null - -jq '{ - final_execution_status: .result.final_execution_status, - receipts_outcome: [ - .result.receipts_outcome[:6][] - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - first_log: (.outcome.logs[0] // null) - } - ] -}' /tmp/intents-rpc.json - -jq -r ' - .result.receipts_outcome[] - | .outcome.logs[] - | select(startswith("EVENT_JSON:")) - | capture("event\":\"(?[^\"]+)\"").event -' /tmp/intents-rpc.json | sort -u -``` - -**Зачем нужен следующий шаг?** - -`POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. - -## Частые задачи - -### Найти одну транзакцию - -**Начните здесь** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. - -**Следующая страница при необходимости** - -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. -- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. - -**Остановитесь, когда** - -- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. -- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. - -### Исследовать квитанцию - -**Начните здесь** - -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. -- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. - -**Остановитесь, когда** - -- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. - -**Переходите дальше, когда** - -- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. - -### Посмотреть недавнюю активность аккаунта - -**Начните здесь** - -- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. - -**Остановитесь, когда** - -- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. - -**Переходите дальше, когда** - -- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). -- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). - -### Восстановить ограниченное окно по блокам - -**Начните здесь** - -- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. -- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. - -**Остановитесь, когда** - -- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. - -**Переходите дальше, когда** + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name + }, + downstream_receivers: ( + [.transactions[0].receipts[] | .receipt.receiver_id] + | unique + ), + first_logs: ( + [.transactions[0].receipts[] | .execution_outcome.outcome.logs[]?] + | .[:5] + ) + }' +``` -- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +Если нужен включающий блок, расширяйтесь один раз до Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block). Если нужен канонический DAG по receipt или сырые логи `EVENT_JSON`, расширяйтесь ещё на один шаг до RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status). Учебная идея здесь общая: начинайте с одного tx hash, перечислите downstream receiver-и и останавливайтесь, пока trace действительно не требует большего. ## Частые ошибки @@ -6722,133 +4816,60 @@ jq -r ' --- -## Berry Club: как восстанавливать исторические доски +## Berry Club Case Study: как читать живую доску и разбирать одну эпоху - HTML-маршрут: https://docs.fastnear.com/ru/tx/examples/berry-club - Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples/berry-club.md **Источник:** [https://docs.fastnear.com/ru/tx/examples/berry-club](https://docs.fastnear.com/ru/tx/examples/berry-club) -{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как восстанавливать доски Berry Club через FastNear. Он отделяет текущее состояние из get_lines от исторического разбора через диапазоны блоков, историю аккаунта, раскрытие транзакций и проигрывание draw-аргументов. */} - -# Berry Club: как восстанавливать исторические доски - -Используйте этот разбор, когда вопрос звучит так: «как Berry Club выглядел в определённую эпоху и какие `draw`-вызовы сделали доску именно такой?» - -Это read-only разбор из семейства Transactions examples. Если нужна только доска прямо сейчас, используйте `get_lines` и остановитесь. Если нужно объяснить, как доска пришла к такому виду, переключайтесь на историю блоков, историю аккаунта, раскрытые `draw`-вызовы и проигрывание. - - Стратегия - Сначала прочитайте живую доску, затем ограничьте эпоху и только после этого проигрывайте draw-вызовы, которые её объясняют. - - 01RPC call_function get_lines даёт текущую доску 50x50 и показывает, как выглядит «сейчас». - 02POST /v0/blocks вместе с POST /v0/account ограничивают одну эпоху и дают кандидатные хеши draw. - 03POST /v0/transactions раскрывает эти draw-вызовы, чтобы их можно было проиграть в исторические контрольные точки. - -Держите рядом: - -- [js.fastnear.com](https://js.fastnear.com/) -- [fastnear/js-monorepo](https://github.com/fastnear/js-monorepo) -- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) -- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) -- [Transactions API: диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) -- [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) - -В этом руководстве история Berry Club разбирается только на mainnet. Снимки ниже собраны из воспроизводимых данных mainnet, которые уже сохранены в этом репозитории. - -## Короткая версия - -Berry Club даёт чистый view текущего состояния через `get_lines`, но не даёт готового эндпоинта вида «доска на блоке N». - -Из-за этого задача делится на две части: - -- используйте RPC `call_function`, когда вопрос звучит как «как доска выглядит сейчас?» -- используйте индексированную историю, когда вопрос звучит как «какие записи привели к этой доске?» -- используйте архивный RPC только тогда, когда нужно напрямую материализовать уже известную контрольную точку - -```mermaid -flowchart TD - A["RPC call_function: get_lines"] --> B["Текущая доска 50x50"] - C["Transactions API: /v0/blocks"] --> D["Ограничить эпоху"] - D --> E["/v0/account для berryclub.ek.near"] - E --> F["Кандидатные хеши draw-транзакций"] - F --> G["Раскрытие через /v0/transactions"] - G --> H["Проигрывание draw-записей в историческую доску"] -``` - -## Почему Berry Club хорошо учит истории в NEAR - -Berry Club удобно показывает обе стороны задачи: - -- чистое чтение текущего состояния через `get_lines` -- длинную историю вызовов `draw` с обычными аргументами `FunctionCall` -- формат доски, который легко декодировать и рендерить обычным JavaScript +{/* FASTNEAR_AI_DISCOVERY: Этот case study показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} -Это очень NEAR-подобная форма: один view-метод для текущего состояния, один write-метод для изменений и индексированная история, когда нужно объяснить, как это состояние вообще появилось. - -## 1. Сначала прочитайте текущую доску - -Живое демо использует `berryclub.ek.near` и читает доску через view-вызов `get_lines`: - -```javascript -await near.view({ - contractId: 'berryclub.ek.near', - methodName: 'get_lines', - args: { - lines: [...Array(50).keys()], - }, -}); -``` +# Berry Club Case Study: как читать живую доску и разбирать одну эпоху -Это путь текущего состояния. Он не отвечает на вопрос, как доска пришла к такому виду. +Используйте этот case study, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. -| Вопрос | Лучшая поверхность | Почему | -| --- | --- | --- | -| как доска выглядит сейчас? | [RPC `call_function`](https://docs.fastnear.com/ru/rpc/contract/call-function) | контракт уже отдаёт текущее состояние через `get_lines` | -| какие `draw` были в этой эпохе? | [`/v0/account`](https://docs.fastnear.com/ru/tx/account) + [`/v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | индексированная история даёт ограниченный набор записей и раскрытые аргументы | -| как доска выглядела в известной контрольной точке? | архивный RPC или полное проигрывание | можно напрямую материализовать состояние из архива или восстановить его самому по историческим записям | +Начните с живой доски. Если этого уже достаточно для ответа, на этом можно остановиться. -## 2. Как декодировать `get_lines` в сетку 50x50 +Переходите к Transactions API только тогда, когда вопрос становится историческим: «как Berry Club выглядел в одной более ранней эпохе и какие `draw`-вызовы сделали доску именно такой?» -Полезная часть Berry Club-разметки из `js.fastnear.com` — это декодер строк: +Карточка живой доски: запрашивает `berryclub.ek.near` `get_lines` через mainnet RPC и рендерит текущую сетку 50x50 в интерфейсе документации. -- каждая строка приходит в base64 -- её нужно декодировать в байты -- первые 4 байта нужно пропустить -- дальше цвета читаются как 32-битные little-endian значения каждые 8 байт +## 1. Прочитайте живую доску -```javascript -function decodeLine(encodedLine) { - const bytes = Buffer.from(encodedLine, 'base64'); - const colors = []; +Это самый короткий полезный запрос: - for (let offset = 4; offset < bytes.length; offset += 8) { - colors.push(bytes.readUInt32LE(offset) & 0xffffff); - } +```bash +ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" - return colors; -} +curl -sS https://rpc.mainnet.fastnear.com \ + -H 'content-type: application/json' \ + --data "{ + \"jsonrpc\": \"2.0\", + \"id\": \"berry-live-board\", + \"method\": \"query\", + \"params\": { + \"request_type\": \"call_function\", + \"finality\": \"final\", + \"account_id\": \"berryclub.ek.near\", + \"method_name\": \"get_lines\", + \"args_base64\": \"$ARGS_BASE64\" + } + }" | jq '.result | {block_height, line_count: (.result | implode | fromjson | length)}' ``` -Примените это ко всем 50 строкам — и получите полную сетку 50x50, готовую к рендерингу. +Этот запрос отдаёт текущую доску 50x50 прямо из контракта. Дальше нужно только декодировать каждую base64-строку в 50 цветов пикселей. -## 3. Ограничьте эпоху, которую хотите изучить +## 2. Восстановите одну более раннюю эпоху -Сначала ограничьте эпоху, прежде чем искать draw-записи. Проверочный снимок запуска в этом репозитории находится на блоке `21898354`, а средний снимок — на блоке `97601515`. +Когда нужна история, держите путь коротким: -Сначала зафиксируйте ближайший диапазон блоков: - -```bash -curl -sS https://tx.main.fastnear.com/v0/blocks \ - -H 'content-type: application/json' \ - --data '{ - "from_block_height": 21898350, - "to_block_height": 21898355, - "desc": false, - "limit": 5 - }' -``` +1. ограничьте одну эпоху +2. получите кандидатные `draw`-транзакции для `berryclub.ek.near` +3. раскройте эти хеши +4. проиграйте массивы `pixels` от старых к новым -Затем переключитесь на историю аккаунта и запросите активность Berry Club внутри ограниченного диапазона блоков: +В этом примере используется узкое окно вокруг блока `97601515`: ```bash curl -sS https://tx.main.fastnear.com/v0/account \ @@ -6860,21 +4881,14 @@ curl -sS https://tx.main.fastnear.com/v0/account \ "is_real_receiver": true, "from_tx_block_height": 97576515, "to_tx_block_height": 97601516, - "desc": true, - "limit": 40 - }' + "desc": false, + "limit": 200 + }' | jq '.account_txs | map({transaction_hash, tx_block_height}) | .[-5:]' ``` -Здесь полезна именно такая последовательность: +Если окно ещё нужно подобрать, сначала можно использовать [`/v0/blocks`](https://docs.fastnear.com/ru/tx/blocks). Это не часть основного Berry Club-сценария. -- `/v0/blocks` помогает понять соседство по высотам блоков -- `/v0/account` возвращает кандидатные хеши транзакций Berry Club внутри этого диапазона - -## 4. Раскройте транзакции и оставьте только `draw` - -Когда кандидатные хеши уже есть, раскройте их и оставьте только верхнеуровневые вызовы `draw`, где получатель — `berryclub.ek.near`. - -Аргументы вызова — это обычные данные `FunctionCall` вида `{ pixels: [...] }`: +Раскройте кандидатные хеши и оставьте только верхнеуровневые вызовы `draw`: ```bash curl -sS https://tx.main.fastnear.com/v0/transactions \ @@ -6888,86 +4902,49 @@ curl -sS https://tx.main.fastnear.com/v0/transactions \ | select(.transaction.receiver_id == "berryclub.ek.near") | .transaction.actions[]?.FunctionCall | select(.method_name == "draw") - | { - method_name, - args: (.args | @base64d | fromjson) - }' + | {method_name, args: (.args | @base64d | fromjson)}' ``` -Это даёт всё, что нужно для проигрывания: - -- какая транзакция записывала пиксели -- какие координаты были затронуты -- какие цвета были записаны - -## 5. Проиграйте исторические `draw`-вызовы в доску - -Для полного проигрывания держите в памяти массив 50x50 и применяйте раскрытые транзакции `draw` от старых к новым. +Затем проиграйте массивы `pixels` от старых к новым: ```javascript const board = Array.from({ length: 50 }, () => Array(50).fill(0)); -function applyDraw(boardState, drawArgs) { - for (const pixel of drawArgs.pixels) { +for (const drawTx of drawTransactionsOldestFirst) { + for (const pixel of drawTx.args.pixels) { if (pixel.x < 0 || pixel.x >= 50 || pixel.y < 0 || pixel.y >= 50) { continue; } - boardState[pixel.y][pixel.x] = pixel.color; + board[pixel.y][pixel.x] = pixel.color; } } - -for (const drawTx of drawTransactionsOldestFirst) { - applyDraw(board, drawTx.args); -} ``` -Важно не путать два разных пути: - -- `get_lines` — это текущее состояние -- `tx/account` плюс `tx/transactions` — это материал для проигрывания - -## 6. Готовые контрольные точки по эпохам - -Галерея ниже использует уже сохранённые данные снимков, собранные из mainnet-истории Berry Club: - -- `launch` — последний успешный `draw` в пределах первых 24 часов после первого успешного draw -- `mid` — последний успешный `draw` не позже средней временной точки всей истории Berry Club -- `recent` — последний успешный `draw`, который увидел скрипт при пересборке снимков - -Галерея снимков: контрольные точки launch, mid и recent из сохранённого `src/data/berryClubSnapshots.json`. - -Сейчас эти снимки привязаны к таким транзакциям: - -- `launch`: `BDNFpCpLXjBrgjR6z6wCZmB9EWdHnVMdqau3iTWTRE5H` на блоке `21898354` -- `mid`: `Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ` на блоке `97601515` -- `recent`: `8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL` на блоке `194588754` +В этом и состоит исторический паттерн. У Berry Club нет готового эндпоинта «доска на блоке N», поэтому старые эпохи восстанавливаются проигрыванием `draw`-записей. -## Куда идти за подписанными взаимодействиями - -Эта страница должна оставаться в режиме чтения. - -Если нужны живые подписанные сценарии для `draw` и `buy_tokens`, переходите сюда: - -- [js.fastnear.com](https://js.fastnear.com/) -- [Berry Club example в fastnear/js-monorepo](https://github.com/fastnear/js-monorepo/tree/main/examples/static/berryclub) +## Связанные руководства -Именно там уместны кошелёк и подписанные действия. Эта страница посвящена историческому восстановлению. +- [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) +- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) --- -## OutLayer: трассировка запроса и разрешения воркером +## OutLayer Case Study: трассировка запроса и разрешения воркером - HTML-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer - Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer.md **Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) -{/* FASTNEAR_AI_DISCOVERY: Этот пример остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} +{/* FASTNEAR_AI_DISCOVERY: Этот case study остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} + +# OutLayer Case Study: трассировка запроса и разрешения воркером -# OutLayer: трассировка запроса и разрешения воркером +Используйте этот case study, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» -Используйте этот сценарий, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» +Эта страница намеренно остаётся только в пределах публичных chain-данных. Она показывает caller-транзакцию, более позднюю worker-транзакцию и finish-receipts. Она не пытается доказывать внутреннюю TEE-, `yield/resume`- или CKD/MPC-архитектуру OutLayer только по chain-trace. Сначала смотрите на это как на задачу по истории транзакций: @@ -6975,24 +4952,6 @@ for (const drawTx of drawTransactionsOldestFirst) { - одна более поздняя worker-side транзакция `submit_execution_output_and_resolve` или `resolve_execution` - переход к receipts только тогда, когда уже важен путь завершения - Стратегия - Сначала найдите два хеша, затем раскройте их, а к worker-receipts переходите только тогда, когда нужен finish-путь. - - 01POST /v0/account по outlayer.* — самый быстрый surface для поиска хешей. - 02POST /v0/transactions превращает caller-хеш и worker-хеш в читаемые signer, method и log-доказательства. - 03Разбирайте receipts worker-транзакции только тогда, когда реальный вопрос уже касается callback, списания или возврата средств. - -**Цель** - -- Восстановить одну caller-side транзакцию, одну worker-side транзакцию и завершающие receipts, которые относятся к одному и тому же запросу OutLayer. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Поиск хешей | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Забираем недавние хеши транзакций для `outlayer.near` | Даёт самый быстрый локальный по контракту surface, когда пара хешей ещё не известна | -| Раскрытие транзакций | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Раскрываем caller-side и worker-side хеши вместе | Превращает сырые хеши в signer-, receiver-, action- и log-доказательства | -| Разбор finish-пути | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем worker-хеш и читаем список его receipts | Показывает, где материализовались callback, списание и возврат средств | -| Необязательная проверка идентичности | RPC [`view_account`](https://docs.fastnear.com/ru/rpc/account/view-account) | Идём в RPC только если следующий вопрос уже про идентичность контракта, а не про историю транзакций | Держит проверку текущего состояния отдельно от трассировки истории | - ## Проверенный shell-сценарий Эта пара работала 18 апреля 2026 года: @@ -7028,14 +4987,7 @@ jq '{ }' /tmp/outlayer-pair.json ``` -Что это доказывает: - -- запросная транзакция шла от `solarflux.near` к `outlayer.near` -- в логах запроса фигурировал проект `zavodil.near/near-email` -- worker-транзакция позже пришла от `worker.outlayer.near` к `outlayer.near` -- в логах воркера были `Stored pending output` и `Resolving execution ...` - -Это и есть главный наблюдаемый цикл в терминах NEAR: сначала caller-side запрос, затем более позднее worker-side разрешение. +Это и есть главный наблюдаемый цикл: сначала caller-side запрос, затем более позднее worker-side разрешение, с читаемыми signer-, method- и log-доказательствами для обеих транзакций. ### 2. Читайте worker-receipts только тогда, когда уже важен finish-путь @@ -7079,29 +5031,217 @@ curl -sS "$TX_BASE_URL/v0/account" \ }' ``` -Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это уже surface, который превращает их в читаемое доказательство. - -## Граница сценария - -Этот сценарий намеренно остаётся в рамках публичных chain-данных, которые FastNear и RPC умеют показывать напрямую: - -- caller-side транзакция запроса -- более поздняя worker-side транзакция разрешения -- finish-receipts и логи - -Он **не** пытается доказывать внутреннюю TEE-модель OutLayer, использование same-account `yield/resume` или путь доверия через CKD/MPC только по публичной трассе транзакций. Это отдельные архитектурные вопросы, и читать их нужно в документации OutLayer, а не считать доказанными этим trace-сценарием. +Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это surface, который превращает их в читаемое доказательство. ## Полезные связанные страницы - [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) - [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) - [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) -- [RPC: view_account](https://docs.fastnear.com/ru/rpc/account/view-account) - [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) - [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) --- +## Расширенный паттерн provenance для SocialDB + +- HTML-маршрут: https://docs.fastnear.com/ru/tx/socialdb-proofs +- Markdown-маршрут: https://docs.fastnear.com/ru/tx/socialdb-proofs.md + +**Источник:** [https://docs.fastnear.com/ru/tx/socialdb-proofs](https://docs.fastnear.com/ru/tx/socialdb-proofs) + +# Расширенный паттерн provenance для SocialDB + +Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому provenance. + +Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" + +## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` + +Используйте этот сценарий, когда читаемый факт уже звучит как "текущее `profile.name` равно `Mike Purvis`", а остаётся только вопрос, какая запись сделала это поле истинным. + +Здесь достаточно сохранить один важный нюанс SocialDB: для исторического provenance правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. + +Для этого живого якоря: + +- текущее `profile.name`: `Mike Purvis` +- блок записи SocialDB на уровне поля: `78675795` +- receipt ID: `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN` +- хеш исходной транзакции: `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ` +- внешний блок транзакции: `78675794` + +### Shell-сценарий + +1. Прочитайте поле из NEAR Social и сохраните block записи на уровне поля. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +PROFILE_FIELD=profile/name + +PROFILE_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-profile-name.json \ + | jq -r --arg account_id "$ACCOUNT_ID" \ + '.[ $account_id ].profile.name[":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + current_name: .[$account_id].profile.name[""], + field_block_height: .[$account_id].profile.name[":block"], + parent_profile_block_height: .[$account_id].profile[":block"] +}' /tmp/mike-profile-name.json +``` + +2. Переиспользуйте этот block уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. + +```bash +PROFILE_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-profile-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + profile_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-profile-block.json +``` + +3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-profile-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), + description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), + tags: ( + .args + | @base64d + | fromjson + | .data["mike.near"].profile.tags + | keys + ) + } + ) +}' /tmp/mike-profile-transaction.json +``` + +4. Завершите каноническим подтверждением текущего состояния через raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-profile-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + finality: "final", + current_name: ( + .result.result + | implode + | fromjson + | .[$account_id].profile.name + ) +}' /tmp/mike-profile-rpc.json +``` + +Это и есть весь provenance-паттерн: читаемое значение, block уровня поля, мост через receipt, payload транзакции и затем необязательное подтверждение текущего состояния. + +## Тот же паттерн для других ключей SocialDB + +Используйте тот же мост каждый раз, когда у вас уже есть читаемое значение SocialDB и его write-block: + +1. Прочитайте семантическое значение и `:block` из NEAR Social или начните с уже известного блока записи виджета. +2. Используйте Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block), чтобы восстановить receipt `*.near -> social.near` и хеш транзакции. +3. Используйте Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), чтобы декодировать payload `social.near set`. +4. Используйте RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) только если всё ещё нужно каноническое подтверждение текущего состояния. + +### Вариант для связи подписки + +Тот же паттерн работает для читаемой связи подписки: + +- текущая связь: `mike.near -> mob.near` +- блок записи SocialDB: `79574924` +- receipt ID: `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th` +- хеш исходной транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` + +Главное отличие от примера с профилем только в ключе и форме payload: читайте `mike.near/graph/follow/mob.near`, а затем декодируйте и `graph.follow`, и соответствующую запись `index.graph` из payload записи. + +### Вариант для исходника виджета + +Тот же паттерн снова работает, когда читаемый факт относится к ключу с исходником виджета: + +- ключ виджета: `mob.near/widget/Profile` +- блок записи SocialDB: `86494825` +- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` +- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` + +Главное отличие здесь в том, что вы обычно стартуете с уже известного блока записи виджета, а затем декодируете `.data["mob.near"].widget.Profile[""]` из исходного payload `social.near set`. + +--- + ## RPC протокола NEAR: Просмотр ключа доступа - HTML-маршрут: https://docs.fastnear.com/ru/rpcs/account/view_access_key diff --git a/static/ru/llms.txt b/static/ru/llms.txt index 0502649..a46fe5d 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -16,7 +16,7 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных FastData-ключей, чтения истории точного ключа и привязки индексированных строк к исходной транзакции. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для чтения точных строк FastData, проверки истории точного ключа и привязки индексированной строки к исходной транзакции. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. - [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. @@ -32,14 +32,15 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп ## Другие гайды -- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга. +- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и классификации стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для polling по оптимистичным и финализированным блокам и перехода к RPC, когда это нужно. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных хешей для дальнейшего разбора. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. - [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. -- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования и подробные разборы для работы с receipt, транзакциями, записями NEAR Social, promise-цепочками и расчётами NEAR Intents. -- [Berry Club: как восстанавливать исторические доски](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Используйте Transactions API, RPC get_lines и проигрывание draw-вызовов, чтобы восстанавливать доски Berry Club по историческим эпохам. -- [OutLayer: трассировка запроса и разрешения воркером](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать завершающие receipts. +- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика, а затем для более глубоких case study, когда они действительно нужны. +- [Berry Club Case Study: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Case study, который начинается с живой доски Berry Club через RPC get_lines, а затем использует Transactions API, чтобы восстановить одну более раннюю эпоху. +- [OutLayer Case Study: трассировка запроса и разрешения воркером](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Case study, который использует Transactions API, чтобы связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать завершающие receipts. +- [Расширенный паттерн provenance для SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs.md): Один расширенный паттерн, который начинается с читаемого значения SocialDB и восстанавливает транзакцию записи за ним. ## Снапшоты diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index e44bd50..a911f9b 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -2,79 +2,91 @@ ## Быстрый старт -Начните с двух helper-маршрутов, которые показывают, что изменилось прямо сейчас. +Начните с одного недавнего финализированного блока и сначала запросите самую маленькую возможную touch-сводку. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID -curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print "optimistic:", $2}' \ - | tr -d '\r' +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" -curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print "final:", $2}' \ - | tr -d '\r' +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | jq --arg target "$TARGET_ACCOUNT_ID" '{ + height: .block.header.height, + hash: .block.header.hash, + direct_tx_count: ([.shards[].chunk.transactions[]? + | select((.transaction.receiver_id // .receiver_id) == $target)] | length), + incoming_receipt_count: ([.shards[].chunk.receipts[]? + | select(.receiver_id == $target)] | length), + outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? + | select( + (.receipt.receiver_id // "") == $target + or (.execution_outcome.outcome.executor_id // "") == $target + )] | length), + state_change_count: ([.shards[].state_changes[]? + | select((.change.account_id // "") == $target)] | length) + } | . + { + touched: ( + (.direct_tx_count > 0) + or (.incoming_receipt_count > 0) + or (.outcome_hit_count > 0) + or (.state_change_count > 0) + ) + }' ``` -Это даёт текущие optimistic и final redirect target до того, как вы запрашиваете полные документы блоков. +Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. ## Готовое расследование -### Поймать новый блок как можно раньше, а затем подтвердить его после finality +### Был ли мой контракт затронут в последнем финализированном блоке? -Используйте это расследование, когда нужно заметить новый блок как можно раньше, но финальный ответ всё равно должен опираться на финализированный блок и иногда на точное чтение через RPC. +Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC. Стратегия - Пусть NEAR Data сначала скажет, что что-то изменилось, а затем переиспользуйте то же семейство блоков для стабильного подтверждения. + Зафиксируйтесь на одном финализированном блоке, просканируйте всё семейство блока по целевому аккаунту, а затем оставьте только компактную сводку и идентификаторы, которые действительно стоит разбирать дальше. - 01block-optimistic или last-block-optimistic дают самый ранний полезный сигнал. - 02block или last-block-final подтверждают, что то же наблюдение дошло до финализированной истории. - 03RPC block нужен только в самом конце, когда уже известна точная высота или хеш. + 01last-block-final даёт одну стабильную высоту блока без угадывания. + 02block — это главный read: он уже содержит транзакции, receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» + 03Только если ответ «да», расширяйтесь дальше: сохраняйте найденные shard id, tx hash и receipt id, а затем передавайте именно эти идентификаторы в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). **Цель** -- Как можно раньше заметить одно свежее изменение в семействе блоков, а затем подтвердить, какой финализированный блок его догнал. +- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только shard id, счётчики и sample-идентификаторы для следующего шага. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](https://docs.fastnear.com/ru/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | -| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](https://docs.fastnear.com/ru/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | -| Стабильное подтверждение | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) или [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | -| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | -| Точный разбор через RPC | RPC [Блок по ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Блок по высоте](https://docs.fastnear.com/ru/rpc/block/block-by-height) | Получаем точный блок, как только понятно, какой именно блок важен | Здесь уже имеет смысл RPC, если нужен тот самый блок-объект, который вернул бы сам протокол | +| Последняя стабильная точка | NEAR Data [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Получаем высоту одного финализированного блока без угадывания | Даёт стабильную отправную точку для всего вопроса | +| Всё семейство блока | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) | Сканируем транзакции, receipts, результаты исполнения receipts и изменения состояния по целевому аккаунту | Это главная поверхность ответа на вопрос «был ли затронут мой контракт?» | +| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Используем, когда нужны только высота, хеш, время или заголовки чанков | Позволяет не тянуть более широкий payload блока, когда фильтрация по контракту не нужна | +| Необязательный follow-up по шарду | NEAR Data [`block-chunk`](https://docs.fastnear.com/ru/neardata/block-chunk) или [`block-shard`](https://docs.fastnear.com/ru/neardata/block-shard) | Повторно открываем только затронутый шард, если нужен более глубокий payload | Полезно, когда вы уже знаете, какой шард mattered | +| Точные поверхности для продолжения | [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc) | Переиспользуем найденные tx hash или receipt id только если нужна полная история исполнения | NEAR Data позволяет сначала понять, нужен ли вообще переход дальше | **Что должен включать полезный ответ** -- какой redirect target и какой разрешённый оптимистичный блок впервые запустили расследование -- когда helper для finality догнал его и в какой блок он разрешился -- изменил ли точный разбор через RPC интерпретацию +- финализированную высоту и хеш +- ответ “затронут / не затронут” +- счётчики прямых транзакций, входящих receipts, outcome-hit и state changes +- по одному sample tx hash или receipt id на категорию, когда он есть -### Shell-сценарий от оптимистичного сигнала к финализированному подтверждению +### Shell-сценарий от финализированного блока к ответу по контракту -Используйте этот сценарий, когда нужно сразу заметить свежее изменение в семействе блоков, а затем доказать, какой финализированный блок его догнал, и подтвердить именно эту высоту через RPC. +Используйте этот сценарий, когда целевой аккаунт уже известен и нужен один свежий финализированный ответ, а не длинный polling-цикл. **Что вы делаете** -- Смотрите redirect, который возвращает `GET /v0/last_block/optimistic`. -- Загружаете разрешённый оптимистичный блок и сохраняете его высоту и хеш. -- Смотрите redirect, который возвращает `GET /v0/last_block/final`, и сохраняете финализированный counterpart. -- Сравниваете оптимистичное и финализированное наблюдения, а затем переиспользуете финализированную высоту в RPC `block` по высоте. +- Получаете redirect target для последнего финализированного блока. +- Один раз загружаете полный документ блока. +- Собираете один компактный ответ по одному `TARGET_ACCOUNT_ID`. +- Получаете ответ “да / нет” плюс минимально полезные счётчики и sample-идентификаторы. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -RPC_URL=https://rpc.mainnet.fastnear.com - -OPTIMISTIC_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -printf 'Optimistic redirect target: %s\n' "$OPTIMISTIC_LOCATION" - -curl -s "$NEARDATA_BASE_URL$OPTIMISTIC_LOCATION" \ - | tee /tmp/neardata-optimistic-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' +TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -82,134 +94,139 @@ FINAL_LOCATION="$( | tr -d '\r' )" +BLOCK_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | sed -E 's#.*/([0-9]+)$#\1#')" + printf 'Final redirect target: %s\n' "$FINAL_LOCATION" +printf 'Final block height: %s\n' "$BLOCK_HEIGHT" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' - -jq -n \ - --slurpfile optimistic /tmp/neardata-optimistic-block.json \ - --slurpfile final /tmp/neardata-final-block.json '{ - optimistic: { - height: $optimistic[0].block.header.height, - hash: $optimistic[0].block.header.hash - }, - final: { - height: $final[0].block.header.height, - hash: $final[0].block.header.hash - }, - same_height: ( - $optimistic[0].block.header.height - == $final[0].block.header.height - ) - }' - -BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "block", - params: { - block_id: ($block_height | tonumber) + | tee /tmp/neardata-block.json >/dev/null + +jq --arg target "$TARGET_ACCOUNT_ID" ' + ( + [ + .shards[] + | .chunk.transactions[]? + | select((.transaction.receiver_id // .receiver_id) == $target) + | (.transaction.hash // .hash) + ] + ) as $txs + | ( + [ + .shards[] + | .chunk.receipts[]? + | select(.receiver_id == $target) + | .receipt_id + ] + ) as $receipts + | ( + [ + .shards[] + | .receipt_execution_outcomes[]? + | select( + (.receipt.receiver_id // "") == $target + or (.execution_outcome.outcome.executor_id // "") == $target + ) + | .tx_hash + | select(. != null) + ] + | unique + ) as $outcomes + | ( + [ + .shards[] + | .state_changes[]? + | select((.change.account_id // "") == $target) + | .type + ] + ) as $state_changes + | { + height: .block.header.height, + hash: .block.header.hash, + touched: ( + ($txs | length) > 0 + or ($receipts | length) > 0 + or ($outcomes | length) > 0 + or ($state_changes | length) > 0 + ), + direct_tx_count: ($txs | length), + incoming_receipt_count: ($receipts | length), + outcome_hit_count: ($outcomes | length), + state_change_count: ($state_changes | length), + sample_direct_tx: ($txs[0] // null), + sample_incoming_receipt: ($receipts[0] // null), + sample_outcome_tx_hash: ($outcomes[0] // null) } - }')" \ - | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json ``` -**Зачем нужен следующий шаг?** - -Так вы получаете обе стороны истории: самый ранний оптимистичный якорь и более поздний финализированный якорь. Как только helper для finality сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. - -## Частые задачи - -### Отслеживать последний оптимистичный блок - -**Начните здесь** - -- [Оптимистичный блок](https://docs.fastnear.com/ru/neardata/block-optimistic) для самого свежего чтения по семейству блоков. - -**Следующая страница при необходимости** - -- [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic), если нужен маршрут перенаправления, который всегда ведёт к самому новому оптимистичному блоку. - -**Остановитесь, когда** - -- Уже можно сообщить о последнем оптимистичном блоке или зафиксировать отставание по свежести. - -**Переходите дальше, когда** - -- Нужна finalized-стабильность вместо максимальной свежести. Переходите к [Финализированному блоку по высоте](https://docs.fastnear.com/ru/neardata/block) или [Перенаправлению на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final). - -### Безопасно отслеживать ход финализации блоков - -**Начните здесь** +Если позже понадобятся более богатые списки или разбор по шардам, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. -- [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block), когда уже известна нужная высота. -- [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers), когда достаточно чтения заголовков. +Необязательное расширение: если всё же нужны `touched_shards`, их можно вычислить из того же сохранённого блока, не утяжеляя основной ответ: -**Следующая страница при необходимости** - -- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final), когда клиент должен следовать за самым новым финализированным блоком без предварительного вычисления высоты. - -**Остановитесь, когда** - -- Уже можно показывать движение финализированных блоков без перехода к более глубоким протокольным деталям. - -**Переходите дальше, когда** - -- Пользователю нужны точные поля блока или семантика транзакций. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). - -### Использовать маршруты перенаправления в клиенте опроса - -**Начните здесь** - -- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) или [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) в зависимости от требуемой свежести. - -**Следующая страница при необходимости** - -- Следуйте по URL блока, который вернул маршрут перенаправления, и уже там читайте нужные данные. - -**Остановитесь, когда** - -- Клиент надёжно проходит по маршруту перенаправления и получает нужный ресурс блока. - -**Переходите дальше, когда** - -- Само перенаправление мешает клиенту. Тогда переходите на прямые маршруты блоков. - -### Перейти от опроса свежих блоков к точному RPC-разбору - -**Начните здесь** - -- Используйте подходящий маршрут NEAR Data, чтобы найти недавний блок или событие в семействе блоков, которое нужно исследовать. - -**Следующая страница при необходимости** +```bash +jq --arg target "$TARGET_ACCOUNT_ID" ' + [ + .shards[] + | .shard_id as $shard_id + | select( + ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) + or ([.chunk.receipts[]? | .receiver_id] | index($target)) + or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) + or ([.state_changes[]? | .change.account_id] | index($target)) + ) + | $shard_id + ] | unique +' /tmp/neardata-block.json +``` -- [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или другой RPC-метод, как только станет понятно, какой именно блок или следующий объект для проверки нужен. +Если в этом ответе `touched: true` и нужен один follow-up на уровне шарда, откройте только первый затронутый шард: -**Остановитесь, когда** +```bash +TOUCHED_SHARD_ID="$( + jq -r --arg target "$TARGET_ACCOUNT_ID" ' + first( + .shards[] + | .shard_id as $shard_id + | select( + ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) + or ([.chunk.receipts[]? | .receiver_id] | index($target)) + or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) + or ([.state_changes[]? | .change.account_id] | index($target)) + ) + | $shard_id + ) // empty + ' /tmp/neardata-block.json +)" -- Уже можно чётко назвать недавний блок, который заслуживает проверки через RPC. +if [ -n "$TOUCHED_SHARD_ID" ]; then + curl -s "$NEARDATA_BASE_URL/v0/block/$BLOCK_HEIGHT/chunk/$TOUCHED_SHARD_ID" \ + | jq '{ + shard_id: .header.shard_id, + chunk_hash: .header.chunk_hash, + tx_hashes: ([.transactions[]? | (.transaction.hash // .hash)] | .[:5]), + receipt_ids: ([.receipts[]? | .receipt_id] | .[:5]), + receipt_receivers: ([.receipts[]? | .receiver_id] | .[:5]) + }' +fi +``` -**Переходите дальше, когда** +**Зачем нужен следующий шаг?** -- Пользователь просит точную структуру данных в терминах протокола, а не просто свежее чтение. +Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один из sample-идентификаторов уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. ## Частые ошибки -- Воспринимать NEAR Data как push-стрим, а не как API для опроса. -- Начинать с RPC, когда настоящая задача — мониторинг свежих блоков. -- Забывать, что невалидный ключ может вернуть `401` ещё до перенаправления, а сами перенаправления подходят не каждому HTTP-клиенту. -- Оставаться на NEAR Data после того, как пользователь уже попросил точные протокольные детали блока. +- Воспринимать NEAR Data как push-стрим, а не как polling- или point-read API. +- Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. +- Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. +- Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. +- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data точные shard id, tx hash и receipt id. ## Полезные связанные страницы - [NEAR Data API](https://docs.fastnear.com/ru/neardata) -- [RPC Reference](https://docs.fastnear.com/ru/rpc) - [Transactions API](https://docs.fastnear.com/ru/tx) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index e44bd50..a911f9b 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -2,79 +2,91 @@ ## Быстрый старт -Начните с двух helper-маршрутов, которые показывают, что изменилось прямо сейчас. +Начните с одного недавнего финализированного блока и сначала запросите самую маленькую возможную touch-сводку. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID -curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print "optimistic:", $2}' \ - | tr -d '\r' +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" -curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print "final:", $2}' \ - | tr -d '\r' +curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ + | jq --arg target "$TARGET_ACCOUNT_ID" '{ + height: .block.header.height, + hash: .block.header.hash, + direct_tx_count: ([.shards[].chunk.transactions[]? + | select((.transaction.receiver_id // .receiver_id) == $target)] | length), + incoming_receipt_count: ([.shards[].chunk.receipts[]? + | select(.receiver_id == $target)] | length), + outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? + | select( + (.receipt.receiver_id // "") == $target + or (.execution_outcome.outcome.executor_id // "") == $target + )] | length), + state_change_count: ([.shards[].state_changes[]? + | select((.change.account_id // "") == $target)] | length) + } | . + { + touched: ( + (.direct_tx_count > 0) + or (.incoming_receipt_count > 0) + or (.outcome_hit_count > 0) + or (.state_change_count > 0) + ) + }' ``` -Это даёт текущие optimistic и final redirect target до того, как вы запрашиваете полные документы блоков. +Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. ## Готовое расследование -### Поймать новый блок как можно раньше, а затем подтвердить его после finality +### Был ли мой контракт затронут в последнем финализированном блоке? -Используйте это расследование, когда нужно заметить новый блок как можно раньше, но финальный ответ всё равно должен опираться на финализированный блок и иногда на точное чтение через RPC. +Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC. Стратегия - Пусть NEAR Data сначала скажет, что что-то изменилось, а затем переиспользуйте то же семейство блоков для стабильного подтверждения. + Зафиксируйтесь на одном финализированном блоке, просканируйте всё семейство блока по целевому аккаунту, а затем оставьте только компактную сводку и идентификаторы, которые действительно стоит разбирать дальше. - 01block-optimistic или last-block-optimistic дают самый ранний полезный сигнал. - 02block или last-block-final подтверждают, что то же наблюдение дошло до финализированной истории. - 03RPC block нужен только в самом конце, когда уже известна точная высота или хеш. + 01last-block-final даёт одну стабильную высоту блока без угадывания. + 02block — это главный read: он уже содержит транзакции, receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» + 03Только если ответ «да», расширяйтесь дальше: сохраняйте найденные shard id, tx hash и receipt id, а затем передавайте именно эти идентификаторы в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). **Цель** -- Как можно раньше заметить одно свежее изменение в семействе блоков, а затем подтвердить, какой финализированный блок его догнал. +- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только shard id, счётчики и sample-идентификаторы для следующего шага. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Самое быстрое обнаружение | NEAR Data [`block-optimistic`](https://docs.fastnear.com/ru/neardata/block-optimistic) | Опрашиваем оптимистичные блоки, чтобы как можно раньше заметить новое изменение в семействе блоков | Даёт самый ранний полезный сигнал ещё до финализированного подтверждения | -| Маршрут для последнего оптимистичного блока | NEAR Data [`last-block-optimistic`](https://docs.fastnear.com/ru/neardata/last-block-optimistic) | Используем маршрут перенаправления, когда клиент должен всегда следовать за самым новым оптимистичным блоком | Упрощает клиент опроса, когда важнее получать последний блок, а не работать с явными высотами | -| Стабильное подтверждение | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) или [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Повторно проверяем то же семейство блоков, когда финальность догоняет ранее замеченное изменение | Подтверждает, что замеченное в оптимистичном режиме изменение действительно попало в финализированную историю | -| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Читаем данные заголовков, если для ответа достаточно времени и общего хода событий | Позволяет не запрашивать более широкий блок, когда хватает заголовков | -| Точный разбор через RPC | RPC [Блок по ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Блок по высоте](https://docs.fastnear.com/ru/rpc/block/block-by-height) | Получаем точный блок, как только понятно, какой именно блок важен | Здесь уже имеет смысл RPC, если нужен тот самый блок-объект, который вернул бы сам протокол | +| Последняя стабильная точка | NEAR Data [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Получаем высоту одного финализированного блока без угадывания | Даёт стабильную отправную точку для всего вопроса | +| Всё семейство блока | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) | Сканируем транзакции, receipts, результаты исполнения receipts и изменения состояния по целевому аккаунту | Это главная поверхность ответа на вопрос «был ли затронут мой контракт?» | +| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Используем, когда нужны только высота, хеш, время или заголовки чанков | Позволяет не тянуть более широкий payload блока, когда фильтрация по контракту не нужна | +| Необязательный follow-up по шарду | NEAR Data [`block-chunk`](https://docs.fastnear.com/ru/neardata/block-chunk) или [`block-shard`](https://docs.fastnear.com/ru/neardata/block-shard) | Повторно открываем только затронутый шард, если нужен более глубокий payload | Полезно, когда вы уже знаете, какой шард mattered | +| Точные поверхности для продолжения | [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc) | Переиспользуем найденные tx hash или receipt id только если нужна полная история исполнения | NEAR Data позволяет сначала понять, нужен ли вообще переход дальше | **Что должен включать полезный ответ** -- какой redirect target и какой разрешённый оптимистичный блок впервые запустили расследование -- когда helper для finality догнал его и в какой блок он разрешился -- изменил ли точный разбор через RPC интерпретацию +- финализированную высоту и хеш +- ответ “затронут / не затронут” +- счётчики прямых транзакций, входящих receipts, outcome-hit и state changes +- по одному sample tx hash или receipt id на категорию, когда он есть -### Shell-сценарий от оптимистичного сигнала к финализированному подтверждению +### Shell-сценарий от финализированного блока к ответу по контракту -Используйте этот сценарий, когда нужно сразу заметить свежее изменение в семействе блоков, а затем доказать, какой финализированный блок его догнал, и подтвердить именно эту высоту через RPC. +Используйте этот сценарий, когда целевой аккаунт уже известен и нужен один свежий финализированный ответ, а не длинный polling-цикл. **Что вы делаете** -- Смотрите redirect, который возвращает `GET /v0/last_block/optimistic`. -- Загружаете разрешённый оптимистичный блок и сохраняете его высоту и хеш. -- Смотрите redirect, который возвращает `GET /v0/last_block/final`, и сохраняете финализированный counterpart. -- Сравниваете оптимистичное и финализированное наблюдения, а затем переиспользуете финализированную высоту в RPC `block` по высоте. +- Получаете redirect target для последнего финализированного блока. +- Один раз загружаете полный документ блока. +- Собираете один компактный ответ по одному `TARGET_ACCOUNT_ID`. +- Получаете ответ “да / нет” плюс минимально полезные счётчики и sample-идентификаторы. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -RPC_URL=https://rpc.mainnet.fastnear.com - -OPTIMISTIC_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -printf 'Optimistic redirect target: %s\n' "$OPTIMISTIC_LOCATION" - -curl -s "$NEARDATA_BASE_URL$OPTIMISTIC_LOCATION" \ - | tee /tmp/neardata-optimistic-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' +TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -82,134 +94,139 @@ FINAL_LOCATION="$( | tr -d '\r' )" +BLOCK_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | sed -E 's#.*/([0-9]+)$#\1#')" + printf 'Final redirect target: %s\n' "$FINAL_LOCATION" +printf 'Final block height: %s\n' "$BLOCK_HEIGHT" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ - | jq '{height: .block.header.height, hash: .block.header.hash}' - -jq -n \ - --slurpfile optimistic /tmp/neardata-optimistic-block.json \ - --slurpfile final /tmp/neardata-final-block.json '{ - optimistic: { - height: $optimistic[0].block.header.height, - hash: $optimistic[0].block.header.hash - }, - final: { - height: $final[0].block.header.height, - hash: $final[0].block.header.hash - }, - same_height: ( - $optimistic[0].block.header.height - == $final[0].block.header.height - ) - }' - -BLOCK_HEIGHT="$(jq -r '.block.header.height' /tmp/neardata-final-block.json)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_height "$BLOCK_HEIGHT" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "block", - params: { - block_id: ($block_height | tonumber) + | tee /tmp/neardata-block.json >/dev/null + +jq --arg target "$TARGET_ACCOUNT_ID" ' + ( + [ + .shards[] + | .chunk.transactions[]? + | select((.transaction.receiver_id // .receiver_id) == $target) + | (.transaction.hash // .hash) + ] + ) as $txs + | ( + [ + .shards[] + | .chunk.receipts[]? + | select(.receiver_id == $target) + | .receipt_id + ] + ) as $receipts + | ( + [ + .shards[] + | .receipt_execution_outcomes[]? + | select( + (.receipt.receiver_id // "") == $target + or (.execution_outcome.outcome.executor_id // "") == $target + ) + | .tx_hash + | select(. != null) + ] + | unique + ) as $outcomes + | ( + [ + .shards[] + | .state_changes[]? + | select((.change.account_id // "") == $target) + | .type + ] + ) as $state_changes + | { + height: .block.header.height, + hash: .block.header.hash, + touched: ( + ($txs | length) > 0 + or ($receipts | length) > 0 + or ($outcomes | length) > 0 + or ($state_changes | length) > 0 + ), + direct_tx_count: ($txs | length), + incoming_receipt_count: ($receipts | length), + outcome_hit_count: ($outcomes | length), + state_change_count: ($state_changes | length), + sample_direct_tx: ($txs[0] // null), + sample_incoming_receipt: ($receipts[0] // null), + sample_outcome_tx_hash: ($outcomes[0] // null) } - }')" \ - | jq '{height: .result.header.height, hash: .result.header.hash, chunks: (.result.chunks | length)}' +' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json ``` -**Зачем нужен следующий шаг?** - -Так вы получаете обе стороны истории: самый ранний оптимистичный якорь и более поздний финализированный якорь. Как только helper для finality сообщил точную высоту блока, RPC становится естественным следующим шагом, если нужен точный блок-объект без догадок о том, что именно проверять. - -## Частые задачи - -### Отслеживать последний оптимистичный блок - -**Начните здесь** - -- [Оптимистичный блок](https://docs.fastnear.com/ru/neardata/block-optimistic) для самого свежего чтения по семейству блоков. - -**Следующая страница при необходимости** - -- [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic), если нужен маршрут перенаправления, который всегда ведёт к самому новому оптимистичному блоку. - -**Остановитесь, когда** - -- Уже можно сообщить о последнем оптимистичном блоке или зафиксировать отставание по свежести. - -**Переходите дальше, когда** - -- Нужна finalized-стабильность вместо максимальной свежести. Переходите к [Финализированному блоку по высоте](https://docs.fastnear.com/ru/neardata/block) или [Перенаправлению на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final). - -### Безопасно отслеживать ход финализации блоков - -**Начните здесь** +Если позже понадобятся более богатые списки или разбор по шардам, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. -- [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block), когда уже известна нужная высота. -- [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers), когда достаточно чтения заголовков. +Необязательное расширение: если всё же нужны `touched_shards`, их можно вычислить из того же сохранённого блока, не утяжеляя основной ответ: -**Следующая страница при необходимости** - -- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final), когда клиент должен следовать за самым новым финализированным блоком без предварительного вычисления высоты. - -**Остановитесь, когда** - -- Уже можно показывать движение финализированных блоков без перехода к более глубоким протокольным деталям. - -**Переходите дальше, когда** - -- Пользователю нужны точные поля блока или семантика транзакций. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). - -### Использовать маршруты перенаправления в клиенте опроса - -**Начните здесь** - -- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) или [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) в зависимости от требуемой свежести. - -**Следующая страница при необходимости** - -- Следуйте по URL блока, который вернул маршрут перенаправления, и уже там читайте нужные данные. - -**Остановитесь, когда** - -- Клиент надёжно проходит по маршруту перенаправления и получает нужный ресурс блока. - -**Переходите дальше, когда** - -- Само перенаправление мешает клиенту. Тогда переходите на прямые маршруты блоков. - -### Перейти от опроса свежих блоков к точному RPC-разбору - -**Начните здесь** - -- Используйте подходящий маршрут NEAR Data, чтобы найти недавний блок или событие в семействе блоков, которое нужно исследовать. - -**Следующая страница при необходимости** +```bash +jq --arg target "$TARGET_ACCOUNT_ID" ' + [ + .shards[] + | .shard_id as $shard_id + | select( + ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) + or ([.chunk.receipts[]? | .receiver_id] | index($target)) + or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) + or ([.state_changes[]? | .change.account_id] | index($target)) + ) + | $shard_id + ] | unique +' /tmp/neardata-block.json +``` -- [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или другой RPC-метод, как только станет понятно, какой именно блок или следующий объект для проверки нужен. +Если в этом ответе `touched: true` и нужен один follow-up на уровне шарда, откройте только первый затронутый шард: -**Остановитесь, когда** +```bash +TOUCHED_SHARD_ID="$( + jq -r --arg target "$TARGET_ACCOUNT_ID" ' + first( + .shards[] + | .shard_id as $shard_id + | select( + ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) + or ([.chunk.receipts[]? | .receiver_id] | index($target)) + or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) + or ([.state_changes[]? | .change.account_id] | index($target)) + ) + | $shard_id + ) // empty + ' /tmp/neardata-block.json +)" -- Уже можно чётко назвать недавний блок, который заслуживает проверки через RPC. +if [ -n "$TOUCHED_SHARD_ID" ]; then + curl -s "$NEARDATA_BASE_URL/v0/block/$BLOCK_HEIGHT/chunk/$TOUCHED_SHARD_ID" \ + | jq '{ + shard_id: .header.shard_id, + chunk_hash: .header.chunk_hash, + tx_hashes: ([.transactions[]? | (.transaction.hash // .hash)] | .[:5]), + receipt_ids: ([.receipts[]? | .receipt_id] | .[:5]), + receipt_receivers: ([.receipts[]? | .receiver_id] | .[:5]) + }' +fi +``` -**Переходите дальше, когда** +**Зачем нужен следующий шаг?** -- Пользователь просит точную структуру данных в терминах протокола, а не просто свежее чтение. +Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один из sample-идентификаторов уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. ## Частые ошибки -- Воспринимать NEAR Data как push-стрим, а не как API для опроса. -- Начинать с RPC, когда настоящая задача — мониторинг свежих блоков. -- Забывать, что невалидный ключ может вернуть `401` ещё до перенаправления, а сами перенаправления подходят не каждому HTTP-клиенту. -- Оставаться на NEAR Data после того, как пользователь уже попросил точные протокольные детали блока. +- Воспринимать NEAR Data как push-стрим, а не как polling- или point-read API. +- Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. +- Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. +- Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. +- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data точные shard id, tx hash и receipt id. ## Полезные связанные страницы - [NEAR Data API](https://docs.fastnear.com/ru/neardata) -- [RPC Reference](https://docs.fastnear.com/ru/rpc) - [Transactions API](https://docs.fastnear.com/ru/tx) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index 6438b2b..9364c2f 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -36,89 +36,31 @@ curl -s "$RPC_URL" \ ## Отправка и отслеживание транзакции -Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» +### Двухчастный паттерн: отправить транзакцию или отследить уже известный tx hash до финального исполнения -### Отправить транзакцию и затем проследить её от хеша до финального исполнения +Базовый паттерн: -Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. +- `broadcast_tx_async` для отправки +- `tx` с `wait_until: "FINAL"` для отслеживания +- `EXPERIMENTAL_tx_status` только если следующий вопрос уже про receipts -Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: +Этот walkthrough намеренно разбит на две части: + +- отправить новую подписанную транзакцию и сохранить возвращённый хеш +- отследить один известный исторический tx hash с воспроизводимым выводом + +Для части про отслеживание используется одна зафиксированная историческая транзакция, поэтому status-lookup идёт через архивный хост: - хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - signer: `mike.near` - receiver: `social.near` -- высота блока включения: `79574923` -- высота блока исполнения receipt для записи в SocialDB: `79574924` - -Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. - - Стратегия - Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. - - 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. - 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. - 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. - -**Что вы здесь решаете** - -- какой эндпоинт отправки брать первым -- что опрашивать после того, как у вас появился tx hash -- как `wait_until` связан с included-, optimistic- и final-гарантиями -- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` - -```mermaid -flowchart LR - S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] - A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] - T --> F["Транзакция полностью завершена"] - T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] - F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] -``` - -| Метод | Когда использовать | Что вернётся | Роль здесь | -| --- | --- | --- | --- | -| [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | -| [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | -| [`broadcast_tx_commit`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | -| [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | -| [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | - -**Карта статусов и ожидания** - -Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. - -| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | -| --- | --- | --- | -| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | -| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | -| `INCLUDED_FINAL` | блок включения уже финален | `tx` | -| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | -| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | - -Практическое различие очень простое: - -- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash -- используйте `tx` как обычный цикл опроса -- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу - -**Что вы делаете** +- `https://archival-rpc.mainnet.fastnear.com` -- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. -- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. -- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. -- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. +1. Отправьте новую подписанную транзакцию и сохраните возвращённый хеш. ```bash RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near -RECEIVER_ID=social.near -``` -1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. - -```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data '{ @@ -130,40 +72,16 @@ curl -s "$RPC_URL" \ | jq . ``` -В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. +Этот первый шаг нужен только для формы отправки. Именно возвращённый хеш вы потом будете отслеживать для своей живой транзакции. -2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. +2. Отслеживайте один известный tx hash, пока не получите самый простой финальный ответ. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "INCLUDED_FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' +RPC_URL=https://archival-rpc.mainnet.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near ``` -Что здесь важно заметить: - -- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality -- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения -- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt - -3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. - ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ @@ -181,18 +99,12 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - status: .result.status, + transaction_status: .result.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Что здесь важно заметить: - -- для исторической tx этот вызов тоже возвращается сразу -- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» -- для многих приложений именно здесь и стоит остановиться - -4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. +3. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда для этого известного tx уже нужен уровень receipts. ```bash curl -s "$RPC_URL" \ @@ -211,271 +123,52 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - status: .result.status, transaction_handoff: .result.transaction_outcome.outcome.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». - -5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transaction_handoff: .transactions[0].transaction_outcome.outcome.status - }' -``` - -Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» - -**Рекомендуемый паттерн** - -- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. -- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. -- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. +Если вы хотите, чтобы узел ждал за вас, используйте [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Но базовый паттерн на этой странице остаётся таким: отправка через `broadcast_tx_async`, затем отслеживание хеша через `tx`. ## Механика аккаунтов и ключей Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. -### Проверить и удалить старые function-call-ключи Near Social +### Может ли этот access key прямо сейчас вызвать этот контракт? -Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. +Используйте этот сценарий, когда у вас уже есть аккаунт, один public key и целевой контракт, а вам нужен простой ответ да или нет до того, как вы начнёте что-то подписывать. Стратегия - Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. + Сначала отфильтруйте ключи аккаунта, затем прочитайте точный ключ и только потом классифицируйте его права. - 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. - 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. - 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. + 01RPC view_access_key_list сужает список до ключей, которые вообще могут относиться к целевому контракту. + 02RPC view_access_key даёт точный permission-object для того public key, которым вы реально можете подписывать. + 03jq превращает этот permission-object в full_access, function_call_match, receiver_mismatch или method_not_allowed. **Что вы делаете** -- Через сам RPC получаете полный список access key аккаунта. -- Сужаете этот список до function-call-ключей, привязанных к `social.near`. -- Точно проверяете один выбранный ключ перед удалением. -- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. - -Сразу важны два ограничения: - -- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. -- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». +- Получаете access key аккаунта и сужаете список до нужного контракта. +- Точно проверяете тот ключ, которым собираетесь подписывать. +- Решаете, может ли он вызвать этот receiver и method, не выходя за пределы RPC. ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export SOCIAL_RECEIVER_ID=social.near -export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' -export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' -export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' -``` - -1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } - }')" \ - | tee /tmp/fastnear-access-keys.json >/dev/null - -jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' - .result.keys[] - | select((.access_key.permission | type) == "object") - | select(.access_key.permission.FunctionCall.receiver_id == $receiver) - | { - public_key, - nonce: .access_key.nonce, - receiver_id: .access_key.permission.FunctionCall.receiver_id, - method_names: .access_key.permission.FunctionCall.method_names, - allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") - } -' /tmp/fastnear-access-keys.json -``` - -Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. - -2. Ещё раз проверьте конкретный ключ перед удалением. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$DELETE_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } - }')" \ - | jq '{nonce: .result.nonce, permission: .result.permission}' -``` - -3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. - -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - is_function_call: true, - limit: 10 - }')" \ - | jq '{ - account_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_success - } - ] - }' -``` - -Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. - -4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. - -Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +TARGET_CONTRACT_ID=crossword.puzzle.near +TARGET_METHOD_NAME=new_puzzle +TARGET_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_WANT_TO_CHECK' -```bash -SIGNED_TX_BASE64="$( - node --input-type=module <<'EOF' - -const { - ACCOUNT_ID, - NETWORK_ID = 'mainnet', - RPC_URL = 'https://rpc.mainnet.fastnear.com', - DELETE_PUBLIC_KEY, - FULL_ACCESS_PUBLIC_KEY, - FULL_ACCESS_PRIVATE_KEY, -} = process.env; - -for (const name of [ - 'ACCOUNT_ID', - 'DELETE_PUBLIC_KEY', - 'FULL_ACCESS_PUBLIC_KEY', - 'FULL_ACCESS_PRIVATE_KEY', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); -const derivedPublicKey = keyPair.getPublicKey().toString(); - -if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { - throw new Error( - `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: ACCOUNT_ID, - public_key: FULL_ACCESS_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const transaction = transactions.createTransaction( - ACCOUNT_ID, - utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), - ACCOUNT_ID, - BigInt(accessKey.nonce) + 1n, - [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -)" +# Пример живых значений, проверенных 19 апреля 2026 года: +# ACCOUNT_ID=mike.near +# TARGET_CONTRACT_ID=crossword.puzzle.near +# TARGET_METHOD_NAME=new_puzzle +# TARGET_PUBLIC_KEY='ed25519:otwaB1X88ocpmUdC1B5XaifucfDLmLKaonb26KqTj96' ``` -5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. +1. Получите ключи аккаунта и сузьте их до целевого контракта. ```bash curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. - -```bash -if curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc: "2.0", @@ -487,60 +180,28 @@ if curl -s "$RPC_URL" \ finality: "final" } }')" \ - | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' - .result.keys[] - | select(.public_key == $public_key) - ' >/dev/null; then - echo "Key is still present: $DELETE_PUBLIC_KEY" -else - echo "Key deleted: $DELETE_PUBLIC_KEY" -fi -``` - -**Зачем нужен следующий шаг?** - -Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. - -### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? - -Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. - - Стратегия - Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. - - 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. - 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. - 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. - -**Что вы делаете** - -- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. -- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. -- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. -- Подтягиваете кандидата по транзакциям и различаете три разных ключа: - - ключ, который был добавлен - - public key верхнеуровневого signer - - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` - -Сразу важны три детали про nonce: - -- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. -- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. -- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' - -# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: -# export ACCOUNT_ID=mike.near -# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' + | tee /tmp/access-key-list.json >/dev/null + +jq --arg target_contract_id "$TARGET_CONTRACT_ID" '{ + candidate_keys: [ + .result.keys[] + | select( + .access_key.permission == "FullAccess" + or ( + (.access_key.permission | type) == "object" + and .access_key.permission.FunctionCall.receiver_id == $target_contract_id + ) + ) + | { + public_key, + nonce: .access_key.nonce, + permission: .access_key.permission + } + ] +}' /tmp/access-key-list.json ``` -1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. +2. Прочитайте точное состояние того ключа, который хотите оценить. ```bash curl -s "$RPC_URL" \ @@ -558,172 +219,69 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/key-origin-view.json >/dev/null + | tee /tmp/exact-access-key.json >/dev/null -CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" -ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" -SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" -SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" - -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_public_key "$TARGET_PUBLIC_KEY" \ - --argjson current_nonce "$CURRENT_NONCE" \ - --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ - --argjson search_from "$SEARCH_FROM" \ - --argjson search_to "$SEARCH_TO" \ - --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ - account_id: $account_id, - target_public_key: $target_public_key, - current_nonce: $current_nonce, - estimated_receipt_block: $estimated_receipt_block, - search_from_tx_block_height: $search_from, - search_to_tx_block_height: $search_to, - permission: ($permission | fromjson) - }' +jq '{nonce: .result.nonce, permission: .result.permission}' /tmp/exact-access-key.json ``` -Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. - -2. Ищите историю аккаунта только внутри этого диапазона блоков. +3. Превратите этот permission-object в ответ да или нет для этого контракта и метода. ```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_tx_block_height "$SEARCH_FROM" \ - --argjson to_tx_block_height "$SEARCH_TO" '{ - account_id: $account_id, - is_real_signer: true, - from_tx_block_height: $from_tx_block_height, - to_tx_block_height: $to_tx_block_height, - desc: false, - limit: 50 - }')" \ - | tee /tmp/key-origin-candidates.json >/dev/null - -jq '{ - txs_count, - candidate_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_signer, - is_real_signer, - is_predecessor, - is_receiver +jq -n \ + --slurpfile key /tmp/exact-access-key.json \ + --arg target_contract_id "$TARGET_CONTRACT_ID" \ + --arg target_method_name "$TARGET_METHOD_NAME" ' + ($key[0].result.permission) as $permission + | if $permission == "FullAccess" then + { + can_call_now: true, + reason: "full_access" } - ] -}' /tmp/key-origin-candidates.json -``` - -Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. - -3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. - -```bash -TX_HASHES_JSON="$( - jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json -)" - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ - | tee /tmp/key-origin-transactions.json >/dev/null - -jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "direct", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $tx.transaction.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }), - ($tx.transaction.actions[]? - | .Delegate? - | .delegate_action as $delegate - | $delegate.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "delegated", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $delegate.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - tx_block_hash: $tx.execution_outcome.block_hash, - receiver_id: $tx.transaction.receiver_id - } + . -' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json -``` - -Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. - -Для примерного ключа `mike.near` выше совпадение оказывается делегированным: - -- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` -- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` -- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` -- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` - -4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. - -```bash -ADD_KEY_RECEIPT_ID="$( - jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | .receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) - | .receipt.receipt_id - ' /tmp/key-origin-transactions.json | head -n 1 -)" - -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receipt_block_height: .receipt.block_height, - tx_block_height: .receipt.tx_block_height, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - transaction_hash: .receipt.transaction_hash - }' + elif $permission.FunctionCall.receiver_id != $target_contract_id then + { + can_call_now: false, + reason: "receiver_mismatch", + receiver_id: $permission.FunctionCall.receiver_id + } + elif ( + ($permission.FunctionCall.method_names | length) == 0 + or ($permission.FunctionCall.method_names | index($target_method_name)) + ) then + { + can_call_now: true, + reason: ( + if ($permission.FunctionCall.method_names | length) == 0 + then "function_call_any_method" + else "function_call_method_match" + end + ), + allowance: ($permission.FunctionCall.allowance // "unlimited") + } + else + { + can_call_now: false, + reason: "method_not_allowed", + allowed_methods: $permission.FunctionCall.method_names + } + end' ``` -Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. +Для примерного ключа `mike.near` выше на 19 апреля 2026 года ответ получается `can_call_now: true`: это function-call-key для `crossword.puzzle.near`, а `method_names: ["new_puzzle"]` явно разрешает тот метод, который мы проверяем. **Зачем нужен следующий шаг?** -Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. +`view_access_key_list` — самый быстрый фильтр на уровне контракта. `view_access_key` — точная проверка полномочий для того public key, которым вы действительно хотите пользоваться. Если ответ `false`, вам нужен другой ключ или другая схема permissions, а не более глубокая историческая трассировка. -### Проверить регистрацию FT storage и затем перевести токены +### Нужно ли этому получателю сначала зарегистрировать FT storage? -Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». +Используйте этот сценарий, когда история звучит так: «я собираюсь отправить FT-токен и хочу получить простой ответ “нужен ли сначала `storage_deposit`?”». Стратегия - Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. + Сначала прочитайте storage-состояние получателя и остановитесь, как только станет понятно, может ли `ft_transfer` уже пройти. 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. 02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит. - 03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог. + 03jq превращает эти два чтения в один ответ: «перевод уже может идти» или «сначала нужен `storage_deposit`». **Сеть** @@ -734,24 +292,19 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) - [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) -В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. +В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Здесь важен именно read-only-ответ: нужен ли сначала `storage_deposit`, или путь перевода уже может продолжаться. **Что вы делаете** - Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. -- При необходимости получаете минимальный размер storage deposit. -- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. -- Подтверждаете баланс получателя тем же view-методом самого контракта. +- Получаете точный минимальный размер storage deposit на этом же контракте. +- Останавливаетесь, как только понимаете: `ft_transfer` уже может идти или сначала нужен `storage_deposit`. ```bash export NETWORK_ID=testnet export RPC_URL=https://rpc.testnet.fastnear.com export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet -export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' -export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' -export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' ``` 1. Проверьте, зарегистрирован ли получатель на FT-контракте. @@ -787,7 +340,7 @@ jq '{ }' /tmp/ft-storage-balance.json ``` -2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. +2. Получите минимальный storage deposit на этом же контракте. ```bash MIN_STORAGE_YOCTO="$( @@ -812,214 +365,39 @@ MIN_STORAGE_YOCTO="$( printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" ``` -3. Определите одну переиспользуемую функцию подписи для function-call к контракту. - -Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. - -```bash -sign_function_call() { - METHOD_NAME="$1" \ - ARGS_JSON="$2" \ - DEPOSIT_YOCTO="$3" \ - GAS_TGAS="$4" \ - node --input-type=module <<'EOF' - -const { - NETWORK_ID = 'testnet', - RPC_URL = 'https://rpc.testnet.fastnear.com', - TOKEN_CONTRACT_ID, - SENDER_ACCOUNT_ID, - SENDER_PUBLIC_KEY, - SENDER_PRIVATE_KEY, - METHOD_NAME, - ARGS_JSON, - DEPOSIT_YOCTO = '0', - GAS_TGAS = '100', -} = process.env; - -for (const name of [ - 'TOKEN_CONTRACT_ID', - 'SENDER_ACCOUNT_ID', - 'SENDER_PUBLIC_KEY', - 'SENDER_PRIVATE_KEY', - 'METHOD_NAME', - 'ARGS_JSON', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); -const signer = await InMemorySigner.fromKeyPair( - NETWORK_ID, - SENDER_ACCOUNT_ID, - keyPair -); - -const derivedPublicKey = keyPair.getPublicKey().toString(); -if (derivedPublicKey !== SENDER_PUBLIC_KEY) { - throw new Error( - `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: SENDER_ACCOUNT_ID, - public_key: SENDER_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const action = transactions.functionCall( - METHOD_NAME, - Buffer.from(ARGS_JSON), - BigInt(GAS_TGAS) * 10n ** 12n, - BigInt(DEPOSIT_YOCTO) -); - -const transaction = transactions.createTransaction( - SENDER_ACCOUNT_ID, - utils.PublicKey.fromString(SENDER_PUBLIC_KEY), - TOKEN_CONTRACT_ID, - BigInt(accessKey.nonce) + 1n, - [action], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - SENDER_ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -} -``` - -4. При необходимости сначала зарегистрируйте storage для получателя. - -```bash -if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then - SIGNED_TX_BASE64="$( - sign_function_call \ - storage_deposit \ - "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id, - registration_only: true - }')" \ - "$MIN_STORAGE_YOCTO" \ - 100 - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash - }' -fi -``` - -5. После готовности storage переведите FT. +3. Превратите эти два чтения в один ответ о готовности перевода. ```bash -SIGNED_TX_BASE64="$( - sign_function_call \ - ft_transfer \ - "$(jq -nc \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ - receiver_id: $receiver_id, - amount: $amount, - memo: "FastNear RPC example" - }')" \ - 1 \ - 100 -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Подтвердите FT-баланс получателя тем же view-методом контракта. - -```bash -RECEIVER_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - receiver_balance: (.result.result | implode | fromjson) +jq -n \ + --slurpfile balance /tmp/ft-storage-balance.json \ + --slurpfile bounds /tmp/ft-storage-bounds.json \ + --arg receiver_account_id "$RECEIVER_ACCOUNT_ID" ' + ( + $balance[0].result.result + | if length == 0 then null else (implode | fromjson) end + ) as $storage + | ( + $bounds[0].result.result + | implode + | fromjson + ) as $bounds + | { + receiver_account_id: $receiver_account_id, + receiver_registered: ($storage != null), + current_storage: $storage, + minimum_storage_deposit_yocto: $bounds.min, + next_step: ( + if $storage != null + then "получатель уже зарегистрирован; ft_transfer может продолжаться" + else "сначала отправьте storage_deposit, потом делайте ft_transfer" + end + ) }' ``` **Зачем нужен следующий шаг?** -Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +Это чистый RPC-вопрос в этом workflow: «зарегистрирован ли уже получатель и какой минимальный депозит потребует контракт, если нет?» Подписанный write-path зависит уже от вашего wallet, CLI или backend-интеграции, поэтому в самый маленький core RPC-пример он не входит. ## Чтения контракта и сырое состояние @@ -1027,42 +405,12 @@ curl -s "$RPC_URL" \ ### Прочитать счётчик прямо из состояния контракта, а потом подтвердить его через view-метод -Используйте этот сценарий, когда история простая: «я знаю, что этот контракт держит счётчик, но можно ли прочитать это число напрямую из storage, не вызывая код контракта?» - -В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: оба чтения должны совпасть в тот момент, когда вы их запускаете: - -- `view_state` читает сырой ключ `STATE` прямо из storage контракта -- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API - - Стратегия - Сначала прочитайте raw storage, затем декодируйте байты, а потом дайте контракту подтвердить тот же ответ через view-метод. - - 01RPC view_state читает сырой ключ STATE, не запуская код контракта. - 02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта. - 03RPC call_function get_num — это удобная перепроверка того, что raw-state-чтение и view-метод по-прежнему дают один и тот же ответ. - -Здесь важнее ментальная модель, чем сам счётчик: - -- `view_state` — это прямое чтение storage из trie -- `call_function` исполняет read-only-метод контракта -- оба способа могут ответить на один и тот же вопрос, но делают разную работу - -```mermaid -flowchart LR - S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] - R --> D["Декодировать base64 + Borsh"] - D --> N["Знаковое значение счётчика"] - C["RPC call_function get_num"] --> J["JSON-результат метода"] - N --> X["Сравнить"] - J --> X - X --> A["Одно и то же текущее значение"] -``` +Используйте этот сценарий, когда вы уже знаете точное семейство storage-ключей и хотите увидеть самый короткий контраст между raw state и публичным методом чтения контракта. -**Что вы делаете** +Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`: -- Читаете сырой ключ `STATE` из storage контракта. -- Декодируете возвращённые байты в текущее знаковое значение счётчика. -- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. +- `view_state` читает сырой ключ `STATE` напрямую +- `call_function get_num` спрашивает у контракта то же текущее число ```bash export NETWORK_ID=testnet @@ -1071,7 +419,7 @@ export CONTRACT_ID=counter.near-examples.testnet export STATE_PREFIX_BASE64=U1RBVEU= ``` -1. Сначала прочитайте сырое состояние контракта. +1. Сначала прочитайте сырой ключ `STATE`. ```bash curl -s "$RPC_URL" \ @@ -1092,46 +440,28 @@ curl -s "$RPC_URL" \ | tee /tmp/counter-view-state.json >/dev/null jq '{ - block_height: .result.block_height, - key_base64: .result.values[0].key, + key: (.result.values[0].key | @base64d), value_base64: .result.values[0].value }' /tmp/counter-view-state.json - -jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json ``` -Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. +Здесь должен появиться `key: "STATE"`. Это и есть тот случай, когда `view_state` уместен: семейство ключей вам уже известно заранее. -2. Декодируйте байты значения в знаковое число счётчика. +2. Декодируйте сырые байты. ```bash RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" -python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . +python3 - "$RAW_VALUE_BASE64" <<'PY' raw = base64.b64decode(sys.argv[1]) - -print(json.dumps({ - "value_base64": sys.argv[1], - "bytes": list(raw), - "hex": raw.hex(), - "signed_i8": int.from_bytes(raw, "little", signed=True), - "unsigned_u8": int.from_bytes(raw, "little", signed=False), -})) +print(int.from_bytes(raw, "little", signed=True)) PY ``` -Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. +Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть сложнее, но правило то же: сначала байты, потом схема. -Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. - -Переиспользуемый рецепт здесь короткий: - -- `view_state` возвращает сырые байты в base64 -- вы декодируете эти байты по известной схеме хранения контракта -- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема - -3. Теперь спросите контракт более привычным способом и сравните. +3. Теперь спросите контракт привычным способом и сравните. ```bash curl -s "$RPC_URL" \ @@ -1156,7 +486,7 @@ jq '{ }' /tmp/counter-call-function.json ``` -4. Сравните оба ответа напрямую. +4. Сравните оба ответа. ```bash RAW_STATE_NUMBER="$( @@ -1180,35 +510,24 @@ jq -n \ }' ``` -Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: - -- `view_state` ответил на вопрос, прочитав storage напрямую -- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта - **Зачем нужен следующий шаг?** -Используйте `view_state`, когда настоящий вопрос относится к точному storage и вы уже знаете семейство ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). +Используйте `view_state`, когда вы уже знаете точное семейство storage-ключей и хотите raw bytes. Используйте `call_function`, когда вам нужен публичный метод чтения самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда уже стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). -## Точные чтения NEAR Social и BOS +## Точные чтения SocialDB -Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. +Оставайтесь на точных чтениях через `call_function get`, когда вы уже знаете нужный ключ SocialDB. На обычном RPC raw `view_state` для `social.near` не подходит как обучающий путь, потому что состояние контракта слишком велико для прямого чтения. -### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? +### Прочитать один пост SocialDB ровно в том виде, как он хранится сейчас -Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». +Используйте этот сценарий, когда продукту, support-инструменту или агенту уже известен аккаунт и нужен живой payload поста из SocialDB без перехода в историю транзакций. Стратегия - Спросите у social.near ровно о двух вещах, которые важны до подписи. - - 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. - 02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near. - 03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer. - -Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: + Сначала прочитайте текущий ключ поста, затем получите точный payload этого поста из social.near. -- есть ли у целевого аккаунта storage на `social.near`? -- если есть, осталось ли там ещё место? -- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? + 01RPC call_function get по mike.near/index/post показывает, какой ключ поста сейчас активен. + 02RPC call_function get по mike.near/post/main возвращает точный сохранённый payload поста. + 03Если следующий вопрос становится «какая транзакция это записала?», переключайтесь на [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). **Официальные ссылки** @@ -1216,52 +535,23 @@ jq -n \ **Что вы делаете** -- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. -- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. -- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. -- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». +- Читаете текущий указатель поста под `mike.near/index/post`. +- Используете этот ключ, чтобы получить точный payload по `mike.near/post/`. +- Останавливаетесь на точном JSON и расширяетесь в историю только тогда, когда действительно нужна provenance. ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com export SOCIAL_CONTRACT_ID=social.near export ACCOUNT_ID=mike.near -export SIGNER_ACCOUNT_ID=mike.near -``` - -1. Сначала проверьте сам аккаунт signer. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } - }')" \ - | tee /tmp/social-publish-signer.json >/dev/null - -jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - signer_account_id: $signer_account_id, - amount: .result.amount, - locked: .result.locked, - storage_usage: .result.storage_usage -}' /tmp/social-publish-signer.json ``` -Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. - -2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. +1. Сначала прочитайте текущий указатель поста. ```bash -SOCIAL_STORAGE_ARGS_BASE64="$( +INDEX_POST_ARGS_BASE64="$( jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id + keys: [($account_id + "/index/post")] }' | base64 | tr -d '\n' )" @@ -1269,208 +559,53 @@ curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + --arg args_base64 "$INDEX_POST_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { request_type: "call_function", account_id: $account_id, - method_name: "get_account_storage", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-account-storage.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - storage: (.result.result | implode | fromjson), - storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) -}' /tmp/social-account-storage.json -``` - -Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. - -3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. - -```bash -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - jq -n --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - signer_matches_target: true, - permission_granted: true, - reason: "owner write" - }' -else - WRITE_PERMISSION_ARGS_BASE64="$( - jq -nc \ - --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ - --arg key "$ACCOUNT_ID" '{ - predecessor_id: $predecessor_id, - key: $key - }' | base64 | tr -d '\n' - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - signer_matches_target: false, - permission_granted: (.result.result | implode | fromjson) - }' -fi -``` - -4. Сведите проверку storage и разрешения в один читаемый итог. - -```bash -AVAILABLE_BYTES="$( - jq -r ' - .result.result - | if length == 0 then "0" - else (implode | fromjson | .available_bytes // 0 | tostring) - end - ' /tmp/social-account-storage.json -)" - -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - PERMISSION_GRANTED=true -else - PERMISSION_GRANTED="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq -r '.result.result | implode | fromjson' - )" -fi - -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ - --argjson available_bytes "$AVAILABLE_BYTES" \ - --argjson permission_granted "$PERMISSION_GRANTED" '{ - account_id: $account_id, - signer_account_id: $signer_account_id, - storage_ready: ($available_bytes > 0), - permission_ready: $permission_granted, - ready_to_publish_now: (($available_bytes > 0) and $permission_granted) - }' -``` - -Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. - -**Зачем нужен следующий шаг?** - -Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. - -### Что прямо сейчас содержит `mob.near/widget/Profile`? - -Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». - - Стратегия - Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. - - 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. - 02RPC call_function get читает точный исходник widget/Profile. - 03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples. - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Что вы делаете** - -- Спрашиваете у `social.near` каталог виджетов под `mob.near`. -- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. -- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. -- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mob.near -export WIDGET_NAME=Profile -``` - -1. Получите каталог виджетов и сохраните высоты блоков последней записи. - -```bash -WIDGET_KEYS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/widget/*")], - options: {return_type: "BlockHeight"} - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "keys", + method_name: "get", args_base64: $args_base64, finality: "final" } }')" \ - | tee /tmp/social-widget-keys.json >/dev/null + | tee /tmp/social-index-post.json >/dev/null jq --arg account_id "$ACCOUNT_ID" ' .result.result | implode | fromjson - | .[$account_id].widget - | to_entries - | sort_by(.value * -1) - | map({ - widget_name: .key, - last_write_block: .value - }) - | .[0:20] -' /tmp/social-widget-keys.json + | { + account_id: $account_id, + index_entry: (.[$account_id].index.post | fromjson), + current_post_key: (.[$account_id].index.post | fromjson | .key) + } +' /tmp/social-index-post.json ``` -2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. +На момент написания текущим ключом поста для `mike.near` был `main`. + +2. Прочитайте точный payload этого поста. ```bash -WIDGET_GET_ARGS_BASE64="$( +POST_KEY="$( + jq -r --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].index.post + | fromjson + | .key + ' /tmp/social-index-post.json +)" + +POST_ARGS_BASE64="$( jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] + --arg post_key "$POST_KEY" '{ + keys: [($account_id + "/post/" + $post_key)] }' | base64 | tr -d '\n' )" @@ -1478,7 +613,7 @@ curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + --arg args_base64 "$POST_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", @@ -1490,131 +625,25 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/social-widget-source.json >/dev/null + | tee /tmp/social-post-main.json >/dev/null -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:25] - | join("\n") - ' /tmp/social-widget-source.json -``` - -3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. - -```bash -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - ' /tmp/social-widget-keys.json \ - | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +jq --arg account_id "$ACCOUNT_ID" --arg post_key "$POST_KEY" ' + .result.result + | implode + | fromjson + | { + account_id: $account_id, + post_key: $post_key, + post: (.[$account_id].post[$post_key] | fromjson) + } +' /tmp/social-post-main.json ``` -На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. +Так вы получаете точный JSON, который хранится для текущего поста, включая поля вроде `type`, `text` и `image`. **Зачем нужен следующий шаг?** -Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. - -## Частые задачи - -### Проверить точное состояние аккаунта или ключа доступа - -**Начните здесь** - -- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для точных полей аккаунта. -- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. - -**Следующая страница при необходимости** - -- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» - -**Остановитесь, когда** - -- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. - -**Переходите дальше, когда** - -- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. -- Пользователя интересует не текущее состояние, а недавняя история активности. - -### Проверить один точный блок или снимок состояния протокола - -**Начните здесь** - -- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. -- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» -- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. - -**Следующая страница при необходимости** - -- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. -- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» - -**Остановитесь, когда** - -- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. - -**Переходите дальше, когда** - -- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). -- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Что этот контракт возвращает прямо сейчас? - -**Начните здесь** - -- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» -- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. -- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. -- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» - -**Следующая страница при необходимости** - -- [FastNear API](https://docs.fastnear.com/ru/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. -- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» - -**Остановитесь, когда** - -- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. - -**Переходите дальше, когда** - -- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. -- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» - -### Отправить транзакцию и подтвердить результат - -**Начните здесь** - -- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. -- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. -- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. -- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» - -**Остановитесь, когда** - -- У вас уже есть результат отправки и нужный финальный статус. - -**Переходите дальше, когда** - -- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. -- Нужен уже не единичный статус, а более широкий сценарий расследования. +Это чистый RPC-паттерн для SocialDB: спросите у контракта один точный ключ, декодируйте возвращённый JSON и остановитесь. Если вопрос превращается в «кто и когда это записал?», переходите к примерам по транзакциям, а не пытайтесь brute-force читать raw state `social.near`. ## Частые ошибки diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index 6438b2b..9364c2f 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -36,89 +36,31 @@ curl -s "$RPC_URL" \ ## Отправка и отслеживание транзакции -Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» +### Двухчастный паттерн: отправить транзакцию или отследить уже известный tx hash до финального исполнения -### Отправить транзакцию и затем проследить её от хеша до финального исполнения +Базовый паттерн: -Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. +- `broadcast_tx_async` для отправки +- `tx` с `wait_until: "FINAL"` для отслеживания +- `EXPERIMENTAL_tx_status` только если следующий вопрос уже про receipts -Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: +Этот walkthrough намеренно разбит на две части: + +- отправить новую подписанную транзакцию и сохранить возвращённый хеш +- отследить один известный исторический tx hash с воспроизводимым выводом + +Для части про отслеживание используется одна зафиксированная историческая транзакция, поэтому status-lookup идёт через архивный хост: - хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - signer: `mike.near` - receiver: `social.near` -- высота блока включения: `79574923` -- высота блока исполнения receipt для записи в SocialDB: `79574924` - -Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. - - Стратегия - Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. - - 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. - 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. - 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. - -**Что вы здесь решаете** - -- какой эндпоинт отправки брать первым -- что опрашивать после того, как у вас появился tx hash -- как `wait_until` связан с included-, optimistic- и final-гарантиями -- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` - -```mermaid -flowchart LR - S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] - A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] - T --> F["Транзакция полностью завершена"] - T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] - F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] -``` - -| Метод | Когда использовать | Что вернётся | Роль здесь | -| --- | --- | --- | --- | -| [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | -| [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | -| [`broadcast_tx_commit`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | -| [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | -| [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | - -**Карта статусов и ожидания** - -Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. - -| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | -| --- | --- | --- | -| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | -| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | -| `INCLUDED_FINAL` | блок включения уже финален | `tx` | -| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | -| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | - -Практическое различие очень простое: - -- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash -- используйте `tx` как обычный цикл опроса -- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу - -**Что вы делаете** +- `https://archival-rpc.mainnet.fastnear.com` -- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. -- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. -- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. -- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. +1. Отправьте новую подписанную транзакцию и сохраните возвращённый хеш. ```bash RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near -RECEIVER_ID=social.near -``` -1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. - -```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data '{ @@ -130,40 +72,16 @@ curl -s "$RPC_URL" \ | jq . ``` -В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. +Этот первый шаг нужен только для формы отправки. Именно возвращённый хеш вы потом будете отслеживать для своей живой транзакции. -2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. +2. Отслеживайте один известный tx hash, пока не получите самый простой финальный ответ. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "INCLUDED_FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' +RPC_URL=https://archival-rpc.mainnet.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near ``` -Что здесь важно заметить: - -- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality -- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения -- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt - -3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. - ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ @@ -181,18 +99,12 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - status: .result.status, + transaction_status: .result.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Что здесь важно заметить: - -- для исторической tx этот вызов тоже возвращается сразу -- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» -- для многих приложений именно здесь и стоит остановиться - -4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. +3. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда для этого известного tx уже нужен уровень receipts. ```bash curl -s "$RPC_URL" \ @@ -211,271 +123,52 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - status: .result.status, transaction_handoff: .result.transaction_outcome.outcome.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». - -5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transaction_handoff: .transactions[0].transaction_outcome.outcome.status - }' -``` - -Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» - -**Рекомендуемый паттерн** - -- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. -- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. -- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. +Если вы хотите, чтобы узел ждал за вас, используйте [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Но базовый паттерн на этой странице остаётся таким: отправка через `broadcast_tx_async`, затем отслеживание хеша через `tx`. ## Механика аккаунтов и ключей Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. -### Проверить и удалить старые function-call-ключи Near Social +### Может ли этот access key прямо сейчас вызвать этот контракт? -Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. +Используйте этот сценарий, когда у вас уже есть аккаунт, один public key и целевой контракт, а вам нужен простой ответ да или нет до того, как вы начнёте что-то подписывать. Стратегия - Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. + Сначала отфильтруйте ключи аккаунта, затем прочитайте точный ключ и только потом классифицируйте его права. - 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. - 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. - 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. + 01RPC view_access_key_list сужает список до ключей, которые вообще могут относиться к целевому контракту. + 02RPC view_access_key даёт точный permission-object для того public key, которым вы реально можете подписывать. + 03jq превращает этот permission-object в full_access, function_call_match, receiver_mismatch или method_not_allowed. **Что вы делаете** -- Через сам RPC получаете полный список access key аккаунта. -- Сужаете этот список до function-call-ключей, привязанных к `social.near`. -- Точно проверяете один выбранный ключ перед удалением. -- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. - -Сразу важны два ограничения: - -- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. -- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». +- Получаете access key аккаунта и сужаете список до нужного контракта. +- Точно проверяете тот ключ, которым собираетесь подписывать. +- Решаете, может ли он вызвать этот receiver и method, не выходя за пределы RPC. ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export SOCIAL_RECEIVER_ID=social.near -export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' -export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' -export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' -``` - -1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } - }')" \ - | tee /tmp/fastnear-access-keys.json >/dev/null - -jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' - .result.keys[] - | select((.access_key.permission | type) == "object") - | select(.access_key.permission.FunctionCall.receiver_id == $receiver) - | { - public_key, - nonce: .access_key.nonce, - receiver_id: .access_key.permission.FunctionCall.receiver_id, - method_names: .access_key.permission.FunctionCall.method_names, - allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") - } -' /tmp/fastnear-access-keys.json -``` - -Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. - -2. Ещё раз проверьте конкретный ключ перед удалением. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$DELETE_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } - }')" \ - | jq '{nonce: .result.nonce, permission: .result.permission}' -``` - -3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. - -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - is_function_call: true, - limit: 10 - }')" \ - | jq '{ - account_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_success - } - ] - }' -``` - -Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. - -4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. - -Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +TARGET_CONTRACT_ID=crossword.puzzle.near +TARGET_METHOD_NAME=new_puzzle +TARGET_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_WANT_TO_CHECK' -```bash -SIGNED_TX_BASE64="$( - node --input-type=module <<'EOF' - -const { - ACCOUNT_ID, - NETWORK_ID = 'mainnet', - RPC_URL = 'https://rpc.mainnet.fastnear.com', - DELETE_PUBLIC_KEY, - FULL_ACCESS_PUBLIC_KEY, - FULL_ACCESS_PRIVATE_KEY, -} = process.env; - -for (const name of [ - 'ACCOUNT_ID', - 'DELETE_PUBLIC_KEY', - 'FULL_ACCESS_PUBLIC_KEY', - 'FULL_ACCESS_PRIVATE_KEY', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); -const derivedPublicKey = keyPair.getPublicKey().toString(); - -if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { - throw new Error( - `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: ACCOUNT_ID, - public_key: FULL_ACCESS_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const transaction = transactions.createTransaction( - ACCOUNT_ID, - utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), - ACCOUNT_ID, - BigInt(accessKey.nonce) + 1n, - [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -)" +# Пример живых значений, проверенных 19 апреля 2026 года: +# ACCOUNT_ID=mike.near +# TARGET_CONTRACT_ID=crossword.puzzle.near +# TARGET_METHOD_NAME=new_puzzle +# TARGET_PUBLIC_KEY='ed25519:otwaB1X88ocpmUdC1B5XaifucfDLmLKaonb26KqTj96' ``` -5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. +1. Получите ключи аккаунта и сузьте их до целевого контракта. ```bash curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. - -```bash -if curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc: "2.0", @@ -487,60 +180,28 @@ if curl -s "$RPC_URL" \ finality: "final" } }')" \ - | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' - .result.keys[] - | select(.public_key == $public_key) - ' >/dev/null; then - echo "Key is still present: $DELETE_PUBLIC_KEY" -else - echo "Key deleted: $DELETE_PUBLIC_KEY" -fi -``` - -**Зачем нужен следующий шаг?** - -Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. - -### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? - -Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. - - Стратегия - Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. - - 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. - 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. - 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. - -**Что вы делаете** - -- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. -- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. -- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. -- Подтягиваете кандидата по транзакциям и различаете три разных ключа: - - ключ, который был добавлен - - public key верхнеуровневого signer - - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` - -Сразу важны три детали про nonce: - -- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. -- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. -- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' - -# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: -# export ACCOUNT_ID=mike.near -# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' + | tee /tmp/access-key-list.json >/dev/null + +jq --arg target_contract_id "$TARGET_CONTRACT_ID" '{ + candidate_keys: [ + .result.keys[] + | select( + .access_key.permission == "FullAccess" + or ( + (.access_key.permission | type) == "object" + and .access_key.permission.FunctionCall.receiver_id == $target_contract_id + ) + ) + | { + public_key, + nonce: .access_key.nonce, + permission: .access_key.permission + } + ] +}' /tmp/access-key-list.json ``` -1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. +2. Прочитайте точное состояние того ключа, который хотите оценить. ```bash curl -s "$RPC_URL" \ @@ -558,172 +219,69 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/key-origin-view.json >/dev/null + | tee /tmp/exact-access-key.json >/dev/null -CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" -ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" -SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" -SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" - -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_public_key "$TARGET_PUBLIC_KEY" \ - --argjson current_nonce "$CURRENT_NONCE" \ - --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ - --argjson search_from "$SEARCH_FROM" \ - --argjson search_to "$SEARCH_TO" \ - --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ - account_id: $account_id, - target_public_key: $target_public_key, - current_nonce: $current_nonce, - estimated_receipt_block: $estimated_receipt_block, - search_from_tx_block_height: $search_from, - search_to_tx_block_height: $search_to, - permission: ($permission | fromjson) - }' +jq '{nonce: .result.nonce, permission: .result.permission}' /tmp/exact-access-key.json ``` -Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. - -2. Ищите историю аккаунта только внутри этого диапазона блоков. +3. Превратите этот permission-object в ответ да или нет для этого контракта и метода. ```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_tx_block_height "$SEARCH_FROM" \ - --argjson to_tx_block_height "$SEARCH_TO" '{ - account_id: $account_id, - is_real_signer: true, - from_tx_block_height: $from_tx_block_height, - to_tx_block_height: $to_tx_block_height, - desc: false, - limit: 50 - }')" \ - | tee /tmp/key-origin-candidates.json >/dev/null - -jq '{ - txs_count, - candidate_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_signer, - is_real_signer, - is_predecessor, - is_receiver +jq -n \ + --slurpfile key /tmp/exact-access-key.json \ + --arg target_contract_id "$TARGET_CONTRACT_ID" \ + --arg target_method_name "$TARGET_METHOD_NAME" ' + ($key[0].result.permission) as $permission + | if $permission == "FullAccess" then + { + can_call_now: true, + reason: "full_access" } - ] -}' /tmp/key-origin-candidates.json -``` - -Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. - -3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. - -```bash -TX_HASHES_JSON="$( - jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json -)" - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ - | tee /tmp/key-origin-transactions.json >/dev/null - -jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "direct", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $tx.transaction.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }), - ($tx.transaction.actions[]? - | .Delegate? - | .delegate_action as $delegate - | $delegate.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "delegated", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $delegate.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - tx_block_hash: $tx.execution_outcome.block_hash, - receiver_id: $tx.transaction.receiver_id - } + . -' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json -``` - -Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. - -Для примерного ключа `mike.near` выше совпадение оказывается делегированным: - -- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` -- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` -- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` -- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` - -4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. - -```bash -ADD_KEY_RECEIPT_ID="$( - jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | .receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) - | .receipt.receipt_id - ' /tmp/key-origin-transactions.json | head -n 1 -)" - -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receipt_block_height: .receipt.block_height, - tx_block_height: .receipt.tx_block_height, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - transaction_hash: .receipt.transaction_hash - }' + elif $permission.FunctionCall.receiver_id != $target_contract_id then + { + can_call_now: false, + reason: "receiver_mismatch", + receiver_id: $permission.FunctionCall.receiver_id + } + elif ( + ($permission.FunctionCall.method_names | length) == 0 + or ($permission.FunctionCall.method_names | index($target_method_name)) + ) then + { + can_call_now: true, + reason: ( + if ($permission.FunctionCall.method_names | length) == 0 + then "function_call_any_method" + else "function_call_method_match" + end + ), + allowance: ($permission.FunctionCall.allowance // "unlimited") + } + else + { + can_call_now: false, + reason: "method_not_allowed", + allowed_methods: $permission.FunctionCall.method_names + } + end' ``` -Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. +Для примерного ключа `mike.near` выше на 19 апреля 2026 года ответ получается `can_call_now: true`: это function-call-key для `crossword.puzzle.near`, а `method_names: ["new_puzzle"]` явно разрешает тот метод, который мы проверяем. **Зачем нужен следующий шаг?** -Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. +`view_access_key_list` — самый быстрый фильтр на уровне контракта. `view_access_key` — точная проверка полномочий для того public key, которым вы действительно хотите пользоваться. Если ответ `false`, вам нужен другой ключ или другая схема permissions, а не более глубокая историческая трассировка. -### Проверить регистрацию FT storage и затем перевести токены +### Нужно ли этому получателю сначала зарегистрировать FT storage? -Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». +Используйте этот сценарий, когда история звучит так: «я собираюсь отправить FT-токен и хочу получить простой ответ “нужен ли сначала `storage_deposit`?”». Стратегия - Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. + Сначала прочитайте storage-состояние получателя и остановитесь, как только станет понятно, может ли `ft_transfer` уже пройти. 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. 02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит. - 03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог. + 03jq превращает эти два чтения в один ответ: «перевод уже может идти» или «сначала нужен `storage_deposit`». **Сеть** @@ -734,24 +292,19 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) - [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) -В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. +В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Здесь важен именно read-only-ответ: нужен ли сначала `storage_deposit`, или путь перевода уже может продолжаться. **Что вы делаете** - Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. -- При необходимости получаете минимальный размер storage deposit. -- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. -- Подтверждаете баланс получателя тем же view-методом самого контракта. +- Получаете точный минимальный размер storage deposit на этом же контракте. +- Останавливаетесь, как только понимаете: `ft_transfer` уже может идти или сначала нужен `storage_deposit`. ```bash export NETWORK_ID=testnet export RPC_URL=https://rpc.testnet.fastnear.com export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet -export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' -export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' -export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' ``` 1. Проверьте, зарегистрирован ли получатель на FT-контракте. @@ -787,7 +340,7 @@ jq '{ }' /tmp/ft-storage-balance.json ``` -2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. +2. Получите минимальный storage deposit на этом же контракте. ```bash MIN_STORAGE_YOCTO="$( @@ -812,214 +365,39 @@ MIN_STORAGE_YOCTO="$( printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" ``` -3. Определите одну переиспользуемую функцию подписи для function-call к контракту. - -Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. - -```bash -sign_function_call() { - METHOD_NAME="$1" \ - ARGS_JSON="$2" \ - DEPOSIT_YOCTO="$3" \ - GAS_TGAS="$4" \ - node --input-type=module <<'EOF' - -const { - NETWORK_ID = 'testnet', - RPC_URL = 'https://rpc.testnet.fastnear.com', - TOKEN_CONTRACT_ID, - SENDER_ACCOUNT_ID, - SENDER_PUBLIC_KEY, - SENDER_PRIVATE_KEY, - METHOD_NAME, - ARGS_JSON, - DEPOSIT_YOCTO = '0', - GAS_TGAS = '100', -} = process.env; - -for (const name of [ - 'TOKEN_CONTRACT_ID', - 'SENDER_ACCOUNT_ID', - 'SENDER_PUBLIC_KEY', - 'SENDER_PRIVATE_KEY', - 'METHOD_NAME', - 'ARGS_JSON', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); -const signer = await InMemorySigner.fromKeyPair( - NETWORK_ID, - SENDER_ACCOUNT_ID, - keyPair -); - -const derivedPublicKey = keyPair.getPublicKey().toString(); -if (derivedPublicKey !== SENDER_PUBLIC_KEY) { - throw new Error( - `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: SENDER_ACCOUNT_ID, - public_key: SENDER_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const action = transactions.functionCall( - METHOD_NAME, - Buffer.from(ARGS_JSON), - BigInt(GAS_TGAS) * 10n ** 12n, - BigInt(DEPOSIT_YOCTO) -); - -const transaction = transactions.createTransaction( - SENDER_ACCOUNT_ID, - utils.PublicKey.fromString(SENDER_PUBLIC_KEY), - TOKEN_CONTRACT_ID, - BigInt(accessKey.nonce) + 1n, - [action], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - SENDER_ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -} -``` - -4. При необходимости сначала зарегистрируйте storage для получателя. - -```bash -if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then - SIGNED_TX_BASE64="$( - sign_function_call \ - storage_deposit \ - "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id, - registration_only: true - }')" \ - "$MIN_STORAGE_YOCTO" \ - 100 - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash - }' -fi -``` - -5. После готовности storage переведите FT. +3. Превратите эти два чтения в один ответ о готовности перевода. ```bash -SIGNED_TX_BASE64="$( - sign_function_call \ - ft_transfer \ - "$(jq -nc \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ - receiver_id: $receiver_id, - amount: $amount, - memo: "FastNear RPC example" - }')" \ - 1 \ - 100 -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Подтвердите FT-баланс получателя тем же view-методом контракта. - -```bash -RECEIVER_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - receiver_balance: (.result.result | implode | fromjson) +jq -n \ + --slurpfile balance /tmp/ft-storage-balance.json \ + --slurpfile bounds /tmp/ft-storage-bounds.json \ + --arg receiver_account_id "$RECEIVER_ACCOUNT_ID" ' + ( + $balance[0].result.result + | if length == 0 then null else (implode | fromjson) end + ) as $storage + | ( + $bounds[0].result.result + | implode + | fromjson + ) as $bounds + | { + receiver_account_id: $receiver_account_id, + receiver_registered: ($storage != null), + current_storage: $storage, + minimum_storage_deposit_yocto: $bounds.min, + next_step: ( + if $storage != null + then "получатель уже зарегистрирован; ft_transfer может продолжаться" + else "сначала отправьте storage_deposit, потом делайте ft_transfer" + end + ) }' ``` **Зачем нужен следующий шаг?** -Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. +Это чистый RPC-вопрос в этом workflow: «зарегистрирован ли уже получатель и какой минимальный депозит потребует контракт, если нет?» Подписанный write-path зависит уже от вашего wallet, CLI или backend-интеграции, поэтому в самый маленький core RPC-пример он не входит. ## Чтения контракта и сырое состояние @@ -1027,42 +405,12 @@ curl -s "$RPC_URL" \ ### Прочитать счётчик прямо из состояния контракта, а потом подтвердить его через view-метод -Используйте этот сценарий, когда история простая: «я знаю, что этот контракт держит счётчик, но можно ли прочитать это число напрямую из storage, не вызывая код контракта?» - -В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: оба чтения должны совпасть в тот момент, когда вы их запускаете: - -- `view_state` читает сырой ключ `STATE` прямо из storage контракта -- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API - - Стратегия - Сначала прочитайте raw storage, затем декодируйте байты, а потом дайте контракту подтвердить тот же ответ через view-метод. - - 01RPC view_state читает сырой ключ STATE, не запуская код контракта. - 02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта. - 03RPC call_function get_num — это удобная перепроверка того, что raw-state-чтение и view-метод по-прежнему дают один и тот же ответ. - -Здесь важнее ментальная модель, чем сам счётчик: - -- `view_state` — это прямое чтение storage из trie -- `call_function` исполняет read-only-метод контракта -- оба способа могут ответить на один и тот же вопрос, но делают разную работу - -```mermaid -flowchart LR - S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] - R --> D["Декодировать base64 + Borsh"] - D --> N["Знаковое значение счётчика"] - C["RPC call_function get_num"] --> J["JSON-результат метода"] - N --> X["Сравнить"] - J --> X - X --> A["Одно и то же текущее значение"] -``` +Используйте этот сценарий, когда вы уже знаете точное семейство storage-ключей и хотите увидеть самый короткий контраст между raw state и публичным методом чтения контракта. -**Что вы делаете** +Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`: -- Читаете сырой ключ `STATE` из storage контракта. -- Декодируете возвращённые байты в текущее знаковое значение счётчика. -- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. +- `view_state` читает сырой ключ `STATE` напрямую +- `call_function get_num` спрашивает у контракта то же текущее число ```bash export NETWORK_ID=testnet @@ -1071,7 +419,7 @@ export CONTRACT_ID=counter.near-examples.testnet export STATE_PREFIX_BASE64=U1RBVEU= ``` -1. Сначала прочитайте сырое состояние контракта. +1. Сначала прочитайте сырой ключ `STATE`. ```bash curl -s "$RPC_URL" \ @@ -1092,46 +440,28 @@ curl -s "$RPC_URL" \ | tee /tmp/counter-view-state.json >/dev/null jq '{ - block_height: .result.block_height, - key_base64: .result.values[0].key, + key: (.result.values[0].key | @base64d), value_base64: .result.values[0].value }' /tmp/counter-view-state.json - -jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json ``` -Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. +Здесь должен появиться `key: "STATE"`. Это и есть тот случай, когда `view_state` уместен: семейство ключей вам уже известно заранее. -2. Декодируйте байты значения в знаковое число счётчика. +2. Декодируйте сырые байты. ```bash RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" -python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . +python3 - "$RAW_VALUE_BASE64" <<'PY' raw = base64.b64decode(sys.argv[1]) - -print(json.dumps({ - "value_base64": sys.argv[1], - "bytes": list(raw), - "hex": raw.hex(), - "signed_i8": int.from_bytes(raw, "little", signed=True), - "unsigned_u8": int.from_bytes(raw, "little", signed=False), -})) +print(int.from_bytes(raw, "little", signed=True)) PY ``` -Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. +Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть сложнее, но правило то же: сначала байты, потом схема. -Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. - -Переиспользуемый рецепт здесь короткий: - -- `view_state` возвращает сырые байты в base64 -- вы декодируете эти байты по известной схеме хранения контракта -- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема - -3. Теперь спросите контракт более привычным способом и сравните. +3. Теперь спросите контракт привычным способом и сравните. ```bash curl -s "$RPC_URL" \ @@ -1156,7 +486,7 @@ jq '{ }' /tmp/counter-call-function.json ``` -4. Сравните оба ответа напрямую. +4. Сравните оба ответа. ```bash RAW_STATE_NUMBER="$( @@ -1180,35 +510,24 @@ jq -n \ }' ``` -Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: - -- `view_state` ответил на вопрос, прочитав storage напрямую -- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта - **Зачем нужен следующий шаг?** -Используйте `view_state`, когда настоящий вопрос относится к точному storage и вы уже знаете семейство ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). +Используйте `view_state`, когда вы уже знаете точное семейство storage-ключей и хотите raw bytes. Используйте `call_function`, когда вам нужен публичный метод чтения самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда уже стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). -## Точные чтения NEAR Social и BOS +## Точные чтения SocialDB -Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. +Оставайтесь на точных чтениях через `call_function get`, когда вы уже знаете нужный ключ SocialDB. На обычном RPC raw `view_state` для `social.near` не подходит как обучающий путь, потому что состояние контракта слишком велико для прямого чтения. -### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? +### Прочитать один пост SocialDB ровно в том виде, как он хранится сейчас -Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». +Используйте этот сценарий, когда продукту, support-инструменту или агенту уже известен аккаунт и нужен живой payload поста из SocialDB без перехода в историю транзакций. Стратегия - Спросите у social.near ровно о двух вещах, которые важны до подписи. - - 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. - 02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near. - 03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer. - -Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: + Сначала прочитайте текущий ключ поста, затем получите точный payload этого поста из social.near. -- есть ли у целевого аккаунта storage на `social.near`? -- если есть, осталось ли там ещё место? -- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? + 01RPC call_function get по mike.near/index/post показывает, какой ключ поста сейчас активен. + 02RPC call_function get по mike.near/post/main возвращает точный сохранённый payload поста. + 03Если следующий вопрос становится «какая транзакция это записала?», переключайтесь на [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). **Официальные ссылки** @@ -1216,52 +535,23 @@ jq -n \ **Что вы делаете** -- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. -- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. -- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. -- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». +- Читаете текущий указатель поста под `mike.near/index/post`. +- Используете этот ключ, чтобы получить точный payload по `mike.near/post/`. +- Останавливаетесь на точном JSON и расширяетесь в историю только тогда, когда действительно нужна provenance. ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com export SOCIAL_CONTRACT_ID=social.near export ACCOUNT_ID=mike.near -export SIGNER_ACCOUNT_ID=mike.near -``` - -1. Сначала проверьте сам аккаунт signer. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } - }')" \ - | tee /tmp/social-publish-signer.json >/dev/null - -jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - signer_account_id: $signer_account_id, - amount: .result.amount, - locked: .result.locked, - storage_usage: .result.storage_usage -}' /tmp/social-publish-signer.json ``` -Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. - -2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. +1. Сначала прочитайте текущий указатель поста. ```bash -SOCIAL_STORAGE_ARGS_BASE64="$( +INDEX_POST_ARGS_BASE64="$( jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id + keys: [($account_id + "/index/post")] }' | base64 | tr -d '\n' )" @@ -1269,208 +559,53 @@ curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + --arg args_base64 "$INDEX_POST_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { request_type: "call_function", account_id: $account_id, - method_name: "get_account_storage", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-account-storage.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - storage: (.result.result | implode | fromjson), - storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) -}' /tmp/social-account-storage.json -``` - -Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. - -3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. - -```bash -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - jq -n --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - signer_matches_target: true, - permission_granted: true, - reason: "owner write" - }' -else - WRITE_PERMISSION_ARGS_BASE64="$( - jq -nc \ - --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ - --arg key "$ACCOUNT_ID" '{ - predecessor_id: $predecessor_id, - key: $key - }' | base64 | tr -d '\n' - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - signer_matches_target: false, - permission_granted: (.result.result | implode | fromjson) - }' -fi -``` - -4. Сведите проверку storage и разрешения в один читаемый итог. - -```bash -AVAILABLE_BYTES="$( - jq -r ' - .result.result - | if length == 0 then "0" - else (implode | fromjson | .available_bytes // 0 | tostring) - end - ' /tmp/social-account-storage.json -)" - -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - PERMISSION_GRANTED=true -else - PERMISSION_GRANTED="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq -r '.result.result | implode | fromjson' - )" -fi - -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ - --argjson available_bytes "$AVAILABLE_BYTES" \ - --argjson permission_granted "$PERMISSION_GRANTED" '{ - account_id: $account_id, - signer_account_id: $signer_account_id, - storage_ready: ($available_bytes > 0), - permission_ready: $permission_granted, - ready_to_publish_now: (($available_bytes > 0) and $permission_granted) - }' -``` - -Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. - -**Зачем нужен следующий шаг?** - -Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. - -### Что прямо сейчас содержит `mob.near/widget/Profile`? - -Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». - - Стратегия - Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. - - 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. - 02RPC call_function get читает точный исходник widget/Profile. - 03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples. - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Что вы делаете** - -- Спрашиваете у `social.near` каталог виджетов под `mob.near`. -- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. -- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. -- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mob.near -export WIDGET_NAME=Profile -``` - -1. Получите каталог виджетов и сохраните высоты блоков последней записи. - -```bash -WIDGET_KEYS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/widget/*")], - options: {return_type: "BlockHeight"} - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "keys", + method_name: "get", args_base64: $args_base64, finality: "final" } }')" \ - | tee /tmp/social-widget-keys.json >/dev/null + | tee /tmp/social-index-post.json >/dev/null jq --arg account_id "$ACCOUNT_ID" ' .result.result | implode | fromjson - | .[$account_id].widget - | to_entries - | sort_by(.value * -1) - | map({ - widget_name: .key, - last_write_block: .value - }) - | .[0:20] -' /tmp/social-widget-keys.json + | { + account_id: $account_id, + index_entry: (.[$account_id].index.post | fromjson), + current_post_key: (.[$account_id].index.post | fromjson | .key) + } +' /tmp/social-index-post.json ``` -2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. +На момент написания текущим ключом поста для `mike.near` был `main`. + +2. Прочитайте точный payload этого поста. ```bash -WIDGET_GET_ARGS_BASE64="$( +POST_KEY="$( + jq -r --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].index.post + | fromjson + | .key + ' /tmp/social-index-post.json +)" + +POST_ARGS_BASE64="$( jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] + --arg post_key "$POST_KEY" '{ + keys: [($account_id + "/post/" + $post_key)] }' | base64 | tr -d '\n' )" @@ -1478,7 +613,7 @@ curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + --arg args_base64 "$POST_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", @@ -1490,131 +625,25 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/social-widget-source.json >/dev/null + | tee /tmp/social-post-main.json >/dev/null -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:25] - | join("\n") - ' /tmp/social-widget-source.json -``` - -3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. - -```bash -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - ' /tmp/social-widget-keys.json \ - | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +jq --arg account_id "$ACCOUNT_ID" --arg post_key "$POST_KEY" ' + .result.result + | implode + | fromjson + | { + account_id: $account_id, + post_key: $post_key, + post: (.[$account_id].post[$post_key] | fromjson) + } +' /tmp/social-post-main.json ``` -На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. +Так вы получаете точный JSON, который хранится для текущего поста, включая поля вроде `type`, `text` и `image`. **Зачем нужен следующий шаг?** -Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. - -## Частые задачи - -### Проверить точное состояние аккаунта или ключа доступа - -**Начните здесь** - -- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для точных полей аккаунта. -- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. - -**Следующая страница при необходимости** - -- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» - -**Остановитесь, когда** - -- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. - -**Переходите дальше, когда** - -- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. -- Пользователя интересует не текущее состояние, а недавняя история активности. - -### Проверить один точный блок или снимок состояния протокола - -**Начните здесь** - -- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. -- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» -- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. - -**Следующая страница при необходимости** - -- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. -- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» - -**Остановитесь, когда** - -- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. - -**Переходите дальше, когда** - -- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). -- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Что этот контракт возвращает прямо сейчас? - -**Начните здесь** - -- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» -- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. -- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. -- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» - -**Следующая страница при необходимости** - -- [FastNear API](https://docs.fastnear.com/ru/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. -- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» - -**Остановитесь, когда** - -- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. - -**Переходите дальше, когда** - -- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. -- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» - -### Отправить транзакцию и подтвердить результат - -**Начните здесь** - -- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. -- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. -- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. -- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» - -**Остановитесь, когда** - -- У вас уже есть результат отправки и нужный финальный статус. - -**Переходите дальше, когда** - -- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. -- Нужен уже не единичный статус, а более широкий сценарий расследования. +Это чистый RPC-паттерн для SocialDB: спросите у контракта один точный ключ, декодируйте возвращённый JSON и остановитесь. Если вопрос превращается в «кто и когда это записал?», переходите к примерам по транзакциям, а не пытайтесь brute-force читать raw state `social.near`. ## Частые ошибки diff --git a/static/ru/snapshots/examples.md b/static/ru/snapshots/examples.md index 1e39417..309f26d 100644 --- a/static/ru/snapshots/examples.md +++ b/static/ru/snapshots/examples.md @@ -4,6 +4,8 @@ Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. +Эти helper-скрипты поддерживаются FastNear и оптимизированы под скорость восстановления. Если в вашей среде нужен review изменений, сначала скачайте скрипт, проверьте его и только потом запускайте, вместо прямого piping в `bash`. + ```bash DATA_PATH=~/.near/data @@ -95,80 +97,6 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. -## Частые задачи - -### Поднять optimized `fast-rpc`-узел в mainnet - -**Начните здесь** - -- Используйте якорь с optimized mainnet `fast-rpc` выше. - -**Следующая страница при необходимости** - -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), если нужно дополнительно настроить `THREADS`, `BWLIMIT` или кастомный `DATA_PATH`. - -**Остановитесь, когда** - -- Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. - -**Переходите дальше, когда** - -- На самом деле требуется архивное хранение, а не просто быстрый запуск. - -### Восстановить обычный RPC-узел в стандартный каталог nearcore - -**Начните здесь** - -- Используйте якорь со стандартным mainnet RPC выше. - -**Следующая страница при необходимости** - -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), если нужно дополнительно настроить `DATA_PATH`, `THREADS` или ограничения по пропускной способности. - -**Остановитесь, когда** - -- Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. - -**Переходите дальше, когда** - -- Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. - -### Правильно поднять архивные hot- и cold-данные mainnet - -**Начните здесь** - -- Используйте архивный walkthrough выше. - -**Следующая страница при необходимости** - -- Сначала получите последнюю высоту архивного снапшота, затем запускайте отдельные загрузки hot- и cold-данных с правильными путями. - -**Остановитесь, когда** - -- План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. - -**Переходите дальше, когда** - -- Оператору нужны уже общие гайды nearcore по bootstrap, а не только FastNear snapshots. - -### Поднять архивные hot-данные в testnet - -**Начните здесь** - -- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet), раздел архивного режима. - -**Следующая страница при необходимости** - -- Получите последнюю высоту архивного снапшота testnet перед шагом загрузки. - -**Остановитесь, когда** - -- Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. - -**Переходите дальше, когда** - -- Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. - ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. diff --git a/static/ru/snapshots/examples/index.md b/static/ru/snapshots/examples/index.md index 1e39417..309f26d 100644 --- a/static/ru/snapshots/examples/index.md +++ b/static/ru/snapshots/examples/index.md @@ -4,6 +4,8 @@ Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. +Эти helper-скрипты поддерживаются FastNear и оптимизированы под скорость восстановления. Если в вашей среде нужен review изменений, сначала скачайте скрипт, проверьте его и только потом запускайте, вместо прямого piping в `bash`. + ```bash DATA_PATH=~/.near/data @@ -95,80 +97,6 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. -## Частые задачи - -### Поднять optimized `fast-rpc`-узел в mainnet - -**Начните здесь** - -- Используйте якорь с optimized mainnet `fast-rpc` выше. - -**Следующая страница при необходимости** - -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), если нужно дополнительно настроить `THREADS`, `BWLIMIT` или кастомный `DATA_PATH`. - -**Остановитесь, когда** - -- Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. - -**Переходите дальше, когда** - -- На самом деле требуется архивное хранение, а не просто быстрый запуск. - -### Восстановить обычный RPC-узел в стандартный каталог nearcore - -**Начните здесь** - -- Используйте якорь со стандартным mainnet RPC выше. - -**Следующая страница при необходимости** - -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), если нужно дополнительно настроить `DATA_PATH`, `THREADS` или ограничения по пропускной способности. - -**Остановитесь, когда** - -- Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. - -**Переходите дальше, когда** - -- Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. - -### Правильно поднять архивные hot- и cold-данные mainnet - -**Начните здесь** - -- Используйте архивный walkthrough выше. - -**Следующая страница при необходимости** - -- Сначала получите последнюю высоту архивного снапшота, затем запускайте отдельные загрузки hot- и cold-данных с правильными путями. - -**Остановитесь, когда** - -- План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. - -**Переходите дальше, когда** - -- Оператору нужны уже общие гайды nearcore по bootstrap, а не только FastNear snapshots. - -### Поднять архивные hot-данные в testnet - -**Начните здесь** - -- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet), раздел архивного режима. - -**Следующая страница при необходимости** - -- Получите последнюю высоту архивного снапшота testnet перед шагом загрузки. - -**Остановитесь, когда** - -- Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. - -**Переходите дальше, когда** - -- Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. - ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. diff --git a/static/ru/structured-data/site-graph.json b/static/ru/structured-data/site-graph.json index 5694603..8218951 100644 --- a/static/ru/structured-data/site-graph.json +++ b/static/ru/structured-data/site-graph.json @@ -6495,6 +6495,19 @@ "routeType": "docs", "url": "https://docs.fastnear.com/ru/tx/receipt" }, + { + "entityIds": { + "familyIds": [], + "mainEntityId": null, + "pageId": "https://docs.fastnear.com/ru/tx/socialdb-proofs#page" + }, + "indexable": true, + "markdownMirrorUrl": "https://docs.fastnear.com/ru/tx/socialdb-proofs.md", + "pageSchemaType": "TechArticle", + "route": "/ru/tx/socialdb-proofs", + "routeType": "docs", + "url": "https://docs.fastnear.com/ru/tx/socialdb-proofs" + }, { "entityIds": { "familyIds": [ diff --git a/static/ru/transfers/examples.md b/static/ru/transfers/examples.md index ba49ebf..3c1096a 100644 --- a/static/ru/transfers/examples.md +++ b/static/ru/transfers/examples.md @@ -43,22 +43,22 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ ## Готовый сценарий -### Найти один подозрительный перевод, а затем пройти по его receipt +### Найти один исходящий перевод и при необходимости перейти к деталям исполнения -Используйте этот сценарий, когда история звучит так: «я вижу, что средства двигались, но хочу получить точную опорную точку исполнения для этого движения, не затягивая сразу всю историю аккаунта». +Используйте этот сценарий, когда история звучит так: «я знаю, что этот аккаунт отправлял средства в этом окне, и мне может понадобиться точная опорная точка исполнения для одной строки, но я не хочу сразу тянуть всю историю аккаунта». Стратегия - Сначала оставайтесь на узкой истории движения, а затем один раз переключайтесь в историю исполнения. + Сначала оставайтесь на узкой истории движения, а затем переходите в историю исполнения только если строки перевода уже недостаточно. 01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять. - 02jq поднимает один receipt_id, не затягивая остальную историю аккаунта. - 03POST /v0/receipt превращает это движение в опорную точку исполнения, которую уже можно продолжать в /tx. + 02Сначала выведите строки, а затем явно выберите один transfer_index перед тем, как поднимать его receipt_id. + 03POST /v0/receipt — это необязательное расширение, когда уже нужны детали исполнения именно за этим переводом. **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Выделяете один перевод, который действительно похож на нужное вам движение. -- Переиспользуете его `receipt_id` в Transactions API, чтобы перейти от движения актива к истории исполнения. +- Выделяете одну строку перевода, которая действительно похожа на нужное вам движение. +- Переиспользуете его `receipt_id` в Transactions API только если нужно перейти от движения актива к истории исполнения. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com @@ -66,113 +66,65 @@ TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=YOUR_ACCOUNT_ID FROM_TIMESTAMP_MS=1711929600000 TO_TIMESTAMP_MS=1712016000000 +TRANSFER_INDEX=0 -RECEIPT_ID="$( - curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ - account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, - desc: true, - limit: 10 - }')" \ - | tee /tmp/transfers-window.json \ - | jq -r '.transfers[0].receipt_id' -)" +curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | tee /tmp/transfers-window.json >/dev/null jq '{ resume_token, transfers: [ - .transfers[] + .transfers + | to_entries[] | { - transaction_id, - receipt_id, - asset_id, - amount, - other_account_id, - block_height + transfer_index: .key, + transaction_id: .value.transaction_id, + receipt_id: .value.receipt_id, + asset_id: .value.asset_id, + amount: .value.amount, + other_account_id: .value.other_account_id, + block_height: .value.block_height } ] }' /tmp/transfers-window.json -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' -``` - -**Зачем нужен следующий шаг?** - -Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` даёт точную опорную точку в исполнении, не затягивая вас сразу в полную историю аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. - -## Частые задачи - -### Найти исходящие переводы одного аккаунта в узком окне времени - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. - -**Следующая страница при необходимости** - -- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. - -**Остановитесь, когда** - -- Уже можно ответить, кто что отправил, когда и в каком активе. - -**Переходите дальше, когда** - -- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Листать ленту переводов дальше и не потерять своё место - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. - -**Следующая страница при необходимости** - -- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. -- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. - -**Остановитесь, когда** - -- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. - -**Переходите дальше, когда** - -- Пользователь просит метаданные транзакции сверх самих переводов. -- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). - -### Перейти от истории переводов к полному расследованию транзакции - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. - -**Следующая страница при необходимости** - -- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. +RECEIPT_ID="$( + jq -r --argjson transfer_index "$TRANSFER_INDEX" \ + '.transfers[$transfer_index].receipt_id // empty' \ + /tmp/transfers-window.json +)" -**Остановитесь, когда** +printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" +printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" -- Уже определено правильное событие перевода и понятно, какой API открывать следующим. +if [ -n "$RECEIPT_ID" ]; then + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' +fi +``` -**Переходите дальше, когда** +**Зачем нужен следующий шаг?** -- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. +Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный следующий шаг, когда самой строки перевода уже недостаточно и нужна опорная точка в истории исполнения. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. ## Частые ошибки diff --git a/static/ru/transfers/examples/index.md b/static/ru/transfers/examples/index.md index ba49ebf..3c1096a 100644 --- a/static/ru/transfers/examples/index.md +++ b/static/ru/transfers/examples/index.md @@ -43,22 +43,22 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ ## Готовый сценарий -### Найти один подозрительный перевод, а затем пройти по его receipt +### Найти один исходящий перевод и при необходимости перейти к деталям исполнения -Используйте этот сценарий, когда история звучит так: «я вижу, что средства двигались, но хочу получить точную опорную точку исполнения для этого движения, не затягивая сразу всю историю аккаунта». +Используйте этот сценарий, когда история звучит так: «я знаю, что этот аккаунт отправлял средства в этом окне, и мне может понадобиться точная опорная точка исполнения для одной строки, но я не хочу сразу тянуть всю историю аккаунта». Стратегия - Сначала оставайтесь на узкой истории движения, а затем один раз переключайтесь в историю исполнения. + Сначала оставайтесь на узкой истории движения, а затем переходите в историю исполнения только если строки перевода уже недостаточно. 01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять. - 02jq поднимает один receipt_id, не затягивая остальную историю аккаунта. - 03POST /v0/receipt превращает это движение в опорную точку исполнения, которую уже можно продолжать в /tx. + 02Сначала выведите строки, а затем явно выберите один transfer_index перед тем, как поднимать его receipt_id. + 03POST /v0/receipt — это необязательное расширение, когда уже нужны детали исполнения именно за этим переводом. **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Выделяете один перевод, который действительно похож на нужное вам движение. -- Переиспользуете его `receipt_id` в Transactions API, чтобы перейти от движения актива к истории исполнения. +- Выделяете одну строку перевода, которая действительно похожа на нужное вам движение. +- Переиспользуете его `receipt_id` в Transactions API только если нужно перейти от движения актива к истории исполнения. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com @@ -66,113 +66,65 @@ TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=YOUR_ACCOUNT_ID FROM_TIMESTAMP_MS=1711929600000 TO_TIMESTAMP_MS=1712016000000 +TRANSFER_INDEX=0 -RECEIPT_ID="$( - curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ - account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, - desc: true, - limit: 10 - }')" \ - | tee /tmp/transfers-window.json \ - | jq -r '.transfers[0].receipt_id' -)" +curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ + --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + account_id: $account_id, + direction: "sender", + from_timestamp_ms: $from_timestamp_ms, + to_timestamp_ms: $to_timestamp_ms, + desc: true, + limit: 10 + }')" \ + | tee /tmp/transfers-window.json >/dev/null jq '{ resume_token, transfers: [ - .transfers[] + .transfers + | to_entries[] | { - transaction_id, - receipt_id, - asset_id, - amount, - other_account_id, - block_height + transfer_index: .key, + transaction_id: .value.transaction_id, + receipt_id: .value.receipt_id, + asset_id: .value.asset_id, + amount: .value.amount, + other_account_id: .value.other_account_id, + block_height: .value.block_height } ] }' /tmp/transfers-window.json -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' -``` - -**Зачем нужен следующий шаг?** - -Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` даёт точную опорную точку в исполнении, не затягивая вас сразу в полную историю аккаунта. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. - -## Частые задачи - -### Найти исходящие переводы одного аккаунта в узком окне времени - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом, исходящим направлением и самым узким полезным фильтром по времени. - -**Следующая страница при необходимости** - -- Сузьте запрос ещё сильнее по активу или сумме, если ответ всё ещё содержит лишние переводы. - -**Остановитесь, когда** - -- Уже можно ответить, кто что отправил, когда и в каком активе. - -**Переходите дальше, когда** - -- Пользователь спрашивает, почему перевод произошёл или какие ещё действия были вокруг него. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Листать ленту переводов дальше и не потерять своё место - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. - -**Следующая страница при необходимости** - -- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. -- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. - -**Остановитесь, когда** - -- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. - -**Переходите дальше, когда** - -- Пользователь просит метаданные транзакции сверх самих переводов. -- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). - -### Перейти от истории переводов к полному расследованию транзакции - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. - -**Следующая страница при необходимости** - -- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. +RECEIPT_ID="$( + jq -r --argjson transfer_index "$TRANSFER_INDEX" \ + '.transfers[$transfer_index].receipt_id // empty' \ + /tmp/transfers-window.json +)" -**Остановитесь, когда** +printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" +printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" -- Уже определено правильное событие перевода и понятно, какой API открывать следующим. +if [ -n "$RECEIPT_ID" ]; then + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' +fi +``` -**Переходите дальше, когда** +**Зачем нужен следующий шаг?** -- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. +Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный следующий шаг, когда самой строки перевода уже недостаточно и нужна опорная точка в истории исполнения. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. ## Частые ошибки diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index 412cfab..a99c80c 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -29,7 +29,7 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. -Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. +Если нужен более развёрнутый case study на той же поверхности, переходите к [Berry Club case study](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer case study](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. ## С чего начать @@ -176,6 +176,135 @@ curl -s "$TX_BASE_URL/v0/receipt" \ `POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. +### Какая receipt выдала этот лог или event? + +Используйте это расследование, когда история звучит так: «у меня есть один tx hash и один фрагмент лога, и мне нужно точно понять, какая именно receipt его выдала». + +Это другой вопрос, чем более поздний сценарий «дошёл ли callback?». Здесь цель проще: привязать одну наблюдаемую строку лога к одному точному `receipt_id`, одному методу и одному исполнителю. + + Стратегия + Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога. + + 01POST /v0/transactions даёт полный индексированный список receipt для одного tx hash, включая receipt-логи. + 02jq сужает этот список до receipt, в логах которых встречается нужный вам фрагмент. + 03Как только совпадение осталось одно, сохраняйте его receipt_id, executor и имя метода как точный ответ. + +**Цель** + +- Начать с одного mainnet tx hash и одного фрагмента лога и определить точную receipt, которая выдала этот лог. + +Для этого зафиксированного mainnet-примера используйте: + +- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` +- фрагмент лога: `Refund` +- ожидаемый matching `receipt_id`: `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` +- ожидаемый executor: `wrap.near` +- ожидаемый метод: `ft_resolve_transfer` + +Эта транзакция полезна тем, что в ней есть две разные logged receipt внутри одной истории: + +- ранний лог `Transfer ...` на receipt с `ft_transfer_call` +- более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` + +```mermaid +flowchart LR + T["Один tx hash
2KhhB1uD..."] --> L["Читаем все receipt-логи"] + L --> X["Ищем фрагмент:
Refund"] + X --> R["Точная receipt
9sLHQpaG..."] + R --> A["Ответ:
wrap.near / ft_resolve_transfer"] +``` + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Атрибуция лога | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | +| Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | + +**Что должен включать полезный ответ** + +- какой `receipt_id` выдал лог +- какой контракт исполнил эту receipt +- какой метод там выполнился +- точную строку лога, которая совпала +- одно простое предложение вроде «лог `Refund` пришёл из `wrap.near` в receipt с методом `ft_resolve_transfer`» + +#### Shell-сценарий атрибуции лога + +Используйте этот сценарий, когда у вас уже есть tx hash и следующий вопрос звучит как «какая receipt это сказала?» + +**Что вы делаете** + +- Один раз получаете транзакцию и сохраняете список её receipt. +- Фильтруете receipt по одному фрагменту лога. +- Останавливаетесь, как только у вас есть один точный `receipt_id`, один executor и одно имя метода. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +LOG_FRAGMENT=Refund +``` + +1. Получите транзакцию и сохраните список receipt. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/log-attribution-transaction.json >/dev/null +``` + +2. Отфильтруйте список receipt до логов, которые содержат нужный вам фрагмент. + +```bash +jq --arg fragment "$LOG_FRAGMENT" '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id + }, + matching_receipts: [ + .transactions[0].receipts[] + | select(any(.execution_outcome.outcome.logs[]?; contains($fragment))) + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + block_height: .execution_outcome.block_height, + logs: .execution_outcome.outcome.logs + } + ] +}' /tmp/log-attribution-transaction.json + +# На что смотреть: +# - фрагмент `Refund` совпадает ровно с одной receipt +# - это receipt 9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w +# - receipt исполнилась на wrap.near +# - имя метода — ft_resolve_transfer +``` + +3. Если хотите увидеть все logged receipt рядом, распечатайте только те receipt, где вообще были логи. + +```bash +jq '{ + logged_receipts: [ + .transactions[0].receipts[] + | select((.execution_outcome.outcome.logs | length) > 0) + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + logs: .execution_outcome.outcome.logs + } + ] +}' /tmp/log-attribution-transaction.json +``` + +Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. + +**Зачем нужен следующий шаг?** + +Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. + ### Превратить один страшный receipt ID из логов в понятную человеческую историю Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. @@ -228,19 +357,6 @@ flowchart LR #### Shell-сценарий: от страшного receipt ID к человеческой истории -## Ошибки и async - -Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и порядок callback-ов. - -Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. - -**Что вы делаете** - -- Сначала разрешаете receipt. -- Извлекаете `receipt.transaction_hash` через `jq`. -- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. -- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. - ```bash TX_BASE_URL=https://tx.main.fastnear.com RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' @@ -314,6 +430,12 @@ jq -r ' `POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. +## Ошибки и async + +Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. + +Используйте этот раздел, когда уже понятно, что транзакция жила дольше одной receipt, и следующий вопрос относится уже к форме исполнения, а не к простому поиску объекта. + ### Доказать, что одно неудачное действие сорвало весь пакет Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? @@ -659,804 +781,170 @@ jq \ Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. -### Проследить асинхронную promise-цепочку и доказать порядок callback-ов +### Дошёл ли callback вообще? + +Используйте это расследование, когда одна транзакция запустила downstream-работу на другом контракте, а настоящий вопрос звучит не как «успешно ли отработал receiver?», а как «вернулся ли callback обратно в исходный контракт?» + +Это самый короткий полезный сценарий про callback на странице: -Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» +- стартуйте с одного tx hash +- найдите downstream-receipt на другом контракте +- найдите более поздний callback-receipt, который вернулся в исходный контракт +- остановитесь, как только доказаны сам факт callback и его результат Стратегия - Смотрите на два хеша как на одну async-историю: докажите, что работа была жива, восстановите запрошенный порядок и сравните его с видимым downstream-state. + Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а. - 01RPC call_function на view отложенной работы доказывает, что promise-работа действительно уже была жива до resume-шага. - 02POST /v0/transactions даёт оба block-anchor и точный порядок, который запросила resume-транзакция. - 03RPC EXPERIMENTAL_tx_status вместе с downstream-view доказывают, где callback-и реально выполнились и в каком видимом порядке. + 01POST /v0/transactions показывает downstream-вызов и более поздний receipt, который возвращается в исходный контракт. + 02jq сужает этот список receipt до одного downstream-вызова и одного callback-receipt. + 03RPC EXPERIMENTAL_tx_status нужен только как дополнительное подтверждение, если вам важны канонический результат callback-а и его логи. **Цель** -- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. +- Доказать на одном фиксированном mainnet-примере, что `wrap.near` отправил `ft_transfer_call` в `v2.ref-finance.near`, receiver выполнил `ft_on_transfer`, а затем `wrap.near` получил callback `ft_resolve_transfer` обратно. -Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: +Этот фиксированный mainnet-пример с callback был замечен **19 апреля 2026 года**: -- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом -- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке -- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и -- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору +- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` +- аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` +- исходный контракт: `wrap.near` +- downstream-receiver: `v2.ref-finance.near` +- верхнеуровневый метод: `ft_transfer_call` +- downstream-метод: `ft_on_transfer` +- callback-метод: `ft_resolve_transfer` +- блок транзакции: `194692298` +- блок downstream-receipt: `194692300` +- блок callback-receipt: `194692301` ```mermaid flowchart LR - Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] - H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] - R --> C["Async cross-contract callback-и"] - C --> B["Recorder state
beta"] - B --> A["Recorder state
alpha"] - A --> G["Recorder state
gamma"] - Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] - R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] - G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] + T["Одна mainnet-транзакция
ft_transfer_call на wrap.near"] --> D["Downstream-receipt
v2.ref-finance.near.ft_on_transfer"] + D --> F["Receiver упал
E51: contract paused"] + F --> C["Callback-receipt обратно в wrap.near
ft_resolve_transfer"] + C --> R["Лог refund на wrap.near"] ``` -Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. - -Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. +Здесь хорошо видна одна полезная деталь NEAR: downstream-сбой не означает, что callback исчез. В этом случае `v2.ref-finance.near` уронил свой `ft_on_transfer`, но `wrap.near` всё равно позже получил `ft_resolve_transfer` и залогировал refund. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | -| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | -| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | -| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | -| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | -| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | -| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | -| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | +| Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | +| Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | **Что должен включать полезный ответ** -- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» -- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования -- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался -- в каких блоках стали видны изменения состояния -- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования - -## Доказательства по SocialDB - -Эти примеры стартуют с читаемого состояния в NEAR Social и откатываются назад к точной записи, которая это состояние создала. +- какой контракт получил downstream-вызов +- какой метод выполнился на downstream-контракте +- вернулся ли более поздний receipt в исходный контракт +- какой callback-метод там выполнился и в каком блоке +- одно простое предложение вроде «receiver упал, но исходный контракт всё равно получил callback и завершил перевод» -### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB +#### Shell-сценарий проверки callback-а -Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». - - Стратегия - Начните с читаемого значения поля, а затем превратите его field-level block в один receipt и одну транзакцию записи. - - 01NEAR Social POST /get даёт текущее значение profile.name и field-level :block. - 02POST /v0/block превращает этот блок в конкретный receipt и хеш транзакции mike.near -> social.near. - 03POST /v0/transactions доказывает payload записи, а RPC call_function get подтверждает, что поле и сейчас разрешается так же. - -**Цель** - -- Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. - -**Официальные ссылки** - -- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) -- [Живая поверхность чтения NEAR Social](https://api.near.social) - -Этот сценарий следует тому же рецепту доказательства, что и расследование по подписке, но добавляет ещё один важный нюанс SocialDB: для исторического доказательства `:block` на уровне конкретного поля обычно точнее, чем `:block` у родительского объекта. В этом живом примере `mike.near/profile/name` был записан на блоке `78675795`, тогда как более широкий объект `mike.near/profile` позже сдвинулся на другой блок из-за изменений в соседних полях. Роль FastNear в этом сценарии — превратить этот блок уровня поля в receipt, затем в транзакцию и потом в читаемый payload записи. - -Для этого живого примера текущее значение `profile.name` равно `Mike Purvis`, блок записи SocialDB на уровне поля равен `78675795`, ID receipt — `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN`, хеш исходной транзакции — `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ`, а внешний блок транзакции — `78675794`. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Семантическое чтение поля | NEAR Social `POST /get` | Читаем `mike.near/profile/name` с включёнными метаданными блока | Даёт читаемое значение поля и опорный `:block` SocialDB на уровне поля, где это значение было записано | -| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем блок уровня поля из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи уровня поля в конкретный receipt и хеш исходной транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который нёс `profile.name` и окружающие поля профиля в одном payload | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что поле и сейчас имеет это значение, хотя предыдущие шаги уже доказали конкретную историческую запись | - -**Что должен включать полезный ответ** - -- разрешается ли `mike.near/profile/name` сейчас в `Mike Purvis` -- высоту блока записи SocialDB на уровне поля (`78675795`) и объяснение, почему для этого вопроса этот якорь лучше, чем блок родительского профиля -- конкретный ID receipt и хеш исходной транзакции за этой записью -- доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload -- различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) - -#### Shell-сценарий доказательства поля профиля в NEAR Social - -Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. +Используйте этот сценарий, когда нужен один конкретный proof callback-а без превращения страницы в полный курс по теории promises. **Что вы делаете** -- Читаете текущее поле `profile.name` из NEAR Social и сохраняете блок записи SocialDB на уровне поля. -- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`, несущей `profile.name`. -- Завершаете каноническим RPC-подтверждением того, что поле всё ещё разрешается в то же значение на `final`. +- Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. +- Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. +- Останавливаетесь сразу, как только можете сказать, вернулся ли callback и что он сделал. ```bash -SOCIAL_API_BASE_URL=https://api.near.social TX_BASE_URL=https://tx.main.fastnear.com RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near -PROFILE_FIELD=profile/name -``` - -1. Прочитайте поле профиля из NEAR Social и сохраните блок записи SocialDB на уровне поля. - -```bash -PROFILE_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-profile-name.json \ - | jq -r --arg account_id "$ACCOUNT_ID" \ - '.[ $account_id ].profile.name[":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - current_name: .[$account_id].profile.name[""], - field_block_height: .[$account_id].profile.name[":block"], - parent_profile_block_height: .[$account_id].profile[":block"] -}' /tmp/mike-profile-name.json - -# Ожидаемое current_name: "Mike Purvis" -# Ожидаемая высота блока уровня поля: 78675795 +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +SENDER_ACCOUNT_ID=7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72 +ORIGIN_CONTRACT_ID=wrap.near +DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near ``` -2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. +1. Получите транзакцию и распечатайте downstream-receipt вместе с callback-receipt. ```bash -PROFILE_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-profile-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/callback-check-transaction.json >/dev/null -jq --arg account_id "$ACCOUNT_ID" '{ - profile_receipt: ( +CALLBACK_RECEIPT_ID="$( + jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | { - receipt_id, - transaction_hash, - block_height, - tx_block_height - } + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + | .receipt.receipt_id ) - ) -}' /tmp/mike-profile-block.json - -# Ожидаемый receipt ID: 2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN -# Ожидаемый хеш транзакции: 6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ -``` - -3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-profile-transaction.json >/dev/null + ' /tmp/callback-check-transaction.json +)" -jq '{ +jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" '{ transaction: { hash: .transactions[0].transaction.hash, signer_id: .transactions[0].transaction.signer_id, receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + tx_block_height: .transactions[0].execution_outcome.block_height }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), - description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), - tags: ( - .args - | @base64d - | fromjson - | .data["mike.near"].profile.tags - | keys - ) - } - ) -}' /tmp/mike-profile-transaction.json -``` - -4. Завершите каноническим подтверждением текущего состояния через raw RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-profile-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - finality: "final", - current_name: ( - .result.result - | implode - | fromjson - | .[$account_id].profile.name - ) -}' /tmp/mike-profile-rpc.json -``` - -Этот последний шаг подтверждает, что поле и сейчас разрешается в `Mike Purvis`. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись установила это поле и какая транзакция несла эту запись. - -**Зачем нужен следующий шаг?** - -NEAR Social даёт семантическое значение поля. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемый payload профиля. RPC даёт каноническое подтверждение текущего состояния. - -### Доказать, что `mike.near` подписался на `mob.near`, а затем восстановить транзакцию записи в SocialDB - -Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». - - Стратегия - Начните с семантической связи подписки, а затем используйте блок записи как мост назад к одному receipt и одной транзакции. - - 01NEAR Social POST /get даёт читаемую связь подписки и SocialDB :block, где она была записана. - 02POST /v0/block превращает этот блок записи в конкретный receipt и хеш транзакции за этой связью. - 03POST /v0/transactions доказывает payload с graph.follow и index.graph, а RPC call_function get подтверждает, что связь и сейчас существует. - -**Цель** - -- Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. - -**Официальные ссылки** - -- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) -- [Живая поверхность чтения NEAR Social](https://api.near.social) - -Читаемая связь подписки приходит из данных NEAR Social, а не из FastNear. Ключевой мост здесь — метаданные SocialDB `:block`: они указывают на блок, в котором исполнился receipt, записавший это значение. Этот блок не совпадает с блоком, в который была включена внешняя транзакция. Роль FastNear в этом сценарии — превратить эту высоту блока в receipt, затем в транзакцию и, наконец, в читаемую историю исполнения. - -Для этого живого примера текущая связь выглядит как `mike.near -> mob.near`, блок записи SocialDB равен `79574924`, ID receipt — `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th`, хеш исходной транзакции — `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb`, а внешний блок транзакции — `79574923`. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Семантическое чтение связи | NEAR Social `POST /get` | Читаем `mike.near/graph/follow/mob.near` с включёнными метаданными блока | Даёт читаемую связь подписки и опорный `:block` из SocialDB, где это значение было записано | -| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем высоту блока из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи SocialDB в конкретный receipt и хеш исходной транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который записал и `graph.follow`, и записи `index.graph` | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что связь подписки существует и сейчас, хотя предыдущие шаги уже доказали конкретную историческую запись | - -**Что должен включать полезный ответ** - -- существует ли сейчас связь подписки `mike.near -> mob.near` -- высоту блока записи SocialDB (`79574924`) и объяснение, почему это блок исполнения receipt -- конкретный ID receipt и хеш исходной транзакции за этой записью -- доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` -- различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) - -#### Shell-сценарий доказательства подписки в NEAR Social - -Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. - -**Что вы делаете** - -- Читаете текущую связь подписки из NEAR Social и сохраняете блок записи SocialDB. -- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`. -- Завершаете каноническим RPC-подтверждением того, что связь всё ещё существует на `final`. - -```bash -SOCIAL_API_BASE_URL=https://api.near.social -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near -TARGET_ACCOUNT_ID=mob.near -``` - -1. Прочитайте связь подписки из NEAR Social и сохраните блок записи SocialDB. - -```bash -FOLLOW_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - keys: [($account_id + "/graph/follow/" + $target_account_id)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-follow-edge.json \ - | jq -r --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" \ - '.[ $account_id ].graph.follow[ $target_account_id ][":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - follow_edge: .[$account_id].graph.follow[$target_account_id][""], - follow_block_height: .[$account_id].graph.follow[$target_account_id][":block"] -}' /tmp/mike-follow-edge.json - -# Ожидаемая высота блока записи: 79574924 -``` - -2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. - -```bash -FOLLOW_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$FOLLOW_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-follow-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - follow_receipt: ( + downstream_receipt: ( first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") + .transactions[0].receipts[] + | select(.receipt.receiver_id == $downstream) | { - receipt_id, - transaction_hash, - block_height, - tx_block_height + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + status: .execution_outcome.outcome.status, + block_height: .execution_outcome.block_height } ) - ) -}' /tmp/mike-follow-block.json - -# Ожидаемый receipt ID: UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th -# Ожидаемый хеш транзакции: FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -``` - -3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$FOLLOW_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-follow-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - follow_edge: (.args | @base64d | fromjson | .data["mike.near"].graph.follow["mob.near"]), - index_graph: ( - .args - | @base64d - | fromjson - | .data["mike.near"].index.graph - | fromjson - | map(select(.value.accountId == "mob.near")) - ) - } - ) -}' /tmp/mike-follow-transaction.json -``` - -4. Завершите каноническим подтверждением текущего состояния через raw RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - keys: [($account_id + "/graph/follow/" + $target_account_id)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-follow-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - finality: "final", - current_follow_edge: ( - .result.result - | implode - | fromjson - | .[$account_id].graph.follow[$target_account_id] - ) -}' /tmp/mike-follow-rpc.json -``` - -Этот последний шаг подтверждает, что связь подписки существует и сейчас. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись создала эту связь и какая транзакция несла эту запись. - -**Зачем нужен следующий шаг?** - -NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. - -### Какая транзакция записала `mob.near/widget/Profile`? - -Используйте это расследование, когда вопрос звучит так: «я уже знаю, что `mob.near/widget/Profile` существует прямо сейчас. Какая именно транзакция записала ту версию виджета, на которую я смотрю?» - -Это естественное tx-продолжение к более лёгкому RPC-сценарию про виджет и к provenance-NFT-сценарию. Задача здесь прямолинейная: - -- стартуем с собственного SocialDB-блока виджета -- превращаем этот блок в один `mob.near -> social.near` receipt -- восстанавливаем исходную транзакцию -- декодируем payload `set` и доказываем, что он действительно нёс исходник виджета - - Стратегия - Смотрите на write-block виджета как на весь мост сразу: блок в receipt, receipt в транзакцию, транзакцию в исходник. - - 01POST /v0/block начинает с блока виджета и сужает его до одного receipt mob.near -> social.near. - 02POST /v0/transactions превращает этот receipt в один читаемый payload set с исходником виджета. - 03RPC call_function get — это финальное подтверждение текущего состояния, что виджет и сейчас существует. - -**Цель** - -- Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -Для этого живого якоря: - -- аккаунт: `mob.near` -- виджет: `Profile` -- блок записи в SocialDB: `86494825` -- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` -- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` -- внешний блок транзакции: `86494824` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `86494825` с `with_receipts: true`, а затем фильтруем его обратно до `mob.near -> social.near` | Превращает блок записи виджета в один конкретный receipt и один конкретный хеш транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `mob.near/widget/Profile` | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | - -**Что должен включать полезный ответ** - -- высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции -- конкретный receipt ID и хеш исходной транзакции за этой записью виджета -- доказательство, что payload записи был `set` с `mob.near/widget/Profile` -- одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» - -#### Shell-сценарий доказательства записи виджета в NEAR Social - -Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. - -**Что вы делаете** - -- Стартуете с блока последней записи виджета. -- Переиспользуете эту высоту в FastNear block receipts, чтобы получить receipt и мост к транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы декодировать записанный исходник виджета. -- Завершаете сырым RPC-подтверждением, что виджет всё ещё существует сейчас. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -WIDGET_BLOCK_HEIGHT=86494825 -``` - -1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. - -```bash -WIDGET_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$WIDGET_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mob-widget-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - widget_write_receipt: ( + ), + callback_receipt: ( first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) | { - receipt_id, - transaction_hash, - block_height, - tx_block_height + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: .receipt.receipt.Action.actions[0].FunctionCall.method_name, + logs: .execution_outcome.outcome.logs, + status: .execution_outcome.outcome.status, + block_height: .execution_outcome.block_height } ) - ) -}' /tmp/mob-widget-block.json - -# Ожидаемый receipt ID: CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6 -# Ожидаемый хеш транзакции: 9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia -``` - -2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mob-widget-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].transaction.actions[0].FunctionCall - | { - method_name, - widget_source_head: ( - .args - | @base64d - | fromjson - | .data["mob.near"].widget.Profile[""] - | split("\n")[0:12] + ), + callback_ran: ( + first( + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" ) - } + | true + ) // false ) -}' /tmp/mob-widget-transaction.json -``` - -Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `9QDup...` вызвала `social.near set` и пронесла в `args` настоящий исходник `mob.near/widget/Profile`. - -3. Завершите каноническим подтверждением текущего состояния через сырой RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mob-widget-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - finality: "final", - current_widget_head: ( - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:5] - ) -}' /tmp/mob-widget-rpc.json -``` - -Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. - -**Зачем нужен следующий шаг?** - -Блок записи виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. - -### Проследить один расчёт NEAR Intents и показать, что именно произошло - -Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». - - Стратегия - Смотрите на один расчёт как на читаемую трассу, а не как на теорию протокола с первой строки. - - 01POST /v0/transactions даёт каркас расчёта: входную точку, первые downstream-контракты и ранние логи. - 02POST /v0/block переиспользует тот же якорь, когда нужен контекст включающего блока вокруг этого расчёта. - 03RPC EXPERIMENTAL_tx_status нужен там, где уже требуется канонический DAG по receipt и имена событий, которые доказывают реальное движение активов. - -**Цель** - -- Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. - -**Официальные ссылки** - -- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) -- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) -- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) - -Для живой трассировки ниже используйте этот фиксированный якорь расчёта из mainnet, зафиксированный **18 апреля 2026 года**: - -- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` -- аккаунт `signer` и `receiver`: `intents.near` -- высота включающего блока: `194573310` - -Быстрая полезная модель здесь простая: +}' /tmp/callback-check-transaction.json -- `intents.near` выполняет входную точку расчёта -- последующие receipt расходятся по контрактам, которые реально переводят или выводят активы -- журналы событий показывают, какие действия расчёта случились, через имена вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` - -Для этого конкретного расчёта короткий человеческий ответ уже неплохой: - -- `intents.near` вызвал `execute_intents` -- downstream receipt ушли в `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` -- трасса выдала события `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` - -Если нужен протокольный фон, базовая форма сопоставления здесь — это двухсторонний `token_diff` intent: одна сторона подписывает желаемую разницу по активам, вторая сторона подписывает противоположную разницу, а затем совпавшая пара отправляется на расчёт. Но для рабочего расследования обычно понятнее начать с одной реальной транзакции расчёта и читать доказательства прямо с цепочки. - -```mermaid -flowchart LR - T["Одна mainnet-транзакция
4cfei8p4..."] --> I["intents.near
execute_intents"] - I --> R["Последующие receipt"] - R --> C["Подключаются другие контракты"] - R --> E["Появляются журналы событий"] - E --> TD["token_diff"] - E --> IE["intents_executed"] - E --> MT["mt_transfer / mt_withdraw"] -``` - -Публичных FastNear-поверхностей уже достаточно, чтобы ответить на практический вопрос: - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Каркас расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и печатаем основную транзакцию плюс первые последующие receipt | Даёт самый быстрый читаемый ответ на вопрос «во что этот расчёт пошёл дальше?» | -| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Показывает, в какой блок попал расчёт и какие receipt этой транзакции видны в блоке | -| Каноническое доказательство по receipt и событиям | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` плюс логи `EVENT_JSON` | Даёт протокольно-канонический DAG, `executor_id` и имена событий, которые объясняют, что реально сделал расчёт | - -**Что должен включать полезный ответ** - -- какую входную точку расчёта вы увидели на `intents.near` -- какие downstream-контракты и методы появились сразу после неё -- какие семейства событий выпустила трассировка -- одно итоговое предложение простым языком о том, что произошло - -Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. - -#### Shell-сценарий расчёта NEAR Intents - -Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. - -**Что вы делаете** - -- Получаете читаемую историю расчёта через Transactions API. -- Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. -- Подтверждаете канонический DAG по receipt и семейства событий через `EXPERIMENTAL_tx_status`. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 -INTENTS_SIGNER_ID=intents.near -``` - -1. Начните с самой транзакции расчёта и восстановите первый читаемый поток последующих receipt. - -```bash -INTENTS_BLOCK_HASH="$( - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$INTENTS_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/intents-transaction.json \ - | jq -r '.transactions[0].execution_outcome.block_hash' -)" - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - receipt_flow: [ - .transactions[0].receipts[:6][] - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - block_height: .execution_outcome.block_height, - methods: ( - [.receipt.receipt.Action.actions[]?.FunctionCall.method_name] - | map(select(. != null)) - ), - first_log: (.execution_outcome.outcome.logs[0] // null) - } - ] -}' /tmp/intents-transaction.json -``` - -Этот первый шаг уже даёт сильный операторский ответ: `intents.near` выполнил транзакцию расчёта, а ранние последующие receipt показывают, какие контракты вошли в каскад сразу после этого. - -2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. - -```bash -curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_id "$INTENTS_BLOCK_HASH" '{ - block_id: $block_id, - with_receipts: true, - with_transactions: false - }')" \ - | tee /tmp/intents-block.json >/dev/null - -jq --arg tx_hash "$INTENTS_TX_HASH" '{ - block_height: .block.block_height, - block_hash: .block.block_hash, - tx_receipts: [ - .block_receipts[] - | select(.transaction_hash == $tx_hash) - | { - receipt_id, - predecessor_id, - receiver_id, - block_height - } - ] -}' /tmp/intents-block.json +# На что смотреть: +# - downstream-receipt выполнил ft_on_transfer на v2.ref-finance.near +# - более поздний callback-receipt выполнил ft_resolve_transfer на wrap.near +# - callback_ran равно true, даже несмотря на downstream-сбой ``` -3. Подтвердите канонический DAG по receipt и извлеките семейства событий через RPC. +2. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ - --arg tx_hash "$INTENTS_TX_HASH" \ - --arg sender_account_id "$INTENTS_SIGNER_ID" '{ + --arg tx_hash "$TX_HASH" \ + --arg sender_account_id "$SENDER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "EXPERIMENTAL_tx_status", @@ -1466,115 +954,86 @@ curl -s "$RPC_URL" \ wait_until: "FINAL" } }')" \ - | tee /tmp/intents-rpc.json >/dev/null + | tee /tmp/callback-check-rpc.json >/dev/null -jq '{ - final_execution_status: .result.final_execution_status, - receipts_outcome: [ - .result.receipts_outcome[:6][] - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - first_log: (.outcome.logs[0] // null) - } - ] -}' /tmp/intents-rpc.json +jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ + top_level_status: .result.status, + callback_receipt: ( + first( + .result.receipts_outcome[] + | select(.id == $callback_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + logs: .outcome.logs, + status: .outcome.status + } + ) + ) +}' /tmp/callback-check-rpc.json -jq -r ' - .result.receipts_outcome[] - | .outcome.logs[] - | select(startswith("EVENT_JSON:")) - | capture("event\":\"(?[^\"]+)\"").event -' /tmp/intents-rpc.json | sort -u +# На что смотреть: +# - downstream ft_on_transfer receipt упал на v2.ref-finance.near +# - wrap.near всё равно позже получил ft_resolve_transfer +# - лог callback-а показывает refund обратно отправителю ``` **Зачем нужен следующий шаг?** -`POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. - -## Частые задачи - -### Найти одну транзакцию - -**Начните здесь** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. - -**Следующая страница при необходимости** - -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. -- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. - -**Остановитесь, когда** - -- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. -- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. - -### Исследовать квитанцию - -**Начните здесь** +Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. +## Расширенные сценарии и case study -**Следующая страница при необходимости** +Примеры ниже всё ещё полезны, но они длиннее или более специализированы, чем основные start-here-сценарии выше. `Berry Club` и `OutLayer` вынесены в отдельные case-study-страницы, паттерн provenance для SocialDB теперь живёт на отдельной расширенной странице, а последний пример здесь оставляет только компактный multi-contract follow-up. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. -- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. +### Расширенный паттерн provenance для SocialDB -**Остановитесь, когда** +Если читаемый факт уже приходит из `api.near.social`, держите follow-up маленьким: сначала семантическое значение, затем `:block`, потом lookup по блоку и транзакции в FastNear. Для одного канонического примера такого сценария используйте [отдельную страницу паттерна provenance для SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs). -- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. +### Расширенный сценарий: какие downstream-контракты затронула эта транзакция? -**Переходите дальше, когда** +Используйте этот сценарий, когда у вас уже есть один multi-contract tx hash и следующий вопрос звучит просто: «в какие контракты ушёл этот вызов после top-level action?» -- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. +Этот зафиксированный mainnet-якорь по-прежнему хорошо подходит как пример, хотя сама транзакция и относится к `intents.near`: -### Посмотреть недавнюю активность аккаунта - -**Начните здесь** - -- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. - -**Остановитесь, когда** - -- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. - -**Переходите дальше, когда** - -- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). -- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). - -### Восстановить ограниченное окно по блокам - -**Начните здесь** - -- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. -- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. +- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` +- аккаунт `signer` и `receiver`: `intents.near` +- высота включающего блока: `194573310` -**Следующая страница при необходимости** +Короткий ответ для этой tx уже полезен: -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. +- top-level метод был `execute_intents` +- ранние downstream-receipt затронули `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` +- в более поздних логах были семейства событий вроде `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` -**Остановитесь, когда** +Для большинства вопросов достаточно Transactions API: -- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 -**Переходите дальше, когда** +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name + }, + downstream_receivers: ( + [.transactions[0].receipts[] | .receipt.receiver_id] + | unique + ), + first_logs: ( + [.transactions[0].receipts[] | .execution_outcome.outcome.logs[]?] + | .[:5] + ) + }' +``` -- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +Если нужен включающий блок, расширяйтесь один раз до Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block). Если нужен канонический DAG по receipt или сырые логи `EVENT_JSON`, расширяйтесь ещё на один шаг до RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status). Учебная идея здесь общая: начинайте с одного tx hash, перечислите downstream receiver-и и останавливайтесь, пока trace действительно не требует большего. ## Частые ошибки diff --git a/static/ru/tx/examples/berry-club.md b/static/ru/tx/examples/berry-club.md index 5606276..dfa2a85 100644 --- a/static/ru/tx/examples/berry-club.md +++ b/static/ru/tx/examples/berry-club.md @@ -1,125 +1,52 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples/berry-club](https://docs.fastnear.com/ru/tx/examples/berry-club) -{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как восстанавливать доски Berry Club через FastNear. Он отделяет текущее состояние из get_lines от исторического разбора через диапазоны блоков, историю аккаунта, раскрытие транзакций и проигрывание draw-аргументов. */} +{/* FASTNEAR_AI_DISCOVERY: Этот case study показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} -# Berry Club: как восстанавливать исторические доски +# Berry Club Case Study: как читать живую доску и разбирать одну эпоху -Используйте этот разбор, когда вопрос звучит так: «как Berry Club выглядел в определённую эпоху и какие `draw`-вызовы сделали доску именно такой?» +Используйте этот case study, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. -Это read-only разбор из семейства Transactions examples. Если нужна только доска прямо сейчас, используйте `get_lines` и остановитесь. Если нужно объяснить, как доска пришла к такому виду, переключайтесь на историю блоков, историю аккаунта, раскрытые `draw`-вызовы и проигрывание. +Начните с живой доски. Если этого уже достаточно для ответа, на этом можно остановиться. - Стратегия - Сначала прочитайте живую доску, затем ограничьте эпоху и только после этого проигрывайте draw-вызовы, которые её объясняют. +Переходите к Transactions API только тогда, когда вопрос становится историческим: «как Berry Club выглядел в одной более ранней эпохе и какие `draw`-вызовы сделали доску именно такой?» - 01RPC call_function get_lines даёт текущую доску 50x50 и показывает, как выглядит «сейчас». - 02POST /v0/blocks вместе с POST /v0/account ограничивают одну эпоху и дают кандидатные хеши draw. - 03POST /v0/transactions раскрывает эти draw-вызовы, чтобы их можно было проиграть в исторические контрольные точки. +Карточка живой доски: запрашивает `berryclub.ek.near` `get_lines` через mainnet RPC и рендерит текущую сетку 50x50 в интерфейсе документации. -Держите рядом: +## 1. Прочитайте живую доску -- [js.fastnear.com](https://js.fastnear.com/) -- [fastnear/js-monorepo](https://github.com/fastnear/js-monorepo) -- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) -- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) -- [Transactions API: диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) -- [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) - -В этом руководстве история Berry Club разбирается только на mainnet. Снимки ниже собраны из воспроизводимых данных mainnet, которые уже сохранены в этом репозитории. - -## Короткая версия - -Berry Club даёт чистый view текущего состояния через `get_lines`, но не даёт готового эндпоинта вида «доска на блоке N». - -Из-за этого задача делится на две части: - -- используйте RPC `call_function`, когда вопрос звучит как «как доска выглядит сейчас?» -- используйте индексированную историю, когда вопрос звучит как «какие записи привели к этой доске?» -- используйте архивный RPC только тогда, когда нужно напрямую материализовать уже известную контрольную точку +Это самый короткий полезный запрос: -```mermaid -flowchart TD - A["RPC call_function: get_lines"] --> B["Текущая доска 50x50"] - C["Transactions API: /v0/blocks"] --> D["Ограничить эпоху"] - D --> E["/v0/account для berryclub.ek.near"] - E --> F["Кандидатные хеши draw-транзакций"] - F --> G["Раскрытие через /v0/transactions"] - G --> H["Проигрывание draw-записей в историческую доску"] -``` - -## Почему Berry Club хорошо учит истории в NEAR - -Berry Club удобно показывает обе стороны задачи: - -- чистое чтение текущего состояния через `get_lines` -- длинную историю вызовов `draw` с обычными аргументами `FunctionCall` -- формат доски, который легко декодировать и рендерить обычным JavaScript - -Это очень NEAR-подобная форма: один view-метод для текущего состояния, один write-метод для изменений и индексированная история, когда нужно объяснить, как это состояние вообще появилось. - -## 1. Сначала прочитайте текущую доску - -Живое демо использует `berryclub.ek.near` и читает доску через view-вызов `get_lines`: +```bash +ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" -```javascript -await near.view({ - contractId: 'berryclub.ek.near', - methodName: 'get_lines', - args: { - lines: [...Array(50).keys()], - }, -}); +curl -sS https://rpc.mainnet.fastnear.com \ + -H 'content-type: application/json' \ + --data "{ + \"jsonrpc\": \"2.0\", + \"id\": \"berry-live-board\", + \"method\": \"query\", + \"params\": { + \"request_type\": \"call_function\", + \"finality\": \"final\", + \"account_id\": \"berryclub.ek.near\", + \"method_name\": \"get_lines\", + \"args_base64\": \"$ARGS_BASE64\" + } + }" | jq '.result | {block_height, line_count: (.result | implode | fromjson | length)}' ``` -Это путь текущего состояния. Он не отвечает на вопрос, как доска пришла к такому виду. - -| Вопрос | Лучшая поверхность | Почему | -| --- | --- | --- | -| как доска выглядит сейчас? | [RPC `call_function`](https://docs.fastnear.com/ru/rpc/contract/call-function) | контракт уже отдаёт текущее состояние через `get_lines` | -| какие `draw` были в этой эпохе? | [`/v0/account`](https://docs.fastnear.com/ru/tx/account) + [`/v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | индексированная история даёт ограниченный набор записей и раскрытые аргументы | -| как доска выглядела в известной контрольной точке? | архивный RPC или полное проигрывание | можно напрямую материализовать состояние из архива или восстановить его самому по историческим записям | +Этот запрос отдаёт текущую доску 50x50 прямо из контракта. Дальше нужно только декодировать каждую base64-строку в 50 цветов пикселей. -## 2. Как декодировать `get_lines` в сетку 50x50 +## 2. Восстановите одну более раннюю эпоху -Полезная часть Berry Club-разметки из `js.fastnear.com` — это декодер строк: +Когда нужна история, держите путь коротким: -- каждая строка приходит в base64 -- её нужно декодировать в байты -- первые 4 байта нужно пропустить -- дальше цвета читаются как 32-битные little-endian значения каждые 8 байт +1. ограничьте одну эпоху +2. получите кандидатные `draw`-транзакции для `berryclub.ek.near` +3. раскройте эти хеши +4. проиграйте массивы `pixels` от старых к новым -```javascript -function decodeLine(encodedLine) { - const bytes = Buffer.from(encodedLine, 'base64'); - const colors = []; - - for (let offset = 4; offset < bytes.length; offset += 8) { - colors.push(bytes.readUInt32LE(offset) & 0xffffff); - } - - return colors; -} -``` - -Примените это ко всем 50 строкам — и получите полную сетку 50x50, готовую к рендерингу. - -## 3. Ограничьте эпоху, которую хотите изучить - -Сначала ограничьте эпоху, прежде чем искать draw-записи. Проверочный снимок запуска в этом репозитории находится на блоке `21898354`, а средний снимок — на блоке `97601515`. - -Сначала зафиксируйте ближайший диапазон блоков: - -```bash -curl -sS https://tx.main.fastnear.com/v0/blocks \ - -H 'content-type: application/json' \ - --data '{ - "from_block_height": 21898350, - "to_block_height": 21898355, - "desc": false, - "limit": 5 - }' -``` - -Затем переключитесь на историю аккаунта и запросите активность Berry Club внутри ограниченного диапазона блоков: +В этом примере используется узкое окно вокруг блока `97601515`: ```bash curl -sS https://tx.main.fastnear.com/v0/account \ @@ -131,21 +58,14 @@ curl -sS https://tx.main.fastnear.com/v0/account \ "is_real_receiver": true, "from_tx_block_height": 97576515, "to_tx_block_height": 97601516, - "desc": true, - "limit": 40 - }' + "desc": false, + "limit": 200 + }' | jq '.account_txs | map({transaction_hash, tx_block_height}) | .[-5:]' ``` -Здесь полезна именно такая последовательность: - -- `/v0/blocks` помогает понять соседство по высотам блоков -- `/v0/account` возвращает кандидатные хеши транзакций Berry Club внутри этого диапазона +Если окно ещё нужно подобрать, сначала можно использовать [`/v0/blocks`](https://docs.fastnear.com/ru/tx/blocks). Это не часть основного Berry Club-сценария. -## 4. Раскройте транзакции и оставьте только `draw` - -Когда кандидатные хеши уже есть, раскройте их и оставьте только верхнеуровневые вызовы `draw`, где получатель — `berryclub.ek.near`. - -Аргументы вызова — это обычные данные `FunctionCall` вида `{ pixels: [...] }`: +Раскройте кандидатные хеши и оставьте только верхнеуровневые вызовы `draw`: ```bash curl -sS https://tx.main.fastnear.com/v0/transactions \ @@ -159,68 +79,29 @@ curl -sS https://tx.main.fastnear.com/v0/transactions \ | select(.transaction.receiver_id == "berryclub.ek.near") | .transaction.actions[]?.FunctionCall | select(.method_name == "draw") - | { - method_name, - args: (.args | @base64d | fromjson) - }' + | {method_name, args: (.args | @base64d | fromjson)}' ``` -Это даёт всё, что нужно для проигрывания: - -- какая транзакция записывала пиксели -- какие координаты были затронуты -- какие цвета были записаны - -## 5. Проиграйте исторические `draw`-вызовы в доску - -Для полного проигрывания держите в памяти массив 50x50 и применяйте раскрытые транзакции `draw` от старых к новым. +Затем проиграйте массивы `pixels` от старых к новым: ```javascript const board = Array.from({ length: 50 }, () => Array(50).fill(0)); -function applyDraw(boardState, drawArgs) { - for (const pixel of drawArgs.pixels) { +for (const drawTx of drawTransactionsOldestFirst) { + for (const pixel of drawTx.args.pixels) { if (pixel.x < 0 || pixel.x >= 50 || pixel.y < 0 || pixel.y >= 50) { continue; } - boardState[pixel.y][pixel.x] = pixel.color; + board[pixel.y][pixel.x] = pixel.color; } } - -for (const drawTx of drawTransactionsOldestFirst) { - applyDraw(board, drawTx.args); -} ``` -Важно не путать два разных пути: - -- `get_lines` — это текущее состояние -- `tx/account` плюс `tx/transactions` — это материал для проигрывания - -## 6. Готовые контрольные точки по эпохам - -Галерея ниже использует уже сохранённые данные снимков, собранные из mainnet-истории Berry Club: - -- `launch` — последний успешный `draw` в пределах первых 24 часов после первого успешного draw -- `mid` — последний успешный `draw` не позже средней временной точки всей истории Berry Club -- `recent` — последний успешный `draw`, который увидел скрипт при пересборке снимков - -Галерея снимков: контрольные точки launch, mid и recent из сохранённого `src/data/berryClubSnapshots.json`. - -Сейчас эти снимки привязаны к таким транзакциям: +В этом и состоит исторический паттерн. У Berry Club нет готового эндпоинта «доска на блоке N», поэтому старые эпохи восстанавливаются проигрыванием `draw`-записей. -- `launch`: `BDNFpCpLXjBrgjR6z6wCZmB9EWdHnVMdqau3iTWTRE5H` на блоке `21898354` -- `mid`: `Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ` на блоке `97601515` -- `recent`: `8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL` на блоке `194588754` +## Связанные руководства -## Куда идти за подписанными взаимодействиями - -Эта страница должна оставаться в режиме чтения. - -Если нужны живые подписанные сценарии для `draw` и `buy_tokens`, переходите сюда: - -- [js.fastnear.com](https://js.fastnear.com/) -- [Berry Club example в fastnear/js-monorepo](https://github.com/fastnear/js-monorepo/tree/main/examples/static/berryclub) - -Именно там уместны кошелёк и подписанные действия. Эта страница посвящена историческому восстановлению. +- [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) +- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) diff --git a/static/ru/tx/examples/berry-club/index.md b/static/ru/tx/examples/berry-club/index.md index 5606276..dfa2a85 100644 --- a/static/ru/tx/examples/berry-club/index.md +++ b/static/ru/tx/examples/berry-club/index.md @@ -1,125 +1,52 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples/berry-club](https://docs.fastnear.com/ru/tx/examples/berry-club) -{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как восстанавливать доски Berry Club через FastNear. Он отделяет текущее состояние из get_lines от исторического разбора через диапазоны блоков, историю аккаунта, раскрытие транзакций и проигрывание draw-аргументов. */} +{/* FASTNEAR_AI_DISCOVERY: Этот case study показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} -# Berry Club: как восстанавливать исторические доски +# Berry Club Case Study: как читать живую доску и разбирать одну эпоху -Используйте этот разбор, когда вопрос звучит так: «как Berry Club выглядел в определённую эпоху и какие `draw`-вызовы сделали доску именно такой?» +Используйте этот case study, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. -Это read-only разбор из семейства Transactions examples. Если нужна только доска прямо сейчас, используйте `get_lines` и остановитесь. Если нужно объяснить, как доска пришла к такому виду, переключайтесь на историю блоков, историю аккаунта, раскрытые `draw`-вызовы и проигрывание. +Начните с живой доски. Если этого уже достаточно для ответа, на этом можно остановиться. - Стратегия - Сначала прочитайте живую доску, затем ограничьте эпоху и только после этого проигрывайте draw-вызовы, которые её объясняют. +Переходите к Transactions API только тогда, когда вопрос становится историческим: «как Berry Club выглядел в одной более ранней эпохе и какие `draw`-вызовы сделали доску именно такой?» - 01RPC call_function get_lines даёт текущую доску 50x50 и показывает, как выглядит «сейчас». - 02POST /v0/blocks вместе с POST /v0/account ограничивают одну эпоху и дают кандидатные хеши draw. - 03POST /v0/transactions раскрывает эти draw-вызовы, чтобы их можно было проиграть в исторические контрольные точки. +Карточка живой доски: запрашивает `berryclub.ek.near` `get_lines` через mainnet RPC и рендерит текущую сетку 50x50 в интерфейсе документации. -Держите рядом: +## 1. Прочитайте живую доску -- [js.fastnear.com](https://js.fastnear.com/) -- [fastnear/js-monorepo](https://github.com/fastnear/js-monorepo) -- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) -- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) -- [Transactions API: диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) -- [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) - -В этом руководстве история Berry Club разбирается только на mainnet. Снимки ниже собраны из воспроизводимых данных mainnet, которые уже сохранены в этом репозитории. - -## Короткая версия - -Berry Club даёт чистый view текущего состояния через `get_lines`, но не даёт готового эндпоинта вида «доска на блоке N». - -Из-за этого задача делится на две части: - -- используйте RPC `call_function`, когда вопрос звучит как «как доска выглядит сейчас?» -- используйте индексированную историю, когда вопрос звучит как «какие записи привели к этой доске?» -- используйте архивный RPC только тогда, когда нужно напрямую материализовать уже известную контрольную точку +Это самый короткий полезный запрос: -```mermaid -flowchart TD - A["RPC call_function: get_lines"] --> B["Текущая доска 50x50"] - C["Transactions API: /v0/blocks"] --> D["Ограничить эпоху"] - D --> E["/v0/account для berryclub.ek.near"] - E --> F["Кандидатные хеши draw-транзакций"] - F --> G["Раскрытие через /v0/transactions"] - G --> H["Проигрывание draw-записей в историческую доску"] -``` - -## Почему Berry Club хорошо учит истории в NEAR - -Berry Club удобно показывает обе стороны задачи: - -- чистое чтение текущего состояния через `get_lines` -- длинную историю вызовов `draw` с обычными аргументами `FunctionCall` -- формат доски, который легко декодировать и рендерить обычным JavaScript - -Это очень NEAR-подобная форма: один view-метод для текущего состояния, один write-метод для изменений и индексированная история, когда нужно объяснить, как это состояние вообще появилось. - -## 1. Сначала прочитайте текущую доску - -Живое демо использует `berryclub.ek.near` и читает доску через view-вызов `get_lines`: +```bash +ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" -```javascript -await near.view({ - contractId: 'berryclub.ek.near', - methodName: 'get_lines', - args: { - lines: [...Array(50).keys()], - }, -}); +curl -sS https://rpc.mainnet.fastnear.com \ + -H 'content-type: application/json' \ + --data "{ + \"jsonrpc\": \"2.0\", + \"id\": \"berry-live-board\", + \"method\": \"query\", + \"params\": { + \"request_type\": \"call_function\", + \"finality\": \"final\", + \"account_id\": \"berryclub.ek.near\", + \"method_name\": \"get_lines\", + \"args_base64\": \"$ARGS_BASE64\" + } + }" | jq '.result | {block_height, line_count: (.result | implode | fromjson | length)}' ``` -Это путь текущего состояния. Он не отвечает на вопрос, как доска пришла к такому виду. - -| Вопрос | Лучшая поверхность | Почему | -| --- | --- | --- | -| как доска выглядит сейчас? | [RPC `call_function`](https://docs.fastnear.com/ru/rpc/contract/call-function) | контракт уже отдаёт текущее состояние через `get_lines` | -| какие `draw` были в этой эпохе? | [`/v0/account`](https://docs.fastnear.com/ru/tx/account) + [`/v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | индексированная история даёт ограниченный набор записей и раскрытые аргументы | -| как доска выглядела в известной контрольной точке? | архивный RPC или полное проигрывание | можно напрямую материализовать состояние из архива или восстановить его самому по историческим записям | +Этот запрос отдаёт текущую доску 50x50 прямо из контракта. Дальше нужно только декодировать каждую base64-строку в 50 цветов пикселей. -## 2. Как декодировать `get_lines` в сетку 50x50 +## 2. Восстановите одну более раннюю эпоху -Полезная часть Berry Club-разметки из `js.fastnear.com` — это декодер строк: +Когда нужна история, держите путь коротким: -- каждая строка приходит в base64 -- её нужно декодировать в байты -- первые 4 байта нужно пропустить -- дальше цвета читаются как 32-битные little-endian значения каждые 8 байт +1. ограничьте одну эпоху +2. получите кандидатные `draw`-транзакции для `berryclub.ek.near` +3. раскройте эти хеши +4. проиграйте массивы `pixels` от старых к новым -```javascript -function decodeLine(encodedLine) { - const bytes = Buffer.from(encodedLine, 'base64'); - const colors = []; - - for (let offset = 4; offset < bytes.length; offset += 8) { - colors.push(bytes.readUInt32LE(offset) & 0xffffff); - } - - return colors; -} -``` - -Примените это ко всем 50 строкам — и получите полную сетку 50x50, готовую к рендерингу. - -## 3. Ограничьте эпоху, которую хотите изучить - -Сначала ограничьте эпоху, прежде чем искать draw-записи. Проверочный снимок запуска в этом репозитории находится на блоке `21898354`, а средний снимок — на блоке `97601515`. - -Сначала зафиксируйте ближайший диапазон блоков: - -```bash -curl -sS https://tx.main.fastnear.com/v0/blocks \ - -H 'content-type: application/json' \ - --data '{ - "from_block_height": 21898350, - "to_block_height": 21898355, - "desc": false, - "limit": 5 - }' -``` - -Затем переключитесь на историю аккаунта и запросите активность Berry Club внутри ограниченного диапазона блоков: +В этом примере используется узкое окно вокруг блока `97601515`: ```bash curl -sS https://tx.main.fastnear.com/v0/account \ @@ -131,21 +58,14 @@ curl -sS https://tx.main.fastnear.com/v0/account \ "is_real_receiver": true, "from_tx_block_height": 97576515, "to_tx_block_height": 97601516, - "desc": true, - "limit": 40 - }' + "desc": false, + "limit": 200 + }' | jq '.account_txs | map({transaction_hash, tx_block_height}) | .[-5:]' ``` -Здесь полезна именно такая последовательность: - -- `/v0/blocks` помогает понять соседство по высотам блоков -- `/v0/account` возвращает кандидатные хеши транзакций Berry Club внутри этого диапазона +Если окно ещё нужно подобрать, сначала можно использовать [`/v0/blocks`](https://docs.fastnear.com/ru/tx/blocks). Это не часть основного Berry Club-сценария. -## 4. Раскройте транзакции и оставьте только `draw` - -Когда кандидатные хеши уже есть, раскройте их и оставьте только верхнеуровневые вызовы `draw`, где получатель — `berryclub.ek.near`. - -Аргументы вызова — это обычные данные `FunctionCall` вида `{ pixels: [...] }`: +Раскройте кандидатные хеши и оставьте только верхнеуровневые вызовы `draw`: ```bash curl -sS https://tx.main.fastnear.com/v0/transactions \ @@ -159,68 +79,29 @@ curl -sS https://tx.main.fastnear.com/v0/transactions \ | select(.transaction.receiver_id == "berryclub.ek.near") | .transaction.actions[]?.FunctionCall | select(.method_name == "draw") - | { - method_name, - args: (.args | @base64d | fromjson) - }' + | {method_name, args: (.args | @base64d | fromjson)}' ``` -Это даёт всё, что нужно для проигрывания: - -- какая транзакция записывала пиксели -- какие координаты были затронуты -- какие цвета были записаны - -## 5. Проиграйте исторические `draw`-вызовы в доску - -Для полного проигрывания держите в памяти массив 50x50 и применяйте раскрытые транзакции `draw` от старых к новым. +Затем проиграйте массивы `pixels` от старых к новым: ```javascript const board = Array.from({ length: 50 }, () => Array(50).fill(0)); -function applyDraw(boardState, drawArgs) { - for (const pixel of drawArgs.pixels) { +for (const drawTx of drawTransactionsOldestFirst) { + for (const pixel of drawTx.args.pixels) { if (pixel.x < 0 || pixel.x >= 50 || pixel.y < 0 || pixel.y >= 50) { continue; } - boardState[pixel.y][pixel.x] = pixel.color; + board[pixel.y][pixel.x] = pixel.color; } } - -for (const drawTx of drawTransactionsOldestFirst) { - applyDraw(board, drawTx.args); -} ``` -Важно не путать два разных пути: - -- `get_lines` — это текущее состояние -- `tx/account` плюс `tx/transactions` — это материал для проигрывания - -## 6. Готовые контрольные точки по эпохам - -Галерея ниже использует уже сохранённые данные снимков, собранные из mainnet-истории Berry Club: - -- `launch` — последний успешный `draw` в пределах первых 24 часов после первого успешного draw -- `mid` — последний успешный `draw` не позже средней временной точки всей истории Berry Club -- `recent` — последний успешный `draw`, который увидел скрипт при пересборке снимков - -Галерея снимков: контрольные точки launch, mid и recent из сохранённого `src/data/berryClubSnapshots.json`. - -Сейчас эти снимки привязаны к таким транзакциям: +В этом и состоит исторический паттерн. У Berry Club нет готового эндпоинта «доска на блоке N», поэтому старые эпохи восстанавливаются проигрыванием `draw`-записей. -- `launch`: `BDNFpCpLXjBrgjR6z6wCZmB9EWdHnVMdqau3iTWTRE5H` на блоке `21898354` -- `mid`: `Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ` на блоке `97601515` -- `recent`: `8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL` на блоке `194588754` +## Связанные руководства -## Куда идти за подписанными взаимодействиями - -Эта страница должна оставаться в режиме чтения. - -Если нужны живые подписанные сценарии для `draw` и `buy_tokens`, переходите сюда: - -- [js.fastnear.com](https://js.fastnear.com/) -- [Berry Club example в fastnear/js-monorepo](https://github.com/fastnear/js-monorepo/tree/main/examples/static/berryclub) - -Именно там уместны кошелёк и подписанные действия. Эта страница посвящена историческому восстановлению. +- [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) +- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index 412cfab..a99c80c 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -29,7 +29,7 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. -Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. +Если нужен более развёрнутый case study на той же поверхности, переходите к [Berry Club case study](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer case study](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. ## С чего начать @@ -176,6 +176,135 @@ curl -s "$TX_BASE_URL/v0/receipt" \ `POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. +### Какая receipt выдала этот лог или event? + +Используйте это расследование, когда история звучит так: «у меня есть один tx hash и один фрагмент лога, и мне нужно точно понять, какая именно receipt его выдала». + +Это другой вопрос, чем более поздний сценарий «дошёл ли callback?». Здесь цель проще: привязать одну наблюдаемую строку лога к одному точному `receipt_id`, одному методу и одному исполнителю. + + Стратегия + Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога. + + 01POST /v0/transactions даёт полный индексированный список receipt для одного tx hash, включая receipt-логи. + 02jq сужает этот список до receipt, в логах которых встречается нужный вам фрагмент. + 03Как только совпадение осталось одно, сохраняйте его receipt_id, executor и имя метода как точный ответ. + +**Цель** + +- Начать с одного mainnet tx hash и одного фрагмента лога и определить точную receipt, которая выдала этот лог. + +Для этого зафиксированного mainnet-примера используйте: + +- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` +- фрагмент лога: `Refund` +- ожидаемый matching `receipt_id`: `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` +- ожидаемый executor: `wrap.near` +- ожидаемый метод: `ft_resolve_transfer` + +Эта транзакция полезна тем, что в ней есть две разные logged receipt внутри одной истории: + +- ранний лог `Transfer ...` на receipt с `ft_transfer_call` +- более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` + +```mermaid +flowchart LR + T["Один tx hash
2KhhB1uD..."] --> L["Читаем все receipt-логи"] + L --> X["Ищем фрагмент:
Refund"] + X --> R["Точная receipt
9sLHQpaG..."] + R --> A["Ответ:
wrap.near / ft_resolve_transfer"] +``` + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Атрибуция лога | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | +| Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | + +**Что должен включать полезный ответ** + +- какой `receipt_id` выдал лог +- какой контракт исполнил эту receipt +- какой метод там выполнился +- точную строку лога, которая совпала +- одно простое предложение вроде «лог `Refund` пришёл из `wrap.near` в receipt с методом `ft_resolve_transfer`» + +#### Shell-сценарий атрибуции лога + +Используйте этот сценарий, когда у вас уже есть tx hash и следующий вопрос звучит как «какая receipt это сказала?» + +**Что вы делаете** + +- Один раз получаете транзакцию и сохраняете список её receipt. +- Фильтруете receipt по одному фрагменту лога. +- Останавливаетесь, как только у вас есть один точный `receipt_id`, один executor и одно имя метода. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +LOG_FRAGMENT=Refund +``` + +1. Получите транзакцию и сохраните список receipt. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/log-attribution-transaction.json >/dev/null +``` + +2. Отфильтруйте список receipt до логов, которые содержат нужный вам фрагмент. + +```bash +jq --arg fragment "$LOG_FRAGMENT" '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id + }, + matching_receipts: [ + .transactions[0].receipts[] + | select(any(.execution_outcome.outcome.logs[]?; contains($fragment))) + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + block_height: .execution_outcome.block_height, + logs: .execution_outcome.outcome.logs + } + ] +}' /tmp/log-attribution-transaction.json + +# На что смотреть: +# - фрагмент `Refund` совпадает ровно с одной receipt +# - это receipt 9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w +# - receipt исполнилась на wrap.near +# - имя метода — ft_resolve_transfer +``` + +3. Если хотите увидеть все logged receipt рядом, распечатайте только те receipt, где вообще были логи. + +```bash +jq '{ + logged_receipts: [ + .transactions[0].receipts[] + | select((.execution_outcome.outcome.logs | length) > 0) + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + logs: .execution_outcome.outcome.logs + } + ] +}' /tmp/log-attribution-transaction.json +``` + +Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. + +**Зачем нужен следующий шаг?** + +Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. + ### Превратить один страшный receipt ID из логов в понятную человеческую историю Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. @@ -228,19 +357,6 @@ flowchart LR #### Shell-сценарий: от страшного receipt ID к человеческой истории -## Ошибки и async - -Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и порядок callback-ов. - -Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. - -**Что вы делаете** - -- Сначала разрешаете receipt. -- Извлекаете `receipt.transaction_hash` через `jq`. -- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. -- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. - ```bash TX_BASE_URL=https://tx.main.fastnear.com RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' @@ -314,6 +430,12 @@ jq -r ' `POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. +## Ошибки и async + +Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. + +Используйте этот раздел, когда уже понятно, что транзакция жила дольше одной receipt, и следующий вопрос относится уже к форме исполнения, а не к простому поиску объекта. + ### Доказать, что одно неудачное действие сорвало весь пакет Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? @@ -659,804 +781,170 @@ jq \ Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. -### Проследить асинхронную promise-цепочку и доказать порядок callback-ов +### Дошёл ли callback вообще? + +Используйте это расследование, когда одна транзакция запустила downstream-работу на другом контракте, а настоящий вопрос звучит не как «успешно ли отработал receiver?», а как «вернулся ли callback обратно в исходный контракт?» + +Это самый короткий полезный сценарий про callback на странице: -Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» +- стартуйте с одного tx hash +- найдите downstream-receipt на другом контракте +- найдите более поздний callback-receipt, который вернулся в исходный контракт +- остановитесь, как только доказаны сам факт callback и его результат Стратегия - Смотрите на два хеша как на одну async-историю: докажите, что работа была жива, восстановите запрошенный порядок и сравните его с видимым downstream-state. + Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а. - 01RPC call_function на view отложенной работы доказывает, что promise-работа действительно уже была жива до resume-шага. - 02POST /v0/transactions даёт оба block-anchor и точный порядок, который запросила resume-транзакция. - 03RPC EXPERIMENTAL_tx_status вместе с downstream-view доказывают, где callback-и реально выполнились и в каком видимом порядке. + 01POST /v0/transactions показывает downstream-вызов и более поздний receipt, который возвращается в исходный контракт. + 02jq сужает этот список receipt до одного downstream-вызова и одного callback-receipt. + 03RPC EXPERIMENTAL_tx_status нужен только как дополнительное подтверждение, если вам важны канонический результат callback-а и его логи. **Цель** -- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. +- Доказать на одном фиксированном mainnet-примере, что `wrap.near` отправил `ft_transfer_call` в `v2.ref-finance.near`, receiver выполнил `ft_on_transfer`, а затем `wrap.near` получил callback `ft_resolve_transfer` обратно. -Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: +Этот фиксированный mainnet-пример с callback был замечен **19 апреля 2026 года**: -- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом -- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке -- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и -- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору +- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` +- аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` +- исходный контракт: `wrap.near` +- downstream-receiver: `v2.ref-finance.near` +- верхнеуровневый метод: `ft_transfer_call` +- downstream-метод: `ft_on_transfer` +- callback-метод: `ft_resolve_transfer` +- блок транзакции: `194692298` +- блок downstream-receipt: `194692300` +- блок callback-receipt: `194692301` ```mermaid flowchart LR - Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] - H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] - R --> C["Async cross-contract callback-и"] - C --> B["Recorder state
beta"] - B --> A["Recorder state
alpha"] - A --> G["Recorder state
gamma"] - Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] - R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] - G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] + T["Одна mainnet-транзакция
ft_transfer_call на wrap.near"] --> D["Downstream-receipt
v2.ref-finance.near.ft_on_transfer"] + D --> F["Receiver упал
E51: contract paused"] + F --> C["Callback-receipt обратно в wrap.near
ft_resolve_transfer"] + C --> R["Лог refund на wrap.near"] ``` -Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. - -Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. +Здесь хорошо видна одна полезная деталь NEAR: downstream-сбой не означает, что callback исчез. В этом случае `v2.ref-finance.near` уронил свой `ft_on_transfer`, но `wrap.near` всё равно позже получил `ft_resolve_transfer` и залогировал refund. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | -| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | -| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | -| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | -| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | -| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | -| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | -| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | +| Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | +| Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | **Что должен включать полезный ответ** -- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» -- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования -- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался -- в каких блоках стали видны изменения состояния -- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования - -## Доказательства по SocialDB - -Эти примеры стартуют с читаемого состояния в NEAR Social и откатываются назад к точной записи, которая это состояние создала. +- какой контракт получил downstream-вызов +- какой метод выполнился на downstream-контракте +- вернулся ли более поздний receipt в исходный контракт +- какой callback-метод там выполнился и в каком блоке +- одно простое предложение вроде «receiver упал, но исходный контракт всё равно получил callback и завершил перевод» -### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB +#### Shell-сценарий проверки callback-а -Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». - - Стратегия - Начните с читаемого значения поля, а затем превратите его field-level block в один receipt и одну транзакцию записи. - - 01NEAR Social POST /get даёт текущее значение profile.name и field-level :block. - 02POST /v0/block превращает этот блок в конкретный receipt и хеш транзакции mike.near -> social.near. - 03POST /v0/transactions доказывает payload записи, а RPC call_function get подтверждает, что поле и сейчас разрешается так же. - -**Цель** - -- Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. - -**Официальные ссылки** - -- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) -- [Живая поверхность чтения NEAR Social](https://api.near.social) - -Этот сценарий следует тому же рецепту доказательства, что и расследование по подписке, но добавляет ещё один важный нюанс SocialDB: для исторического доказательства `:block` на уровне конкретного поля обычно точнее, чем `:block` у родительского объекта. В этом живом примере `mike.near/profile/name` был записан на блоке `78675795`, тогда как более широкий объект `mike.near/profile` позже сдвинулся на другой блок из-за изменений в соседних полях. Роль FastNear в этом сценарии — превратить этот блок уровня поля в receipt, затем в транзакцию и потом в читаемый payload записи. - -Для этого живого примера текущее значение `profile.name` равно `Mike Purvis`, блок записи SocialDB на уровне поля равен `78675795`, ID receipt — `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN`, хеш исходной транзакции — `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ`, а внешний блок транзакции — `78675794`. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Семантическое чтение поля | NEAR Social `POST /get` | Читаем `mike.near/profile/name` с включёнными метаданными блока | Даёт читаемое значение поля и опорный `:block` SocialDB на уровне поля, где это значение было записано | -| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем блок уровня поля из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи уровня поля в конкретный receipt и хеш исходной транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который нёс `profile.name` и окружающие поля профиля в одном payload | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что поле и сейчас имеет это значение, хотя предыдущие шаги уже доказали конкретную историческую запись | - -**Что должен включать полезный ответ** - -- разрешается ли `mike.near/profile/name` сейчас в `Mike Purvis` -- высоту блока записи SocialDB на уровне поля (`78675795`) и объяснение, почему для этого вопроса этот якорь лучше, чем блок родительского профиля -- конкретный ID receipt и хеш исходной транзакции за этой записью -- доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload -- различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) - -#### Shell-сценарий доказательства поля профиля в NEAR Social - -Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. +Используйте этот сценарий, когда нужен один конкретный proof callback-а без превращения страницы в полный курс по теории promises. **Что вы делаете** -- Читаете текущее поле `profile.name` из NEAR Social и сохраняете блок записи SocialDB на уровне поля. -- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`, несущей `profile.name`. -- Завершаете каноническим RPC-подтверждением того, что поле всё ещё разрешается в то же значение на `final`. +- Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. +- Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. +- Останавливаетесь сразу, как только можете сказать, вернулся ли callback и что он сделал. ```bash -SOCIAL_API_BASE_URL=https://api.near.social TX_BASE_URL=https://tx.main.fastnear.com RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near -PROFILE_FIELD=profile/name -``` - -1. Прочитайте поле профиля из NEAR Social и сохраните блок записи SocialDB на уровне поля. - -```bash -PROFILE_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-profile-name.json \ - | jq -r --arg account_id "$ACCOUNT_ID" \ - '.[ $account_id ].profile.name[":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - current_name: .[$account_id].profile.name[""], - field_block_height: .[$account_id].profile.name[":block"], - parent_profile_block_height: .[$account_id].profile[":block"] -}' /tmp/mike-profile-name.json - -# Ожидаемое current_name: "Mike Purvis" -# Ожидаемая высота блока уровня поля: 78675795 +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +SENDER_ACCOUNT_ID=7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72 +ORIGIN_CONTRACT_ID=wrap.near +DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near ``` -2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. +1. Получите транзакцию и распечатайте downstream-receipt вместе с callback-receipt. ```bash -PROFILE_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-profile-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/callback-check-transaction.json >/dev/null -jq --arg account_id "$ACCOUNT_ID" '{ - profile_receipt: ( +CALLBACK_RECEIPT_ID="$( + jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | { - receipt_id, - transaction_hash, - block_height, - tx_block_height - } + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + | .receipt.receipt_id ) - ) -}' /tmp/mike-profile-block.json - -# Ожидаемый receipt ID: 2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN -# Ожидаемый хеш транзакции: 6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ -``` - -3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-profile-transaction.json >/dev/null + ' /tmp/callback-check-transaction.json +)" -jq '{ +jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" '{ transaction: { hash: .transactions[0].transaction.hash, signer_id: .transactions[0].transaction.signer_id, receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + tx_block_height: .transactions[0].execution_outcome.block_height }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), - description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), - tags: ( - .args - | @base64d - | fromjson - | .data["mike.near"].profile.tags - | keys - ) - } - ) -}' /tmp/mike-profile-transaction.json -``` - -4. Завершите каноническим подтверждением текущего состояния через raw RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-profile-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - finality: "final", - current_name: ( - .result.result - | implode - | fromjson - | .[$account_id].profile.name - ) -}' /tmp/mike-profile-rpc.json -``` - -Этот последний шаг подтверждает, что поле и сейчас разрешается в `Mike Purvis`. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись установила это поле и какая транзакция несла эту запись. - -**Зачем нужен следующий шаг?** - -NEAR Social даёт семантическое значение поля. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемый payload профиля. RPC даёт каноническое подтверждение текущего состояния. - -### Доказать, что `mike.near` подписался на `mob.near`, а затем восстановить транзакцию записи в SocialDB - -Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». - - Стратегия - Начните с семантической связи подписки, а затем используйте блок записи как мост назад к одному receipt и одной транзакции. - - 01NEAR Social POST /get даёт читаемую связь подписки и SocialDB :block, где она была записана. - 02POST /v0/block превращает этот блок записи в конкретный receipt и хеш транзакции за этой связью. - 03POST /v0/transactions доказывает payload с graph.follow и index.graph, а RPC call_function get подтверждает, что связь и сейчас существует. - -**Цель** - -- Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. - -**Официальные ссылки** - -- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) -- [Живая поверхность чтения NEAR Social](https://api.near.social) - -Читаемая связь подписки приходит из данных NEAR Social, а не из FastNear. Ключевой мост здесь — метаданные SocialDB `:block`: они указывают на блок, в котором исполнился receipt, записавший это значение. Этот блок не совпадает с блоком, в который была включена внешняя транзакция. Роль FastNear в этом сценарии — превратить эту высоту блока в receipt, затем в транзакцию и, наконец, в читаемую историю исполнения. - -Для этого живого примера текущая связь выглядит как `mike.near -> mob.near`, блок записи SocialDB равен `79574924`, ID receipt — `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th`, хеш исходной транзакции — `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb`, а внешний блок транзакции — `79574923`. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Семантическое чтение связи | NEAR Social `POST /get` | Читаем `mike.near/graph/follow/mob.near` с включёнными метаданными блока | Даёт читаемую связь подписки и опорный `:block` из SocialDB, где это значение было записано | -| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем высоту блока из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи SocialDB в конкретный receipt и хеш исходной транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который записал и `graph.follow`, и записи `index.graph` | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что связь подписки существует и сейчас, хотя предыдущие шаги уже доказали конкретную историческую запись | - -**Что должен включать полезный ответ** - -- существует ли сейчас связь подписки `mike.near -> mob.near` -- высоту блока записи SocialDB (`79574924`) и объяснение, почему это блок исполнения receipt -- конкретный ID receipt и хеш исходной транзакции за этой записью -- доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` -- различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) - -#### Shell-сценарий доказательства подписки в NEAR Social - -Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. - -**Что вы делаете** - -- Читаете текущую связь подписки из NEAR Social и сохраняете блок записи SocialDB. -- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`. -- Завершаете каноническим RPC-подтверждением того, что связь всё ещё существует на `final`. - -```bash -SOCIAL_API_BASE_URL=https://api.near.social -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near -TARGET_ACCOUNT_ID=mob.near -``` - -1. Прочитайте связь подписки из NEAR Social и сохраните блок записи SocialDB. - -```bash -FOLLOW_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - keys: [($account_id + "/graph/follow/" + $target_account_id)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-follow-edge.json \ - | jq -r --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" \ - '.[ $account_id ].graph.follow[ $target_account_id ][":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - follow_edge: .[$account_id].graph.follow[$target_account_id][""], - follow_block_height: .[$account_id].graph.follow[$target_account_id][":block"] -}' /tmp/mike-follow-edge.json - -# Ожидаемая высота блока записи: 79574924 -``` - -2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. - -```bash -FOLLOW_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$FOLLOW_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-follow-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - follow_receipt: ( + downstream_receipt: ( first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") + .transactions[0].receipts[] + | select(.receipt.receiver_id == $downstream) | { - receipt_id, - transaction_hash, - block_height, - tx_block_height + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + status: .execution_outcome.outcome.status, + block_height: .execution_outcome.block_height } ) - ) -}' /tmp/mike-follow-block.json - -# Ожидаемый receipt ID: UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th -# Ожидаемый хеш транзакции: FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -``` - -3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$FOLLOW_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-follow-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - follow_edge: (.args | @base64d | fromjson | .data["mike.near"].graph.follow["mob.near"]), - index_graph: ( - .args - | @base64d - | fromjson - | .data["mike.near"].index.graph - | fromjson - | map(select(.value.accountId == "mob.near")) - ) - } - ) -}' /tmp/mike-follow-transaction.json -``` - -4. Завершите каноническим подтверждением текущего состояния через raw RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - keys: [($account_id + "/graph/follow/" + $target_account_id)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-follow-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - finality: "final", - current_follow_edge: ( - .result.result - | implode - | fromjson - | .[$account_id].graph.follow[$target_account_id] - ) -}' /tmp/mike-follow-rpc.json -``` - -Этот последний шаг подтверждает, что связь подписки существует и сейчас. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись создала эту связь и какая транзакция несла эту запись. - -**Зачем нужен следующий шаг?** - -NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. - -### Какая транзакция записала `mob.near/widget/Profile`? - -Используйте это расследование, когда вопрос звучит так: «я уже знаю, что `mob.near/widget/Profile` существует прямо сейчас. Какая именно транзакция записала ту версию виджета, на которую я смотрю?» - -Это естественное tx-продолжение к более лёгкому RPC-сценарию про виджет и к provenance-NFT-сценарию. Задача здесь прямолинейная: - -- стартуем с собственного SocialDB-блока виджета -- превращаем этот блок в один `mob.near -> social.near` receipt -- восстанавливаем исходную транзакцию -- декодируем payload `set` и доказываем, что он действительно нёс исходник виджета - - Стратегия - Смотрите на write-block виджета как на весь мост сразу: блок в receipt, receipt в транзакцию, транзакцию в исходник. - - 01POST /v0/block начинает с блока виджета и сужает его до одного receipt mob.near -> social.near. - 02POST /v0/transactions превращает этот receipt в один читаемый payload set с исходником виджета. - 03RPC call_function get — это финальное подтверждение текущего состояния, что виджет и сейчас существует. - -**Цель** - -- Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -Для этого живого якоря: - -- аккаунт: `mob.near` -- виджет: `Profile` -- блок записи в SocialDB: `86494825` -- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` -- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` -- внешний блок транзакции: `86494824` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `86494825` с `with_receipts: true`, а затем фильтруем его обратно до `mob.near -> social.near` | Превращает блок записи виджета в один конкретный receipt и один конкретный хеш транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `mob.near/widget/Profile` | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | - -**Что должен включать полезный ответ** - -- высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции -- конкретный receipt ID и хеш исходной транзакции за этой записью виджета -- доказательство, что payload записи был `set` с `mob.near/widget/Profile` -- одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» - -#### Shell-сценарий доказательства записи виджета в NEAR Social - -Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. - -**Что вы делаете** - -- Стартуете с блока последней записи виджета. -- Переиспользуете эту высоту в FastNear block receipts, чтобы получить receipt и мост к транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы декодировать записанный исходник виджета. -- Завершаете сырым RPC-подтверждением, что виджет всё ещё существует сейчас. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -WIDGET_BLOCK_HEIGHT=86494825 -``` - -1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. - -```bash -WIDGET_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$WIDGET_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mob-widget-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - widget_write_receipt: ( + ), + callback_receipt: ( first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) | { - receipt_id, - transaction_hash, - block_height, - tx_block_height + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: .receipt.receipt.Action.actions[0].FunctionCall.method_name, + logs: .execution_outcome.outcome.logs, + status: .execution_outcome.outcome.status, + block_height: .execution_outcome.block_height } ) - ) -}' /tmp/mob-widget-block.json - -# Ожидаемый receipt ID: CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6 -# Ожидаемый хеш транзакции: 9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia -``` - -2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mob-widget-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].transaction.actions[0].FunctionCall - | { - method_name, - widget_source_head: ( - .args - | @base64d - | fromjson - | .data["mob.near"].widget.Profile[""] - | split("\n")[0:12] + ), + callback_ran: ( + first( + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" ) - } + | true + ) // false ) -}' /tmp/mob-widget-transaction.json -``` - -Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `9QDup...` вызвала `social.near set` и пронесла в `args` настоящий исходник `mob.near/widget/Profile`. - -3. Завершите каноническим подтверждением текущего состояния через сырой RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mob-widget-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - finality: "final", - current_widget_head: ( - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:5] - ) -}' /tmp/mob-widget-rpc.json -``` - -Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. - -**Зачем нужен следующий шаг?** - -Блок записи виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. - -### Проследить один расчёт NEAR Intents и показать, что именно произошло - -Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». - - Стратегия - Смотрите на один расчёт как на читаемую трассу, а не как на теорию протокола с первой строки. - - 01POST /v0/transactions даёт каркас расчёта: входную точку, первые downstream-контракты и ранние логи. - 02POST /v0/block переиспользует тот же якорь, когда нужен контекст включающего блока вокруг этого расчёта. - 03RPC EXPERIMENTAL_tx_status нужен там, где уже требуется канонический DAG по receipt и имена событий, которые доказывают реальное движение активов. - -**Цель** - -- Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. - -**Официальные ссылки** - -- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) -- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) -- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) - -Для живой трассировки ниже используйте этот фиксированный якорь расчёта из mainnet, зафиксированный **18 апреля 2026 года**: - -- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` -- аккаунт `signer` и `receiver`: `intents.near` -- высота включающего блока: `194573310` - -Быстрая полезная модель здесь простая: +}' /tmp/callback-check-transaction.json -- `intents.near` выполняет входную точку расчёта -- последующие receipt расходятся по контрактам, которые реально переводят или выводят активы -- журналы событий показывают, какие действия расчёта случились, через имена вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` - -Для этого конкретного расчёта короткий человеческий ответ уже неплохой: - -- `intents.near` вызвал `execute_intents` -- downstream receipt ушли в `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` -- трасса выдала события `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` - -Если нужен протокольный фон, базовая форма сопоставления здесь — это двухсторонний `token_diff` intent: одна сторона подписывает желаемую разницу по активам, вторая сторона подписывает противоположную разницу, а затем совпавшая пара отправляется на расчёт. Но для рабочего расследования обычно понятнее начать с одной реальной транзакции расчёта и читать доказательства прямо с цепочки. - -```mermaid -flowchart LR - T["Одна mainnet-транзакция
4cfei8p4..."] --> I["intents.near
execute_intents"] - I --> R["Последующие receipt"] - R --> C["Подключаются другие контракты"] - R --> E["Появляются журналы событий"] - E --> TD["token_diff"] - E --> IE["intents_executed"] - E --> MT["mt_transfer / mt_withdraw"] -``` - -Публичных FastNear-поверхностей уже достаточно, чтобы ответить на практический вопрос: - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Каркас расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и печатаем основную транзакцию плюс первые последующие receipt | Даёт самый быстрый читаемый ответ на вопрос «во что этот расчёт пошёл дальше?» | -| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Показывает, в какой блок попал расчёт и какие receipt этой транзакции видны в блоке | -| Каноническое доказательство по receipt и событиям | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` плюс логи `EVENT_JSON` | Даёт протокольно-канонический DAG, `executor_id` и имена событий, которые объясняют, что реально сделал расчёт | - -**Что должен включать полезный ответ** - -- какую входную точку расчёта вы увидели на `intents.near` -- какие downstream-контракты и методы появились сразу после неё -- какие семейства событий выпустила трассировка -- одно итоговое предложение простым языком о том, что произошло - -Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. - -#### Shell-сценарий расчёта NEAR Intents - -Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. - -**Что вы делаете** - -- Получаете читаемую историю расчёта через Transactions API. -- Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. -- Подтверждаете канонический DAG по receipt и семейства событий через `EXPERIMENTAL_tx_status`. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 -INTENTS_SIGNER_ID=intents.near -``` - -1. Начните с самой транзакции расчёта и восстановите первый читаемый поток последующих receipt. - -```bash -INTENTS_BLOCK_HASH="$( - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$INTENTS_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/intents-transaction.json \ - | jq -r '.transactions[0].execution_outcome.block_hash' -)" - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - receipt_flow: [ - .transactions[0].receipts[:6][] - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - block_height: .execution_outcome.block_height, - methods: ( - [.receipt.receipt.Action.actions[]?.FunctionCall.method_name] - | map(select(. != null)) - ), - first_log: (.execution_outcome.outcome.logs[0] // null) - } - ] -}' /tmp/intents-transaction.json -``` - -Этот первый шаг уже даёт сильный операторский ответ: `intents.near` выполнил транзакцию расчёта, а ранние последующие receipt показывают, какие контракты вошли в каскад сразу после этого. - -2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. - -```bash -curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_id "$INTENTS_BLOCK_HASH" '{ - block_id: $block_id, - with_receipts: true, - with_transactions: false - }')" \ - | tee /tmp/intents-block.json >/dev/null - -jq --arg tx_hash "$INTENTS_TX_HASH" '{ - block_height: .block.block_height, - block_hash: .block.block_hash, - tx_receipts: [ - .block_receipts[] - | select(.transaction_hash == $tx_hash) - | { - receipt_id, - predecessor_id, - receiver_id, - block_height - } - ] -}' /tmp/intents-block.json +# На что смотреть: +# - downstream-receipt выполнил ft_on_transfer на v2.ref-finance.near +# - более поздний callback-receipt выполнил ft_resolve_transfer на wrap.near +# - callback_ran равно true, даже несмотря на downstream-сбой ``` -3. Подтвердите канонический DAG по receipt и извлеките семейства событий через RPC. +2. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ - --arg tx_hash "$INTENTS_TX_HASH" \ - --arg sender_account_id "$INTENTS_SIGNER_ID" '{ + --arg tx_hash "$TX_HASH" \ + --arg sender_account_id "$SENDER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "EXPERIMENTAL_tx_status", @@ -1466,115 +954,86 @@ curl -s "$RPC_URL" \ wait_until: "FINAL" } }')" \ - | tee /tmp/intents-rpc.json >/dev/null + | tee /tmp/callback-check-rpc.json >/dev/null -jq '{ - final_execution_status: .result.final_execution_status, - receipts_outcome: [ - .result.receipts_outcome[:6][] - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - first_log: (.outcome.logs[0] // null) - } - ] -}' /tmp/intents-rpc.json +jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ + top_level_status: .result.status, + callback_receipt: ( + first( + .result.receipts_outcome[] + | select(.id == $callback_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + logs: .outcome.logs, + status: .outcome.status + } + ) + ) +}' /tmp/callback-check-rpc.json -jq -r ' - .result.receipts_outcome[] - | .outcome.logs[] - | select(startswith("EVENT_JSON:")) - | capture("event\":\"(?[^\"]+)\"").event -' /tmp/intents-rpc.json | sort -u +# На что смотреть: +# - downstream ft_on_transfer receipt упал на v2.ref-finance.near +# - wrap.near всё равно позже получил ft_resolve_transfer +# - лог callback-а показывает refund обратно отправителю ``` **Зачем нужен следующий шаг?** -`POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. - -## Частые задачи - -### Найти одну транзакцию - -**Начните здесь** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. - -**Следующая страница при необходимости** - -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. -- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. - -**Остановитесь, когда** - -- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. -- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. - -### Исследовать квитанцию - -**Начните здесь** +Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. +## Расширенные сценарии и case study -**Следующая страница при необходимости** +Примеры ниже всё ещё полезны, но они длиннее или более специализированы, чем основные start-here-сценарии выше. `Berry Club` и `OutLayer` вынесены в отдельные case-study-страницы, паттерн provenance для SocialDB теперь живёт на отдельной расширенной странице, а последний пример здесь оставляет только компактный multi-contract follow-up. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. -- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. +### Расширенный паттерн provenance для SocialDB -**Остановитесь, когда** +Если читаемый факт уже приходит из `api.near.social`, держите follow-up маленьким: сначала семантическое значение, затем `:block`, потом lookup по блоку и транзакции в FastNear. Для одного канонического примера такого сценария используйте [отдельную страницу паттерна provenance для SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs). -- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. +### Расширенный сценарий: какие downstream-контракты затронула эта транзакция? -**Переходите дальше, когда** +Используйте этот сценарий, когда у вас уже есть один multi-contract tx hash и следующий вопрос звучит просто: «в какие контракты ушёл этот вызов после top-level action?» -- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. +Этот зафиксированный mainnet-якорь по-прежнему хорошо подходит как пример, хотя сама транзакция и относится к `intents.near`: -### Посмотреть недавнюю активность аккаунта - -**Начните здесь** - -- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. - -**Остановитесь, когда** - -- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. - -**Переходите дальше, когда** - -- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). -- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). - -### Восстановить ограниченное окно по блокам - -**Начните здесь** - -- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. -- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. +- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` +- аккаунт `signer` и `receiver`: `intents.near` +- высота включающего блока: `194573310` -**Следующая страница при необходимости** +Короткий ответ для этой tx уже полезен: -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. +- top-level метод был `execute_intents` +- ранние downstream-receipt затронули `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` +- в более поздних логах были семейства событий вроде `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` -**Остановитесь, когда** +Для большинства вопросов достаточно Transactions API: -- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 -**Переходите дальше, когда** +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name + }, + downstream_receivers: ( + [.transactions[0].receipts[] | .receipt.receiver_id] + | unique + ), + first_logs: ( + [.transactions[0].receipts[] | .execution_outcome.outcome.logs[]?] + | .[:5] + ) + }' +``` -- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +Если нужен включающий блок, расширяйтесь один раз до Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block). Если нужен канонический DAG по receipt или сырые логи `EVENT_JSON`, расширяйтесь ещё на один шаг до RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status). Учебная идея здесь общая: начинайте с одного tx hash, перечислите downstream receiver-и и останавливайтесь, пока trace действительно не требует большего. ## Частые ошибки diff --git a/static/ru/tx/examples/outlayer.md b/static/ru/tx/examples/outlayer.md index 4c7ea07..fa8cee9 100644 --- a/static/ru/tx/examples/outlayer.md +++ b/static/ru/tx/examples/outlayer.md @@ -1,10 +1,12 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) -{/* FASTNEAR_AI_DISCOVERY: Этот пример остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} +{/* FASTNEAR_AI_DISCOVERY: Этот case study остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} -# OutLayer: трассировка запроса и разрешения воркером +# OutLayer Case Study: трассировка запроса и разрешения воркером -Используйте этот сценарий, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» +Используйте этот case study, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» + +Эта страница намеренно остаётся только в пределах публичных chain-данных. Она показывает caller-транзакцию, более позднюю worker-транзакцию и finish-receipts. Она не пытается доказывать внутреннюю TEE-, `yield/resume`- или CKD/MPC-архитектуру OutLayer только по chain-trace. Сначала смотрите на это как на задачу по истории транзакций: @@ -12,24 +14,6 @@ - одна более поздняя worker-side транзакция `submit_execution_output_and_resolve` или `resolve_execution` - переход к receipts только тогда, когда уже важен путь завершения - Стратегия - Сначала найдите два хеша, затем раскройте их, а к worker-receipts переходите только тогда, когда нужен finish-путь. - - 01POST /v0/account по outlayer.* — самый быстрый surface для поиска хешей. - 02POST /v0/transactions превращает caller-хеш и worker-хеш в читаемые signer, method и log-доказательства. - 03Разбирайте receipts worker-транзакции только тогда, когда реальный вопрос уже касается callback, списания или возврата средств. - -**Цель** - -- Восстановить одну caller-side транзакцию, одну worker-side транзакцию и завершающие receipts, которые относятся к одному и тому же запросу OutLayer. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Поиск хешей | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Забираем недавние хеши транзакций для `outlayer.near` | Даёт самый быстрый локальный по контракту surface, когда пара хешей ещё не известна | -| Раскрытие транзакций | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Раскрываем caller-side и worker-side хеши вместе | Превращает сырые хеши в signer-, receiver-, action- и log-доказательства | -| Разбор finish-пути | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем worker-хеш и читаем список его receipts | Показывает, где материализовались callback, списание и возврат средств | -| Необязательная проверка идентичности | RPC [`view_account`](https://docs.fastnear.com/ru/rpc/account/view-account) | Идём в RPC только если следующий вопрос уже про идентичность контракта, а не про историю транзакций | Держит проверку текущего состояния отдельно от трассировки истории | - ## Проверенный shell-сценарий Эта пара работала 18 апреля 2026 года: @@ -65,14 +49,7 @@ jq '{ }' /tmp/outlayer-pair.json ``` -Что это доказывает: - -- запросная транзакция шла от `solarflux.near` к `outlayer.near` -- в логах запроса фигурировал проект `zavodil.near/near-email` -- worker-транзакция позже пришла от `worker.outlayer.near` к `outlayer.near` -- в логах воркера были `Stored pending output` и `Resolving execution ...` - -Это и есть главный наблюдаемый цикл в терминах NEAR: сначала caller-side запрос, затем более позднее worker-side разрешение. +Это и есть главный наблюдаемый цикл: сначала caller-side запрос, затем более позднее worker-side разрешение, с читаемыми signer-, method- и log-доказательствами для обеих транзакций. ### 2. Читайте worker-receipts только тогда, когда уже важен finish-путь @@ -116,23 +93,12 @@ curl -sS "$TX_BASE_URL/v0/account" \ }' ``` -Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это уже surface, который превращает их в читаемое доказательство. - -## Граница сценария - -Этот сценарий намеренно остаётся в рамках публичных chain-данных, которые FastNear и RPC умеют показывать напрямую: - -- caller-side транзакция запроса -- более поздняя worker-side транзакция разрешения -- finish-receipts и логи - -Он **не** пытается доказывать внутреннюю TEE-модель OutLayer, использование same-account `yield/resume` или путь доверия через CKD/MPC только по публичной трассе транзакций. Это отдельные архитектурные вопросы, и читать их нужно в документации OutLayer, а не считать доказанными этим trace-сценарием. +Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это surface, который превращает их в читаемое доказательство. ## Полезные связанные страницы - [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) - [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) - [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) -- [RPC: view_account](https://docs.fastnear.com/ru/rpc/account/view-account) - [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) - [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) diff --git a/static/ru/tx/examples/outlayer/index.md b/static/ru/tx/examples/outlayer/index.md index 4c7ea07..fa8cee9 100644 --- a/static/ru/tx/examples/outlayer/index.md +++ b/static/ru/tx/examples/outlayer/index.md @@ -1,10 +1,12 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) -{/* FASTNEAR_AI_DISCOVERY: Этот пример остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} +{/* FASTNEAR_AI_DISCOVERY: Этот case study остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} -# OutLayer: трассировка запроса и разрешения воркером +# OutLayer Case Study: трассировка запроса и разрешения воркером -Используйте этот сценарий, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» +Используйте этот case study, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» + +Эта страница намеренно остаётся только в пределах публичных chain-данных. Она показывает caller-транзакцию, более позднюю worker-транзакцию и finish-receipts. Она не пытается доказывать внутреннюю TEE-, `yield/resume`- или CKD/MPC-архитектуру OutLayer только по chain-trace. Сначала смотрите на это как на задачу по истории транзакций: @@ -12,24 +14,6 @@ - одна более поздняя worker-side транзакция `submit_execution_output_and_resolve` или `resolve_execution` - переход к receipts только тогда, когда уже важен путь завершения - Стратегия - Сначала найдите два хеша, затем раскройте их, а к worker-receipts переходите только тогда, когда нужен finish-путь. - - 01POST /v0/account по outlayer.* — самый быстрый surface для поиска хешей. - 02POST /v0/transactions превращает caller-хеш и worker-хеш в читаемые signer, method и log-доказательства. - 03Разбирайте receipts worker-транзакции только тогда, когда реальный вопрос уже касается callback, списания или возврата средств. - -**Цель** - -- Восстановить одну caller-side транзакцию, одну worker-side транзакцию и завершающие receipts, которые относятся к одному и тому же запросу OutLayer. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Поиск хешей | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Забираем недавние хеши транзакций для `outlayer.near` | Даёт самый быстрый локальный по контракту surface, когда пара хешей ещё не известна | -| Раскрытие транзакций | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Раскрываем caller-side и worker-side хеши вместе | Превращает сырые хеши в signer-, receiver-, action- и log-доказательства | -| Разбор finish-пути | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем worker-хеш и читаем список его receipts | Показывает, где материализовались callback, списание и возврат средств | -| Необязательная проверка идентичности | RPC [`view_account`](https://docs.fastnear.com/ru/rpc/account/view-account) | Идём в RPC только если следующий вопрос уже про идентичность контракта, а не про историю транзакций | Держит проверку текущего состояния отдельно от трассировки истории | - ## Проверенный shell-сценарий Эта пара работала 18 апреля 2026 года: @@ -65,14 +49,7 @@ jq '{ }' /tmp/outlayer-pair.json ``` -Что это доказывает: - -- запросная транзакция шла от `solarflux.near` к `outlayer.near` -- в логах запроса фигурировал проект `zavodil.near/near-email` -- worker-транзакция позже пришла от `worker.outlayer.near` к `outlayer.near` -- в логах воркера были `Stored pending output` и `Resolving execution ...` - -Это и есть главный наблюдаемый цикл в терминах NEAR: сначала caller-side запрос, затем более позднее worker-side разрешение. +Это и есть главный наблюдаемый цикл: сначала caller-side запрос, затем более позднее worker-side разрешение, с читаемыми signer-, method- и log-доказательствами для обеих транзакций. ### 2. Читайте worker-receipts только тогда, когда уже важен finish-путь @@ -116,23 +93,12 @@ curl -sS "$TX_BASE_URL/v0/account" \ }' ``` -Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это уже surface, который превращает их в читаемое доказательство. - -## Граница сценария - -Этот сценарий намеренно остаётся в рамках публичных chain-данных, которые FastNear и RPC умеют показывать напрямую: - -- caller-side транзакция запроса -- более поздняя worker-side транзакция разрешения -- finish-receipts и логи - -Он **не** пытается доказывать внутреннюю TEE-модель OutLayer, использование same-account `yield/resume` или путь доверия через CKD/MPC только по публичной трассе транзакций. Это отдельные архитектурные вопросы, и читать их нужно в документации OutLayer, а не считать доказанными этим trace-сценарием. +Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это surface, который превращает их в читаемое доказательство. ## Полезные связанные страницы - [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) - [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) - [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) -- [RPC: view_account](https://docs.fastnear.com/ru/rpc/account/view-account) - [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) - [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) diff --git a/static/ru/tx/socialdb-proofs.md b/static/ru/tx/socialdb-proofs.md new file mode 100644 index 0000000..4f654dd --- /dev/null +++ b/static/ru/tx/socialdb-proofs.md @@ -0,0 +1,191 @@ +**Источник:** [https://docs.fastnear.com/ru/tx/socialdb-proofs](https://docs.fastnear.com/ru/tx/socialdb-proofs) + +# Расширенный паттерн provenance для SocialDB + +Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому provenance. + +Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" + +## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` + +Используйте этот сценарий, когда читаемый факт уже звучит как "текущее `profile.name` равно `Mike Purvis`", а остаётся только вопрос, какая запись сделала это поле истинным. + +Здесь достаточно сохранить один важный нюанс SocialDB: для исторического provenance правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. + +Для этого живого якоря: + +- текущее `profile.name`: `Mike Purvis` +- блок записи SocialDB на уровне поля: `78675795` +- receipt ID: `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN` +- хеш исходной транзакции: `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ` +- внешний блок транзакции: `78675794` + +### Shell-сценарий + +1. Прочитайте поле из NEAR Social и сохраните block записи на уровне поля. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +PROFILE_FIELD=profile/name + +PROFILE_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-profile-name.json \ + | jq -r --arg account_id "$ACCOUNT_ID" \ + '.[ $account_id ].profile.name[":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + current_name: .[$account_id].profile.name[""], + field_block_height: .[$account_id].profile.name[":block"], + parent_profile_block_height: .[$account_id].profile[":block"] +}' /tmp/mike-profile-name.json +``` + +2. Переиспользуйте этот block уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. + +```bash +PROFILE_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-profile-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + profile_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-profile-block.json +``` + +3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-profile-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), + description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), + tags: ( + .args + | @base64d + | fromjson + | .data["mike.near"].profile.tags + | keys + ) + } + ) +}' /tmp/mike-profile-transaction.json +``` + +4. Завершите каноническим подтверждением текущего состояния через raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-profile-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + finality: "final", + current_name: ( + .result.result + | implode + | fromjson + | .[$account_id].profile.name + ) +}' /tmp/mike-profile-rpc.json +``` + +Это и есть весь provenance-паттерн: читаемое значение, block уровня поля, мост через receipt, payload транзакции и затем необязательное подтверждение текущего состояния. + +## Тот же паттерн для других ключей SocialDB + +Используйте тот же мост каждый раз, когда у вас уже есть читаемое значение SocialDB и его write-block: + +1. Прочитайте семантическое значение и `:block` из NEAR Social или начните с уже известного блока записи виджета. +2. Используйте Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block), чтобы восстановить receipt `*.near -> social.near` и хеш транзакции. +3. Используйте Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), чтобы декодировать payload `social.near set`. +4. Используйте RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) только если всё ещё нужно каноническое подтверждение текущего состояния. + +### Вариант для связи подписки + +Тот же паттерн работает для читаемой связи подписки: + +- текущая связь: `mike.near -> mob.near` +- блок записи SocialDB: `79574924` +- receipt ID: `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th` +- хеш исходной транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` + +Главное отличие от примера с профилем только в ключе и форме payload: читайте `mike.near/graph/follow/mob.near`, а затем декодируйте и `graph.follow`, и соответствующую запись `index.graph` из payload записи. + +### Вариант для исходника виджета + +Тот же паттерн снова работает, когда читаемый факт относится к ключу с исходником виджета: + +- ключ виджета: `mob.near/widget/Profile` +- блок записи SocialDB: `86494825` +- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` +- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` + +Главное отличие здесь в том, что вы обычно стартуете с уже известного блока записи виджета, а затем декодируете `.data["mob.near"].widget.Profile[""]` из исходного payload `social.near set`. diff --git a/static/ru/tx/socialdb-proofs/index.md b/static/ru/tx/socialdb-proofs/index.md new file mode 100644 index 0000000..4f654dd --- /dev/null +++ b/static/ru/tx/socialdb-proofs/index.md @@ -0,0 +1,191 @@ +**Источник:** [https://docs.fastnear.com/ru/tx/socialdb-proofs](https://docs.fastnear.com/ru/tx/socialdb-proofs) + +# Расширенный паттерн provenance для SocialDB + +Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому provenance. + +Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" + +## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` + +Используйте этот сценарий, когда читаемый факт уже звучит как "текущее `profile.name` равно `Mike Purvis`", а остаётся только вопрос, какая запись сделала это поле истинным. + +Здесь достаточно сохранить один важный нюанс SocialDB: для исторического provenance правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. + +Для этого живого якоря: + +- текущее `profile.name`: `Mike Purvis` +- блок записи SocialDB на уровне поля: `78675795` +- receipt ID: `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN` +- хеш исходной транзакции: `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ` +- внешний блок транзакции: `78675794` + +### Shell-сценарий + +1. Прочитайте поле из NEAR Social и сохраните block записи на уровне поля. + +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +PROFILE_FIELD=profile/name + +PROFILE_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-profile-name.json \ + | jq -r --arg account_id "$ACCOUNT_ID" \ + '.[ $account_id ].profile.name[":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + current_name: .[$account_id].profile.name[""], + field_block_height: .[$account_id].profile.name[":block"], + parent_profile_block_height: .[$account_id].profile[":block"] +}' /tmp/mike-profile-name.json +``` + +2. Переиспользуйте этот block уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. + +```bash +PROFILE_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-profile-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + profile_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-profile-block.json +``` + +3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-profile-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), + description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), + tags: ( + .args + | @base64d + | fromjson + | .data["mike.near"].profile.tags + | keys + ) + } + ) +}' /tmp/mike-profile-transaction.json +``` + +4. Завершите каноническим подтверждением текущего состояния через raw RPC. + +```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-profile-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + finality: "final", + current_name: ( + .result.result + | implode + | fromjson + | .[$account_id].profile.name + ) +}' /tmp/mike-profile-rpc.json +``` + +Это и есть весь provenance-паттерн: читаемое значение, block уровня поля, мост через receipt, payload транзакции и затем необязательное подтверждение текущего состояния. + +## Тот же паттерн для других ключей SocialDB + +Используйте тот же мост каждый раз, когда у вас уже есть читаемое значение SocialDB и его write-block: + +1. Прочитайте семантическое значение и `:block` из NEAR Social или начните с уже известного блока записи виджета. +2. Используйте Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block), чтобы восстановить receipt `*.near -> social.near` и хеш транзакции. +3. Используйте Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), чтобы декодировать payload `social.near set`. +4. Используйте RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) только если всё ещё нужно каноническое подтверждение текущего состояния. + +### Вариант для связи подписки + +Тот же паттерн работает для читаемой связи подписки: + +- текущая связь: `mike.near -> mob.near` +- блок записи SocialDB: `79574924` +- receipt ID: `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th` +- хеш исходной транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` + +Главное отличие от примера с профилем только в ключе и форме payload: читайте `mike.near/graph/follow/mob.near`, а затем декодируйте и `graph.follow`, и соответствующую запись `index.graph` из payload записи. + +### Вариант для исходника виджета + +Тот же паттерн снова работает, когда читаемый факт относится к ключу с исходником виджета: + +- ключ виджета: `mob.near/widget/Profile` +- блок записи SocialDB: `86494825` +- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` +- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` + +Главное отличие здесь в том, что вы обычно стартуете с уже известного блока записи виджета, а затем декодируете `.data["mob.near"].widget.Profile[""]` из исходного payload `social.near set`. From 19e7896eab5d6c6437b6dc8739e409340890a2a0 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sun, 19 Apr 2026 10:07:38 -0700 Subject: [PATCH 16/35] docs: add rainbow bridge rpc example --- docs/fastdata/kv/examples.md | 8 +- docs/rpc/examples.md | 193 ++++++++++++++++++ .../current/fastdata/kv/examples.md | 8 +- .../current/rpc/examples.md | 193 ++++++++++++++++++ static/ru/fastdata/kv/examples.md | 8 +- static/ru/fastdata/kv/examples/index.md | 8 +- static/ru/llms-full.txt | 193 +++++++++++++++++- static/ru/rpc/examples.md | 185 +++++++++++++++++ static/ru/rpc/examples/index.md | 185 +++++++++++++++++ 9 files changed, 961 insertions(+), 20 deletions(-) diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index c067a95..8b08454 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -43,9 +43,9 @@ This is the shortest useful FastData read on the page: one request, two exact ro ## Worked investigation -### Read one indexed setting, then trace it back to the write +### Read one indexed setting and inspect its history -Use this investigation when you already know the contract and predecessor, and the question is: “what is the current indexed setting value, did it change before, and which transaction created it?” +Use this investigation when you already know the contract and predecessor, and the question is: “what is the current indexed setting value, and did it change before?”
@@ -55,13 +55,13 @@ Use this investigation when you already know the contract and predecessor, and t

01multi or get-latest-key reads the exact indexed setting rows.

02get-history-key shows whether the indexed setting changed again later.

-

03latest-by-predecessor with metadata plus POST /v0/transactions proves which write created those indexed rows.

+

03Only if provenance matters, latest-by-predecessor with metadata plus POST /v0/transactions proves which write created those indexed rows.

**Goal** -- Read one stable indexed setting from a minimal public testnet contract, confirm the exact-key history for one row, and recover the transaction that created both rows. +- Read one stable indexed setting from a minimal public testnet contract and confirm the exact-key history for one row. | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index c075b7e..700b21c 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -536,6 +536,199 @@ jq -n \ Use `view_state` when you already know the exact storage key family and want raw bytes. Use `call_function` when you want the contract’s public read API. If the next question becomes historical instead of “what is it right now?”, widen into [KV FastData API](/fastdata/kv). +### Which Rainbow Bridge ERC-20 tokens exist on NEAR, and how much of one token is out there? + +Use this when you want to discover Rainbow Bridge ERC-20 contracts and inspect one token's live supply on NEAR. Rainbow Bridge deploys one NEAR contract per bridged ERC-20 token, and `factory.bridge.near` lists them. + +
+
+ Strategy +

One factory read lists the token contracts. Two small view calls on one token tell you what it is and how much is on NEAR right now.

+
+
+

01RPC call_function get_tokens_accounts on factory.bridge.near returns the deployed bridged token contracts.

+

02RPC call_function ft_metadata on one bridged token contract returns its name, symbol, and decimals.

+

03RPC call_function ft_total_supply on the same contract returns the current raw supply on NEAR.

+
+
+ +**What you're doing** + +- Ask the bridge factory for every bridged token contract it has created. +- Pick one bridged token contract and read its metadata. +- Read the same contract's total supply and convert it to human units using `decimals`. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export FACTORY_ID=factory.bridge.near +export TOKENS_FILE=/tmp/rainbow-bridge-tokens.json +``` + +1. List the bridged token contracts. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$FACTORY_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_tokens_accounts", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee "$TOKENS_FILE" >/dev/null + +jq -r '.result.result | implode | fromjson | .[]' "$TOKENS_FILE" +``` + +Each line is one bridged FT contract on NEAR in the form `.factory.bridge.near`. For example, bridged ERC-20 USDT on Ethereum address `0xdAC17F958D2ee523a2206206994597C13D831ec7` appears as `dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near`. + +2. Read metadata for one token contract. + +```bash +export TOKEN_ID=dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_metadata", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/rainbow-bridge-token-metadata.json >/dev/null + +jq '.result.result | implode | fromjson | {name, symbol, decimals}' /tmp/rainbow-bridge-token-metadata.json +``` + +3. Read the current total supply on NEAR and convert it to human units. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_total_supply", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/rainbow-bridge-token-supply.json >/dev/null + +RAW_SUPPLY="$( + jq -r '.result.result | implode | fromjson' /tmp/rainbow-bridge-token-supply.json +)" + +DECIMALS="$( + jq -r '.result.result | implode | fromjson | .decimals' /tmp/rainbow-bridge-token-metadata.json +)" + +HUMAN_SUPPLY="$( + python3 - "$RAW_SUPPLY" "$DECIMALS" <<'PY' +from decimal import Decimal +import sys + +raw = Decimal(sys.argv[1]) +decimals = int(sys.argv[2]) +human = raw / (Decimal(10) ** decimals) +print(human) +PY +)" + +jq -n \ + --arg token_id "$TOKEN_ID" \ + --arg raw_supply "$RAW_SUPPLY" \ + --argjson decimals "$DECIMALS" \ + --arg human_supply "$HUMAN_SUPPLY" '{ + token_id: $token_id, + raw_supply: $raw_supply, + decimals: $decimals, + human_supply: $human_supply + }' +``` + +The `ft_total_supply` result is in the token's smallest units. Use the `decimals` from `ft_metadata` to convert it into a human-readable supply. + +#### Optional extension: print the first few bridged tokens with metadata and supply + +Use this when you want a quick sample inventory without leaving RPC. + +```bash +export TOKEN_SAMPLE_COUNT=5 + +python3 <<'PY' +import json +import os +from decimal import Decimal + +TOKENS_FILE = os.environ["TOKENS_FILE"] +LIMIT = int(os.environ.get("TOKEN_SAMPLE_COUNT", "5")) +RPC_URL = os.environ["RPC_URL"] + +def decode_result(result): + return json.loads("".join(chr(b) for b in result)) + +with open(TOKENS_FILE) as fh: + token_ids = decode_result(json.load(fh)["result"]["result"])[:LIMIT] + +def rpc_call(account_id, method_name): + payload = { + "jsonrpc": "2.0", + "id": "fastnear", + "method": "query", + "params": { + "request_type": "call_function", + "account_id": account_id, + "method_name": method_name, + "args_base64": "e30=", + "finality": "final", + }, + } + import subprocess + raw = subprocess.check_output([ + "curl", "-s", RPC_URL, + "-H", "content-type: application/json", + "--data", json.dumps(payload), + ], text=True) + return decode_result(json.loads(raw)["result"]["result"]) + +print(f"{'token_id':<56} {'symbol':<12} {'decimals':>8} {'raw_supply':>24} {'human_supply':>24} name") +for token_id in token_ids: + metadata = rpc_call(token_id, "ft_metadata") + raw_supply = rpc_call(token_id, "ft_total_supply") + human_supply = Decimal(raw_supply) / (Decimal(10) ** metadata["decimals"]) + print( + f"{token_id:<56} " + f"{metadata['symbol']:<12} " + f"{metadata['decimals']:>8} " + f"{raw_supply:>24} " + f"{str(human_supply):>24} " + f"{metadata['name']}" + ) +PY +``` + +**Why this next step?** + +Stay on RPC while the question is “what bridged token contracts exist and how much of one token is out there?” The factory is the source of truth for the bridged token set, and each token contract answers its own metadata and supply through standard NEP-141 view methods. If the next question becomes “who holds this token?”, move to [V1 FT Top Holders](/api/v1/ft-top) instead of trying to walk holders through RPC. + ## SocialDB Exact Reads Stay on exact `call_function get` reads when you already know the SocialDB key you want. On standard RPC, raw `view_state` against `social.near` is not a practical teaching path because the contract state is too large to scan directly. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index 416b756..4e4ea87 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -43,9 +43,9 @@ curl -s "$KV_BASE_URL/v0/multi" \ ## Готовое расследование -### Прочитать одну индексированную настройку, а затем привязать её к записи +### Прочитать одну индексированную настройку и посмотреть её историю -Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки, менялось ли оно раньше и какая транзакция его создала?» +Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки и менялось ли оно раньше?»
@@ -55,13 +55,13 @@ curl -s "$KV_BASE_URL/v0/multi" \

01multi или get-latest-key читают точные индексированные строки настройки.

02get-history-key показывает, менялось ли это индексированное значение позже.

-

03latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки.

+

03Только если важна provenance-цепочка, latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки.

**Цель** -- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта, подтвердить историю точного ключа для одной строки и восстановить транзакцию, которая создала обе строки. +- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта и подтвердить историю точного ключа для одной строки. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index dec3319..66977e2 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -536,6 +536,199 @@ jq -n \ Используйте `view_state`, когда вы уже знаете точное семейство storage-ключей и хотите raw bytes. Используйте `call_function`, когда вам нужен публичный метод чтения самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда уже стоит расширяться в [KV FastData API](/fastdata/kv). +### Какие ERC-20 токены из Rainbow Bridge существуют на NEAR и сколько одного такого токена сейчас в обращении? + +Используйте этот сценарий, когда хотите найти Rainbow Bridge ERC-20 контракты и посмотреть живой объём одного токена на NEAR. Rainbow Bridge развёртывает по одному NEAR-контракту на каждый bridged ERC-20 токен, а `factory.bridge.near` их перечисляет. + +
+
+ Стратегия +

Одно чтение factory перечисляет token-контракты. Ещё два небольших view-вызова по одному токену показывают, что это за токен и сколько его сейчас на NEAR.

+
+
+

01RPC call_function get_tokens_accounts по factory.bridge.near возвращает развёрнутые bridged token-контракты.

+

02Следующий RPC call_function по одному bridged token-контракту возвращает метаданные токена: имя, тикер и число десятичных знаков.

+

03Ещё один RPC call_function по тому же контракту возвращает текущее сырое значение объёма в обращении на NEAR.

+
+
+ +**Что вы делаете** + +- Спрашиваете у bridge factory обо всех bridged token-контрактах, которые она создала. +- Выбираете один bridged token-контракт и читаете его метаданные. +- Читаете total supply того же контракта и переводите его в человеческие единицы через `decimals`. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export FACTORY_ID=factory.bridge.near +export TOKENS_FILE=/tmp/rainbow-bridge-tokens.json +``` + +1. Получите список bridged token-контрактов. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$FACTORY_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_tokens_accounts", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee "$TOKENS_FILE" >/dev/null + +jq -r '.result.result | implode | fromjson | .[]' "$TOKENS_FILE" +``` + +Каждая строка — это один bridged FT-контракт на NEAR в форме `.factory.bridge.near`. Например, bridged ERC-20 USDT с Ethereum-адреса `0xdAC17F958D2ee523a2206206994597C13D831ec7` появляется как `dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near`. + +2. Прочитайте метаданные одного токен-контракта. + +```bash +export TOKEN_ID=dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_metadata", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/rainbow-bridge-token-metadata.json >/dev/null + +jq '.result.result | implode | fromjson | {name, symbol, decimals}' /tmp/rainbow-bridge-token-metadata.json +``` + +3. Прочитайте текущий total supply на NEAR и переведите его в человеческие единицы. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_total_supply", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/rainbow-bridge-token-supply.json >/dev/null + +RAW_SUPPLY="$( + jq -r '.result.result | implode | fromjson' /tmp/rainbow-bridge-token-supply.json +)" + +DECIMALS="$( + jq -r '.result.result | implode | fromjson | .decimals' /tmp/rainbow-bridge-token-metadata.json +)" + +HUMAN_SUPPLY="$( + python3 - "$RAW_SUPPLY" "$DECIMALS" <<'PY' +from decimal import Decimal +import sys + +raw = Decimal(sys.argv[1]) +decimals = int(sys.argv[2]) +human = raw / (Decimal(10) ** decimals) +print(human) +PY +)" + +jq -n \ + --arg token_id "$TOKEN_ID" \ + --arg raw_supply "$RAW_SUPPLY" \ + --argjson decimals "$DECIMALS" \ + --arg human_supply "$HUMAN_SUPPLY" '{ + token_id: $token_id, + raw_supply: $raw_supply, + decimals: $decimals, + human_supply: $human_supply + }' +``` + +Результат `ft_total_supply` приходит в минимальных единицах токена. Используйте `decimals` из ответа предыдущего шага, чтобы перевести его в человекочитаемый объём в обращении. + +#### Необязательное расширение: показать первые несколько bridged token-ов с метаданными и объёмом в обращении + +Используйте это расширение, когда нужен быстрый sample-инвентарь и вы всё ещё хотите оставаться в RPC. + +```bash +export TOKEN_SAMPLE_COUNT=5 + +python3 <<'PY' +import json +import os +from decimal import Decimal + +TOKENS_FILE = os.environ["TOKENS_FILE"] +LIMIT = int(os.environ.get("TOKEN_SAMPLE_COUNT", "5")) +RPC_URL = os.environ["RPC_URL"] + +def decode_result(result): + return json.loads("".join(chr(b) for b in result)) + +with open(TOKENS_FILE) as fh: + token_ids = decode_result(json.load(fh)["result"]["result"])[:LIMIT] + +def rpc_call(account_id, method_name): + payload = { + "jsonrpc": "2.0", + "id": "fastnear", + "method": "query", + "params": { + "request_type": "call_function", + "account_id": account_id, + "method_name": method_name, + "args_base64": "e30=", + "finality": "final", + }, + } + import subprocess + raw = subprocess.check_output([ + "curl", "-s", RPC_URL, + "-H", "content-type: application/json", + "--data", json.dumps(payload), + ], text=True) + return decode_result(json.loads(raw)["result"]["result"]) + +print(f"{'token_id':<56} {'symbol':<12} {'decimals':>8} {'raw_supply':>24} {'human_supply':>24} name") +for token_id in token_ids: + metadata = rpc_call(token_id, "ft_metadata") + raw_supply = rpc_call(token_id, "ft_total_supply") + human_supply = Decimal(raw_supply) / (Decimal(10) ** metadata["decimals"]) + print( + f"{token_id:<56} " + f"{metadata['symbol']:<12} " + f"{metadata['decimals']:>8} " + f"{raw_supply:>24} " + f"{str(human_supply):>24} " + f"{metadata['name']}" + ) +PY +``` + +**Зачем нужен следующий шаг?** + +Оставайтесь в RPC, пока вопрос звучит как «какие bridged token-контракты существуют и сколько одного такого токена сейчас в обращении?» Factory — это источник истины для множества bridged token-ов, а каждый token-контракт сам отвечает за свои метаданные и объём в обращении через стандартные NEP-141 view-методы. Если следующий вопрос становится «кто держит этот токен?», переключайтесь на [V1 FT Top Holders](/api/v1/ft-top), а не пытайтесь обходить holders через RPC. + ## Точные чтения SocialDB Оставайтесь на точных чтениях через `call_function get`, когда вы уже знаете нужный ключ SocialDB. На обычном RPC raw `view_state` для `social.near` не подходит как обучающий путь, потому что состояние контракта слишком велико для прямого чтения. diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index b201047..0750ea7 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -35,20 +35,20 @@ curl -s "$KV_BASE_URL/v0/multi" \ ## Готовое расследование -### Прочитать одну индексированную настройку, а затем привязать её к записи +### Прочитать одну индексированную настройку и посмотреть её историю -Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки, менялось ли оно раньше и какая транзакция его создала?» +Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки и менялось ли оно раньше?» Стратегия Сначала читайте точные строки настройки, расширяйтесь до метаданных предшественника только если нужна provenance-цепочка, и переходите к Transactions API только для финального доказательства. 01multi или get-latest-key читают точные индексированные строки настройки. 02get-history-key показывает, менялось ли это индексированное значение позже. - 03latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки. + 03Только если важна provenance-цепочка, latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки. **Цель** -- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта, подтвердить историю точного ключа для одной строки и восстановить транзакцию, которая создала обе строки. +- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта и подтвердить историю точного ключа для одной строки. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index b201047..0750ea7 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -35,20 +35,20 @@ curl -s "$KV_BASE_URL/v0/multi" \ ## Готовое расследование -### Прочитать одну индексированную настройку, а затем привязать её к записи +### Прочитать одну индексированную настройку и посмотреть её историю -Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки, менялось ли оно раньше и какая транзакция его создала?» +Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки и менялось ли оно раньше?» Стратегия Сначала читайте точные строки настройки, расширяйтесь до метаданных предшественника только если нужна provenance-цепочка, и переходите к Transactions API только для финального доказательства. 01multi или get-latest-key читают точные индексированные строки настройки. 02get-history-key показывает, менялось ли это индексированное значение позже. - 03latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки. + 03Только если важна provenance-цепочка, latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки. **Цель** -- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта, подтвердить историю точного ключа для одной строки и восстановить транзакцию, которая создала обе строки. +- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта и подтвердить историю точного ключа для одной строки. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 85ae2ca..7d88629 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -1481,20 +1481,20 @@ curl -s "$KV_BASE_URL/v0/multi" \ ## Готовое расследование -### Прочитать одну индексированную настройку, а затем привязать её к записи +### Прочитать одну индексированную настройку и посмотреть её историю -Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки, менялось ли оно раньше и какая транзакция его создала?» +Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки и менялось ли оно раньше?» Стратегия Сначала читайте точные строки настройки, расширяйтесь до метаданных предшественника только если нужна provenance-цепочка, и переходите к Transactions API только для финального доказательства. 01multi или get-latest-key читают точные индексированные строки настройки. 02get-history-key показывает, менялось ли это индексированное значение позже. - 03latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки. + 03Только если важна provenance-цепочка, latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки. **Цель** -- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта, подтвердить историю точного ключа для одной строки и восстановить транзакцию, которая создала обе строки. +- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта и подтвердить историю точного ключа для одной строки. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | @@ -2898,6 +2898,191 @@ jq -n \ Используйте `view_state`, когда вы уже знаете точное семейство storage-ключей и хотите raw bytes. Используйте `call_function`, когда вам нужен публичный метод чтения самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда уже стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). +### Какие ERC-20 токены из Rainbow Bridge существуют на NEAR и сколько одного такого токена сейчас в обращении? + +Используйте этот сценарий, когда хотите найти Rainbow Bridge ERC-20 контракты и посмотреть живой объём одного токена на NEAR. Rainbow Bridge развёртывает по одному NEAR-контракту на каждый bridged ERC-20 токен, а `factory.bridge.near` их перечисляет. + + Стратегия + Одно чтение factory перечисляет token-контракты. Ещё два небольших view-вызова по одному токену показывают, что это за токен и сколько его сейчас на NEAR. + + 01RPC call_function get_tokens_accounts по factory.bridge.near возвращает развёрнутые bridged token-контракты. + 02Следующий RPC call_function по одному bridged token-контракту возвращает метаданные токена: имя, тикер и число десятичных знаков. + 03Ещё один RPC call_function по тому же контракту возвращает текущее сырое значение объёма в обращении на NEAR. + +**Что вы делаете** + +- Спрашиваете у bridge factory обо всех bridged token-контрактах, которые она создала. +- Выбираете один bridged token-контракт и читаете его метаданные. +- Читаете total supply того же контракта и переводите его в человеческие единицы через `decimals`. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export FACTORY_ID=factory.bridge.near +export TOKENS_FILE=/tmp/rainbow-bridge-tokens.json +``` + +1. Получите список bridged token-контрактов. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$FACTORY_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_tokens_accounts", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee "$TOKENS_FILE" >/dev/null + +jq -r '.result.result | implode | fromjson | .[]' "$TOKENS_FILE" +``` + +Каждая строка — это один bridged FT-контракт на NEAR в форме `.factory.bridge.near`. Например, bridged ERC-20 USDT с Ethereum-адреса `0xdAC17F958D2ee523a2206206994597C13D831ec7` появляется как `dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near`. + +2. Прочитайте метаданные одного токен-контракта. + +```bash +export TOKEN_ID=dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_metadata", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/rainbow-bridge-token-metadata.json >/dev/null + +jq '.result.result | implode | fromjson | {name, symbol, decimals}' /tmp/rainbow-bridge-token-metadata.json +``` + +3. Прочитайте текущий total supply на NEAR и переведите его в человеческие единицы. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_total_supply", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/rainbow-bridge-token-supply.json >/dev/null + +RAW_SUPPLY="$( + jq -r '.result.result | implode | fromjson' /tmp/rainbow-bridge-token-supply.json +)" + +DECIMALS="$( + jq -r '.result.result | implode | fromjson | .decimals' /tmp/rainbow-bridge-token-metadata.json +)" + +HUMAN_SUPPLY="$( + python3 - "$RAW_SUPPLY" "$DECIMALS" <<'PY' +from decimal import Decimal + +raw = Decimal(sys.argv[1]) +decimals = int(sys.argv[2]) +human = raw / (Decimal(10) ** decimals) +print(human) +PY +)" + +jq -n \ + --arg token_id "$TOKEN_ID" \ + --arg raw_supply "$RAW_SUPPLY" \ + --argjson decimals "$DECIMALS" \ + --arg human_supply "$HUMAN_SUPPLY" '{ + token_id: $token_id, + raw_supply: $raw_supply, + decimals: $decimals, + human_supply: $human_supply + }' +``` + +Результат `ft_total_supply` приходит в минимальных единицах токена. Используйте `decimals` из ответа предыдущего шага, чтобы перевести его в человекочитаемый объём в обращении. + +#### Необязательное расширение: показать первые несколько bridged token-ов с метаданными и объёмом в обращении + +Используйте это расширение, когда нужен быстрый sample-инвентарь и вы всё ещё хотите оставаться в RPC. + +```bash +export TOKEN_SAMPLE_COUNT=5 + +python3 <<'PY' +from decimal import Decimal + +TOKENS_FILE = os.environ["TOKENS_FILE"] +LIMIT = int(os.environ.get("TOKEN_SAMPLE_COUNT", "5")) +RPC_URL = os.environ["RPC_URL"] + +def decode_result(result): + return json.loads("".join(chr(b) for b in result)) + +with open(TOKENS_FILE) as fh: + token_ids = decode_result(json.load(fh)["result"]["result"])[:LIMIT] + +def rpc_call(account_id, method_name): + payload = { + "jsonrpc": "2.0", + "id": "fastnear", + "method": "query", + "params": { + "request_type": "call_function", + "account_id": account_id, + "method_name": method_name, + "args_base64": "e30=", + "finality": "final", + }, + } + import subprocess + raw = subprocess.check_output([ + "curl", "-s", RPC_URL, + "-H", "content-type: application/json", + "--data", json.dumps(payload), + ], text=True) + return decode_result(json.loads(raw)["result"]["result"]) + +print(f"{'token_id':<56} {'symbol':<12} {'decimals':>8} {'raw_supply':>24} {'human_supply':>24} name") +for token_id in token_ids: + metadata = rpc_call(token_id, "ft_metadata") + raw_supply = rpc_call(token_id, "ft_total_supply") + human_supply = Decimal(raw_supply) / (Decimal(10) ** metadata["decimals"]) + print( + f"{token_id:<56} " + f"{metadata['symbol']:<12} " + f"{metadata['decimals']:>8} " + f"{raw_supply:>24} " + f"{str(human_supply):>24} " + f"{metadata['name']}" + ) +PY +``` + +**Зачем нужен следующий шаг?** + +Оставайтесь в RPC, пока вопрос звучит как «какие bridged token-контракты существуют и сколько одного такого токена сейчас в обращении?» Factory — это источник истины для множества bridged token-ов, а каждый token-контракт сам отвечает за свои метаданные и объём в обращении через стандартные NEP-141 view-методы. Если следующий вопрос становится «кто держит этот токен?», переключайтесь на [V1 FT Top Holders](https://docs.fastnear.com/ru/api/v1/ft-top), а не пытайтесь обходить holders через RPC. + ## Точные чтения SocialDB Оставайтесь на точных чтениях через `call_function get`, когда вы уже знаете нужный ключ SocialDB. На обычном RPC raw `view_state` для `social.near` не подходит как обучающий путь, потому что состояние контракта слишком велико для прямого чтения. diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index 9364c2f..1808b0e 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -514,6 +514,191 @@ jq -n \ Используйте `view_state`, когда вы уже знаете точное семейство storage-ключей и хотите raw bytes. Используйте `call_function`, когда вам нужен публичный метод чтения самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда уже стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). +### Какие ERC-20 токены из Rainbow Bridge существуют на NEAR и сколько одного такого токена сейчас в обращении? + +Используйте этот сценарий, когда хотите найти Rainbow Bridge ERC-20 контракты и посмотреть живой объём одного токена на NEAR. Rainbow Bridge развёртывает по одному NEAR-контракту на каждый bridged ERC-20 токен, а `factory.bridge.near` их перечисляет. + + Стратегия + Одно чтение factory перечисляет token-контракты. Ещё два небольших view-вызова по одному токену показывают, что это за токен и сколько его сейчас на NEAR. + + 01RPC call_function get_tokens_accounts по factory.bridge.near возвращает развёрнутые bridged token-контракты. + 02Следующий RPC call_function по одному bridged token-контракту возвращает метаданные токена: имя, тикер и число десятичных знаков. + 03Ещё один RPC call_function по тому же контракту возвращает текущее сырое значение объёма в обращении на NEAR. + +**Что вы делаете** + +- Спрашиваете у bridge factory обо всех bridged token-контрактах, которые она создала. +- Выбираете один bridged token-контракт и читаете его метаданные. +- Читаете total supply того же контракта и переводите его в человеческие единицы через `decimals`. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export FACTORY_ID=factory.bridge.near +export TOKENS_FILE=/tmp/rainbow-bridge-tokens.json +``` + +1. Получите список bridged token-контрактов. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$FACTORY_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_tokens_accounts", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee "$TOKENS_FILE" >/dev/null + +jq -r '.result.result | implode | fromjson | .[]' "$TOKENS_FILE" +``` + +Каждая строка — это один bridged FT-контракт на NEAR в форме `.factory.bridge.near`. Например, bridged ERC-20 USDT с Ethereum-адреса `0xdAC17F958D2ee523a2206206994597C13D831ec7` появляется как `dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near`. + +2. Прочитайте метаданные одного токен-контракта. + +```bash +export TOKEN_ID=dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_metadata", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/rainbow-bridge-token-metadata.json >/dev/null + +jq '.result.result | implode | fromjson | {name, symbol, decimals}' /tmp/rainbow-bridge-token-metadata.json +``` + +3. Прочитайте текущий total supply на NEAR и переведите его в человеческие единицы. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_total_supply", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/rainbow-bridge-token-supply.json >/dev/null + +RAW_SUPPLY="$( + jq -r '.result.result | implode | fromjson' /tmp/rainbow-bridge-token-supply.json +)" + +DECIMALS="$( + jq -r '.result.result | implode | fromjson | .decimals' /tmp/rainbow-bridge-token-metadata.json +)" + +HUMAN_SUPPLY="$( + python3 - "$RAW_SUPPLY" "$DECIMALS" <<'PY' +from decimal import Decimal + +raw = Decimal(sys.argv[1]) +decimals = int(sys.argv[2]) +human = raw / (Decimal(10) ** decimals) +print(human) +PY +)" + +jq -n \ + --arg token_id "$TOKEN_ID" \ + --arg raw_supply "$RAW_SUPPLY" \ + --argjson decimals "$DECIMALS" \ + --arg human_supply "$HUMAN_SUPPLY" '{ + token_id: $token_id, + raw_supply: $raw_supply, + decimals: $decimals, + human_supply: $human_supply + }' +``` + +Результат `ft_total_supply` приходит в минимальных единицах токена. Используйте `decimals` из ответа предыдущего шага, чтобы перевести его в человекочитаемый объём в обращении. + +#### Необязательное расширение: показать первые несколько bridged token-ов с метаданными и объёмом в обращении + +Используйте это расширение, когда нужен быстрый sample-инвентарь и вы всё ещё хотите оставаться в RPC. + +```bash +export TOKEN_SAMPLE_COUNT=5 + +python3 <<'PY' +from decimal import Decimal + +TOKENS_FILE = os.environ["TOKENS_FILE"] +LIMIT = int(os.environ.get("TOKEN_SAMPLE_COUNT", "5")) +RPC_URL = os.environ["RPC_URL"] + +def decode_result(result): + return json.loads("".join(chr(b) for b in result)) + +with open(TOKENS_FILE) as fh: + token_ids = decode_result(json.load(fh)["result"]["result"])[:LIMIT] + +def rpc_call(account_id, method_name): + payload = { + "jsonrpc": "2.0", + "id": "fastnear", + "method": "query", + "params": { + "request_type": "call_function", + "account_id": account_id, + "method_name": method_name, + "args_base64": "e30=", + "finality": "final", + }, + } + import subprocess + raw = subprocess.check_output([ + "curl", "-s", RPC_URL, + "-H", "content-type: application/json", + "--data", json.dumps(payload), + ], text=True) + return decode_result(json.loads(raw)["result"]["result"]) + +print(f"{'token_id':<56} {'symbol':<12} {'decimals':>8} {'raw_supply':>24} {'human_supply':>24} name") +for token_id in token_ids: + metadata = rpc_call(token_id, "ft_metadata") + raw_supply = rpc_call(token_id, "ft_total_supply") + human_supply = Decimal(raw_supply) / (Decimal(10) ** metadata["decimals"]) + print( + f"{token_id:<56} " + f"{metadata['symbol']:<12} " + f"{metadata['decimals']:>8} " + f"{raw_supply:>24} " + f"{str(human_supply):>24} " + f"{metadata['name']}" + ) +PY +``` + +**Зачем нужен следующий шаг?** + +Оставайтесь в RPC, пока вопрос звучит как «какие bridged token-контракты существуют и сколько одного такого токена сейчас в обращении?» Factory — это источник истины для множества bridged token-ов, а каждый token-контракт сам отвечает за свои метаданные и объём в обращении через стандартные NEP-141 view-методы. Если следующий вопрос становится «кто держит этот токен?», переключайтесь на [V1 FT Top Holders](https://docs.fastnear.com/ru/api/v1/ft-top), а не пытайтесь обходить holders через RPC. + ## Точные чтения SocialDB Оставайтесь на точных чтениях через `call_function get`, когда вы уже знаете нужный ключ SocialDB. На обычном RPC raw `view_state` для `social.near` не подходит как обучающий путь, потому что состояние контракта слишком велико для прямого чтения. diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index 9364c2f..1808b0e 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -514,6 +514,191 @@ jq -n \ Используйте `view_state`, когда вы уже знаете точное семейство storage-ключей и хотите raw bytes. Используйте `call_function`, когда вам нужен публичный метод чтения самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда уже стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). +### Какие ERC-20 токены из Rainbow Bridge существуют на NEAR и сколько одного такого токена сейчас в обращении? + +Используйте этот сценарий, когда хотите найти Rainbow Bridge ERC-20 контракты и посмотреть живой объём одного токена на NEAR. Rainbow Bridge развёртывает по одному NEAR-контракту на каждый bridged ERC-20 токен, а `factory.bridge.near` их перечисляет. + + Стратегия + Одно чтение factory перечисляет token-контракты. Ещё два небольших view-вызова по одному токену показывают, что это за токен и сколько его сейчас на NEAR. + + 01RPC call_function get_tokens_accounts по factory.bridge.near возвращает развёрнутые bridged token-контракты. + 02Следующий RPC call_function по одному bridged token-контракту возвращает метаданные токена: имя, тикер и число десятичных знаков. + 03Ещё один RPC call_function по тому же контракту возвращает текущее сырое значение объёма в обращении на NEAR. + +**Что вы делаете** + +- Спрашиваете у bridge factory обо всех bridged token-контрактах, которые она создала. +- Выбираете один bridged token-контракт и читаете его метаданные. +- Читаете total supply того же контракта и переводите его в человеческие единицы через `decimals`. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export FACTORY_ID=factory.bridge.near +export TOKENS_FILE=/tmp/rainbow-bridge-tokens.json +``` + +1. Получите список bridged token-контрактов. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$FACTORY_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_tokens_accounts", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee "$TOKENS_FILE" >/dev/null + +jq -r '.result.result | implode | fromjson | .[]' "$TOKENS_FILE" +``` + +Каждая строка — это один bridged FT-контракт на NEAR в форме `.factory.bridge.near`. Например, bridged ERC-20 USDT с Ethereum-адреса `0xdAC17F958D2ee523a2206206994597C13D831ec7` появляется как `dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near`. + +2. Прочитайте метаданные одного токен-контракта. + +```bash +export TOKEN_ID=dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_metadata", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/rainbow-bridge-token-metadata.json >/dev/null + +jq '.result.result | implode | fromjson | {name, symbol, decimals}' /tmp/rainbow-bridge-token-metadata.json +``` + +3. Прочитайте текущий total supply на NEAR и переведите его в человеческие единицы. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_total_supply", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/rainbow-bridge-token-supply.json >/dev/null + +RAW_SUPPLY="$( + jq -r '.result.result | implode | fromjson' /tmp/rainbow-bridge-token-supply.json +)" + +DECIMALS="$( + jq -r '.result.result | implode | fromjson | .decimals' /tmp/rainbow-bridge-token-metadata.json +)" + +HUMAN_SUPPLY="$( + python3 - "$RAW_SUPPLY" "$DECIMALS" <<'PY' +from decimal import Decimal + +raw = Decimal(sys.argv[1]) +decimals = int(sys.argv[2]) +human = raw / (Decimal(10) ** decimals) +print(human) +PY +)" + +jq -n \ + --arg token_id "$TOKEN_ID" \ + --arg raw_supply "$RAW_SUPPLY" \ + --argjson decimals "$DECIMALS" \ + --arg human_supply "$HUMAN_SUPPLY" '{ + token_id: $token_id, + raw_supply: $raw_supply, + decimals: $decimals, + human_supply: $human_supply + }' +``` + +Результат `ft_total_supply` приходит в минимальных единицах токена. Используйте `decimals` из ответа предыдущего шага, чтобы перевести его в человекочитаемый объём в обращении. + +#### Необязательное расширение: показать первые несколько bridged token-ов с метаданными и объёмом в обращении + +Используйте это расширение, когда нужен быстрый sample-инвентарь и вы всё ещё хотите оставаться в RPC. + +```bash +export TOKEN_SAMPLE_COUNT=5 + +python3 <<'PY' +from decimal import Decimal + +TOKENS_FILE = os.environ["TOKENS_FILE"] +LIMIT = int(os.environ.get("TOKEN_SAMPLE_COUNT", "5")) +RPC_URL = os.environ["RPC_URL"] + +def decode_result(result): + return json.loads("".join(chr(b) for b in result)) + +with open(TOKENS_FILE) as fh: + token_ids = decode_result(json.load(fh)["result"]["result"])[:LIMIT] + +def rpc_call(account_id, method_name): + payload = { + "jsonrpc": "2.0", + "id": "fastnear", + "method": "query", + "params": { + "request_type": "call_function", + "account_id": account_id, + "method_name": method_name, + "args_base64": "e30=", + "finality": "final", + }, + } + import subprocess + raw = subprocess.check_output([ + "curl", "-s", RPC_URL, + "-H", "content-type: application/json", + "--data", json.dumps(payload), + ], text=True) + return decode_result(json.loads(raw)["result"]["result"]) + +print(f"{'token_id':<56} {'symbol':<12} {'decimals':>8} {'raw_supply':>24} {'human_supply':>24} name") +for token_id in token_ids: + metadata = rpc_call(token_id, "ft_metadata") + raw_supply = rpc_call(token_id, "ft_total_supply") + human_supply = Decimal(raw_supply) / (Decimal(10) ** metadata["decimals"]) + print( + f"{token_id:<56} " + f"{metadata['symbol']:<12} " + f"{metadata['decimals']:>8} " + f"{raw_supply:>24} " + f"{str(human_supply):>24} " + f"{metadata['name']}" + ) +PY +``` + +**Зачем нужен следующий шаг?** + +Оставайтесь в RPC, пока вопрос звучит как «какие bridged token-контракты существуют и сколько одного такого токена сейчас в обращении?» Factory — это источник истины для множества bridged token-ов, а каждый token-контракт сам отвечает за свои метаданные и объём в обращении через стандартные NEP-141 view-методы. Если следующий вопрос становится «кто держит этот токен?», переключайтесь на [V1 FT Top Holders](https://docs.fastnear.com/ru/api/v1/ft-top), а не пытайтесь обходить holders через RPC. + ## Точные чтения SocialDB Оставайтесь на точных чтениях через `call_function get`, когда вы уже знаете нужный ключ SocialDB. На обычном RPC raw `view_state` для `social.near` не подходит как обучающий путь, потому что состояние контракта слишком велико для прямого чтения. From 457918c65a6238283e1c7c16f1c14bd9a4bf2712 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sun, 19 Apr 2026 10:56:25 -0700 Subject: [PATCH 17/35] docs: tighten examples around answer-first workflows --- docs/api/examples.md | 56 +- docs/fastdata/kv/examples.md | 2 +- docs/neardata/examples.md | 92 ++-- docs/rpc/examples.md | 139 +---- docs/transfers/examples.md | 29 +- docs/tx/berry-club.mdx | 12 +- docs/tx/examples.md | 61 +-- docs/tx/outlayer.mdx | 46 +- docs/tx/socialdb-proofs.mdx | 78 +-- .../current/api/examples.md | 56 +- .../current/fastdata/kv/examples.md | 2 +- .../current/neardata/examples.md | 92 ++-- .../current/rpc/examples.md | 139 +---- .../current/transfers/examples.md | 29 +- .../current/tx/berry-club.mdx | 12 +- .../current/tx/examples.md | 61 +-- .../current/tx/outlayer.mdx | 44 +- .../current/tx/socialdb-proofs.mdx | 78 +-- sidebars.js | 5 +- static/ru/api/examples.md | 54 +- static/ru/api/examples/index.md | 54 +- static/ru/guides/llms.txt | 18 +- static/ru/llms-full.txt | 484 +++++------------- static/ru/llms.txt | 18 +- static/ru/neardata/examples.md | 90 ++-- static/ru/neardata/examples/index.md | 90 ++-- static/ru/rpc/examples.md | 131 ----- static/ru/rpc/examples/index.md | 131 ----- static/ru/transfers/examples.md | 27 +- static/ru/transfers/examples/index.md | 27 +- static/ru/tx/examples.md | 58 +-- static/ru/tx/examples/berry-club.md | 6 +- static/ru/tx/examples/berry-club/index.md | 6 +- static/ru/tx/examples/index.md | 58 +-- static/ru/tx/examples/outlayer.md | 38 +- static/ru/tx/examples/outlayer/index.md | 38 +- static/ru/tx/socialdb-proofs.md | 74 +-- static/ru/tx/socialdb-proofs/index.md | 74 +-- 38 files changed, 706 insertions(+), 1803 deletions(-) diff --git a/docs/api/examples.md b/docs/api/examples.md index 0a42e04..848bf30 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /api/examples title: API Examples -description: Plain-language workflows for using FastNear API docs for account lookups, asset inventory, and staking classification. +description: Plain-language workflows for using FastNear API docs for account lookups, asset inventory, and direct staking checks. displayed_sidebar: fastnearApiSidebar page_actions: - markdown @@ -144,7 +144,59 @@ At the time of writing, `mike.near` returned visible direct staking pools here. **Why this next step?** -This keeps the question narrow and operational. If the answer is `true`, the next practical step is usually pool-specific unstake or withdraw work. If the answer is `false`, do not infer liquid staking from this example alone; this example is only about direct pool positions. +This keeps the question narrow and operational. If the answer is `true`, remember what that means on chain: the account usually delegated into a staking-pool contract such as `polkachu.poolv1.near` by sending a `FunctionCall` like `deposit_and_stake` with attached deposit. The pool contract later performs the actual `Stake` action on its own account. If the answer is `false`, do not infer liquid staking from this example alone; this example is only about direct pool positions. + +#### Optional follow-up: What did this contract call for delegation do? + +Use this when the staking endpoint already showed a pool like `polkachu.poolv1.near` and you want to see the transaction shape behind one real delegation. + +This pinned mainnet tx is useful because it shows the full pattern clearly: + +- transaction hash: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` +- top-level receiver: `polkachu.poolv1.near` +- top-level method: `deposit_and_stake` +- attached deposit: `34650000000000000000000000` + +The important chain shape is: + +- the delegator sends a `FunctionCall deposit_and_stake` into the pool contract +- the pool contract records the deposit and staking shares +- the pool later emits a self-receipt with a real `Stake` action + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/staking-delegation-tx.json >/dev/null + +jq '{ + top_level_call: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + attached_deposit: .transactions[0].transaction.actions[0].FunctionCall.deposit + }, + pool_side_effects: [ + .transactions[0].receipts[] + | select(.receipt.receiver_id == "polkachu.poolv1.near") + | { + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + actions: ( + .receipt.receipt.Action.actions + | map(if type == "string" then . else keys[0] end) + ), + first_logs: (.execution_outcome.outcome.logs[:3]) + } + ] +}' /tmp/staking-delegation-tx.json +``` + +The answer you want is simple: the delegator did not sign a raw `Stake` action directly. They called the staking-pool contract with `deposit_and_stake` and attached deposit, and the pool contract later executed the `Stake` action on its own account. ### What FT balances and NFT collections does this account show right now? diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index 8b08454..f4da6a6 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: KV FastData Examples -description: Plain-language workflows for reading exact FastData rows, checking exact-key history, and tracing one indexed row back to its originating transaction. +description: Plain-language workflows for reading exact FastData rows, checking exact-key history, and optionally tracing one indexed setting back to its originating transaction. displayed_sidebar: kvFastDataSidebar page_actions: - markdown diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index 12976d8..0ad5d4c 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /neardata/examples title: NEAR Data Examples -description: Plain-language workflows for checking whether a contract was touched in the latest finalized block and extracting the exact hashes worth following up. +description: Plain-language workflows for checking whether a contract was touched in the latest finalized block and extracting the exact identifiers worth following up. displayed_sidebar: nearDataApiSidebar page_actions: - markdown @@ -58,33 +58,25 @@ Use this investigation when you want a concrete yes/no answer before you widen i
Strategy -

Anchor on one finalized block, scan the whole block family for your target account, then keep only one small summary plus the identifiers worth following up.

+

Answer the contract-touch question first, then keep only one tx hash or receipt id for the next step.

01last-block-final gives you one stable block height without guessing.

02block is the main read: it already contains the transactions, receipts, receipt execution outcomes, and state changes you need to answer “touched or not?”.

-

03Only if the answer is “yes” do you widen: keep the shard ids, tx hashes, and receipt ids you discovered, then hand those exact identifiers to [Transactions API](/tx) or [RPC Reference](/rpc).

+

03Only if the answer is “yes” do you widen: keep one exact tx hash or receipt id from the same cached block, then hand that identifier to [Transactions API](/tx) or [RPC Reference](/rpc).

**Goal** -- Decide whether one target contract was touched in the latest finalized block, and keep only the shard ids, counts, and sample identifiers worth investigating next. - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Latest stable anchor | NEAR Data [`last-block-final`](/neardata/last-block-final) | Get one finalized block height without guessing | Gives you a stable starting point for the whole question | -| Whole block family | NEAR Data [`block`](/neardata/block) | Scan transactions, receipts, receipt execution outcomes, and state changes for the target account | This is the main answer surface for “was my contract touched?” | -| Light block summary | NEAR Data [`block-headers`](/neardata/block-headers) | Use when you only need the height, hash, timing, or chunk headers | Avoids the wider block payload when contract-level filtering is not needed | -| Optional shard follow-up | NEAR Data [`block-chunk`](/neardata/block-chunk) or [`block-shard`](/neardata/block-shard) | Re-open only the touched shard if you need deeper payload details | Useful after you already know which shard mattered | -| Exact follow-up surfaces | [Transactions API](/tx) or [RPC Reference](/rpc) | Reuse the discovered tx hashes or receipt ids only if you need the full execution story | NEAR Data tells you whether widening is necessary at all | +- Decide whether one target contract was touched in the latest finalized block, and keep only the compact counts plus one exact identifier worth investigating next. **What a useful answer should include** - finalized height and hash - touched or not touched - counts for direct txs, incoming receipts, outcome hits, and state changes -- one sample tx hash or receipt id per category when present +- one sample tx hash or receipt id when present ### Final block to contract-touch answer shell walkthrough @@ -107,10 +99,7 @@ FINAL_LOCATION="$( | tr -d '\r' )" -BLOCK_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | sed -E 's#.*/([0-9]+)$#\1#')" - printf 'Final redirect target: %s\n' "$FINAL_LOCATION" -printf 'Final block height: %s\n' "$BLOCK_HEIGHT" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | tee /tmp/neardata-block.json >/dev/null @@ -173,60 +162,41 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' ' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json ``` -If you need richer shard-by-shard or full-list detail later, keep reusing `/tmp/neardata-block.json`. The point of this first pass is to answer “touched or not?” before you widen into longer arrays or deeper traces. +If you need richer detail later, keep reusing `/tmp/neardata-block.json`. The point of this first pass is to answer “touched or not?” before you widen into longer arrays or deeper traces. -Optional extension: if you still want the touched shard ids, compute them from the same cached block without changing the main answer shape: - -```bash -jq --arg target "$TARGET_ACCOUNT_ID" ' - [ - .shards[] - | .shard_id as $shard_id - | select( - ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) - or ([.chunk.receipts[]? | .receiver_id] | index($target)) - or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) - or ([.state_changes[]? | .change.account_id] | index($target)) - ) - | $shard_id - ] | unique -' /tmp/neardata-block.json -``` +#### Optional follow-up: Which tx hash or receipt id should I inspect next? -If that answer says `touched: true` and you want one shard-level follow-up, reopen only the first touched shard: +Keep the same cached block and summary, then lift one exact identifier for the next surface. ```bash -TOUCHED_SHARD_ID="$( - jq -r --arg target "$TARGET_ACCOUNT_ID" ' - first( - .shards[] - | .shard_id as $shard_id - | select( - ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) - or ([.chunk.receipts[]? | .receiver_id] | index($target)) - or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) - or ([.state_changes[]? | .change.account_id] | index($target)) - ) - | $shard_id - ) // empty - ' /tmp/neardata-block.json +FOLLOW_UP_KIND="$( + jq -r ' + if .sample_direct_tx != null then "tx_hash" + elif .sample_incoming_receipt != null then "receipt_id" + elif .sample_outcome_tx_hash != null then "tx_hash" + else "none" + end + ' /tmp/neardata-touch-summary.json )" -if [ -n "$TOUCHED_SHARD_ID" ]; then - curl -s "$NEARDATA_BASE_URL/v0/block/$BLOCK_HEIGHT/chunk/$TOUCHED_SHARD_ID" \ - | jq '{ - shard_id: .header.shard_id, - chunk_hash: .header.chunk_hash, - tx_hashes: ([.transactions[]? | (.transaction.hash // .hash)] | .[:5]), - receipt_ids: ([.receipts[]? | .receipt_id] | .[:5]), - receipt_receivers: ([.receipts[]? | .receiver_id] | .[:5]) - }' -fi +FOLLOW_UP_VALUE="$( + jq -r ' + .sample_direct_tx + // .sample_incoming_receipt + // .sample_outcome_tx_hash + // empty + ' /tmp/neardata-touch-summary.json +)" + +printf 'Next identifier kind: %s\n' "$FOLLOW_UP_KIND" +printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" ``` +If the identifier is a `tx_hash`, hand it to [Transactions API](/tx) or RPC `tx` status. If it is a `receipt_id`, hand it to [Transactions API: Receipt by ID](/tx/receipt). Only after that should you decide whether shard-level reopening is still necessary. + **Why this next step?** -This keeps the question as small as possible: first answer “was my contract touched?”, then widen only if one of the sample identifiers justifies a deeper trace. NEAR Data is the discovery layer here, not just a block monitor. +This keeps the question as small as possible: first answer “was my contract touched?”, then widen only if one exact tx hash or receipt id justifies a deeper trace. NEAR Data is the discovery layer here, not just a block monitor. ## Common mistakes @@ -235,7 +205,7 @@ This keeps the question as small as possible: first answer “was my contract to - Starting with RPC before checking whether one finalized block already answers the contract-touch question. - Looking only for direct transactions and forgetting that contracts are often touched through receipts or state changes. - Assuming one hard-coded shard id should be checked before you inspect the block family itself. -- Widening to Transactions API or RPC before extracting the exact shard ids, tx hashes, or receipt ids from NEAR Data. +- Widening to Transactions API or RPC before extracting one exact tx hash or receipt id from NEAR Data. ## Related guides diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index 700b21c..51c6bd1 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /rpc/examples title: RPC Examples -description: Plain-language workflows for using FastNear RPC docs for exact state checks, block inspection, contract views, and transaction submission. +description: Plain-language workflows for using FastNear RPC docs for transaction submission, access-key checks, FT preflight, raw-state reads, and Rainbow Bridge discovery. displayed_sidebar: rpcSidebar page_actions: - markdown @@ -729,143 +729,6 @@ PY Stay on RPC while the question is “what bridged token contracts exist and how much of one token is out there?” The factory is the source of truth for the bridged token set, and each token contract answers its own metadata and supply through standard NEP-141 view methods. If the next question becomes “who holds this token?”, move to [V1 FT Top Holders](/api/v1/ft-top) instead of trying to walk holders through RPC. -## SocialDB Exact Reads - -Stay on exact `call_function get` reads when you already know the SocialDB key you want. On standard RPC, raw `view_state` against `social.near` is not a practical teaching path because the contract state is too large to scan directly. - -### Read one SocialDB post exactly as stored right now - -Use this when a product, support tool, or agent already knows the account and wants the live SocialDB post payload without widening into transaction history. - -
-
- Strategy -

Read the current post key first, then fetch that exact post payload from social.near.

-
-
-

01RPC call_function get on mike.near/index/post tells you which post key is current.

-

02RPC call_function get on mike.near/post/main returns the exact stored post payload.

-

03If the next question becomes “which transaction wrote this?”, switch to [Transactions Examples](/tx/examples).

-
-
- -**Official references** - -- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) - -**What you're doing** - -- Read the current post pointer under `mike.near/index/post`. -- Reuse that key to fetch the exact post payload under `mike.near/post/`. -- Stop once you have the exact stored JSON and only widen into history if provenance matters. - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mike.near -``` - -1. Read the current post pointer first. - -```bash -INDEX_POST_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/index/post")] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$INDEX_POST_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-index-post.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | { - account_id: $account_id, - index_entry: (.[$account_id].index.post | fromjson), - current_post_key: (.[$account_id].index.post | fromjson | .key) - } -' /tmp/social-index-post.json -``` - -At the time of writing, the current post key for `mike.near` was `main`. - -2. Read that exact post payload. - -```bash -POST_KEY="$( - jq -r --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | .[$account_id].index.post - | fromjson - | .key - ' /tmp/social-index-post.json -)" - -POST_ARGS_BASE64="$( - jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg post_key "$POST_KEY" '{ - keys: [($account_id + "/post/" + $post_key)] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$POST_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-post-main.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg post_key "$POST_KEY" ' - .result.result - | implode - | fromjson - | { - account_id: $account_id, - post_key: $post_key, - post: (.[$account_id].post[$post_key] | fromjson) - } -' /tmp/social-post-main.json -``` - -That gives you the exact JSON stored for the current post, including fields like `type`, `text`, and `image`. - -**Why this next step?** - -This is the clean RPC pattern for SocialDB: ask the contract for one exact key, decode the returned JSON, and stop. If the question turns into “who wrote this and when?”, move to the transaction examples instead of trying to brute-force raw `social.near` state. - - ## Common mistakes - Starting in RPC when the user really wants a holdings summary or indexed history. diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index c043024..49661e2 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /transfers/examples title: Transfers Examples -description: Plain-language workflows for finding transfers, paginating with resume_token, and pivoting into transaction history. +description: Plain-language workflows for checking whether funds moved in one window and optionally anchoring one row to a receipt. displayed_sidebar: transfersApiSidebar page_actions: - markdown @@ -47,35 +47,34 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ }' ``` -This is the shortest way to answer “did funds move here, and which receipt should I chase next?” +This is the shortest way to answer “did funds move here, and which row should I inspect next?” ## Worked walkthrough -### Find one outgoing transfer, then pivot to execution details if needed +### Did this account send funds in this window, and which row should I inspect? -Use this when the user story is “I know this account sent funds in this window, and I may need the exact execution anchor behind one row, but I do not want the whole account history yet.” +Use this when the user story is “I need one narrow outgoing window first, and only after I see the rows will I decide whether one of them needs a receipt-level follow-up.”
Strategy -

Stay narrow on movement first, then pivot once into execution history only if the transfer row is not enough.

+

Answer the movement question first, then widen once only if one row still needs an execution anchor.

01POST /v0/transfers gives you the tight outgoing window and the specific movement worth chasing.

02Print the rows first, then choose one transfer_index before lifting its receipt_id.

-

03POST /v0/receipt is the optional widening step when you need execution history behind that transfer.

+

03POST /v0/receipt is the optional follow-up when you want to know what one transfer row did on chain.

**What you're doing** - Query a bounded outgoing transfer window for one account on mainnet. -- Pull out one transfer row that looks like the movement you care about. -- Reuse its `receipt_id` in Transactions API only if you need to move from balance movement into execution history. +- Print the rows first, then choose one transfer row that looks like the movement you care about. +- Reuse its `receipt_id` only if you need to move from balance movement into execution history. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=YOUR_ACCOUNT_ID FROM_TIMESTAMP_MS=1711929600000 TO_TIMESTAMP_MS=1712016000000 @@ -121,6 +120,16 @@ RECEIPT_ID="$( printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" +``` + +That answers the first question: did funds move here, and which transfer row should you inspect next? + +#### Optional follow-up: What did this transfer row do on chain? + +Only widen to receipt history if the transfer row itself is not enough. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com if [ -n "$RECEIPT_ID" ]; then curl -s "$TX_BASE_URL/v0/receipt" \ @@ -137,7 +146,7 @@ fi **Why this next step?** -The transfer query answers the first question quickly: did this account send funds in this window, and to whom? Looking up the `receipt_id` is the optional next step when the transfer row itself is not enough and you need the execution anchor behind it. If you still need more rows afterward, keep paginating with the same `resume_token` and unchanged filters. +The transfer query answers the first question quickly: did this account send funds in this window, and to whom? Looking up the `receipt_id` is the optional second question: what execution anchor sits behind this one row? If you still need more rows afterward, keep paginating with the same `resume_token` and unchanged filters. ## Common mistakes diff --git a/docs/tx/berry-club.mdx b/docs/tx/berry-club.mdx index 3924d7f..796172e 100644 --- a/docs/tx/berry-club.mdx +++ b/docs/tx/berry-club.mdx @@ -1,8 +1,8 @@ --- -sidebar_label: Berry Club Case Study +sidebar_label: Berry Club slug: /tx/examples/berry-club -title: "Berry Club Case Study: Read the live board, then reconstruct one era" -description: "A case study that starts with the live Berry Club board from RPC get_lines, then uses Transactions API to reconstruct one older era." +title: "Berry Club: Read the live board, then reconstruct one era" +description: "Read the live Berry Club board with RPC get_lines, then use Transactions API to reconstruct one older era." displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -19,11 +19,11 @@ keywords: import Link from '@site/src/components/LocalizedLink'; import BerryClubLiveBoard from '@site/src/components/BerryClubLiveBoard'; -{/* FASTNEAR_AI_DISCOVERY: This case study shows the shortest useful Berry Club flow: read the live board with RPC get_lines, then use Transactions API only when you need to reconstruct one older era from draw calls. */} +{/* FASTNEAR_AI_DISCOVERY: This walkthrough shows the shortest useful Berry Club flow: read the live board with RPC get_lines, then use Transactions API only when you need to reconstruct one older era from draw calls. */} -# Berry Club Case Study: Read the live board, then reconstruct one era +# Berry Club: Read the live board, then reconstruct one era -Use this case study when the live board is easy to read, but you need one historical reconstruction path behind it. +Use this walkthrough when the live board is easy to read, but you need one historical reconstruction path behind it. Start with the live board. If that already answers the question, stop there. diff --git a/docs/tx/examples.md b/docs/tx/examples.md index dcfda05..d92c514 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /tx/examples title: Transactions Examples -description: Plain-language transaction investigations for common developer jobs first, plus a few deeper case studies when you need them. +description: Plain-language transaction investigations for common developer jobs first. displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -37,8 +37,6 @@ curl -s "$TX_BASE_URL/v0/transactions" \ This is the shortest investigation on the page. Only move to RPC or receipt IDs if this output is not enough. -If you want the longer case-study version of the same surface, jump to the [Berry Club case study](/tx/examples/berry-club) for historical board reconstruction or the [OutLayer case study](/tx/examples/outlayer) for worker and callback tracing. - ## Start Here These are the smallest useful anchors on the page: start with one tx hash, then one receipt ID, and only go deeper when the simpler story stops being enough. @@ -1020,60 +1018,6 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ For callback questions, the important proof is not “did every receipt succeed?” but “did the origin contract get its callback receipt back, and what happened there?” `POST /v0/transactions` gives the fastest readable answer. RPC is only the optional confirmation layer when you need the callback receipt's canonical outcome and logs. -## Advanced and Case Studies - -The examples below are still useful, but they are longer or more specialized than the default start-here flows above. `Berry Club` and `OutLayer` live as separate case-study pages, the SocialDB provenance pattern now lives on its own advanced page, and the last example here keeps only a slim multi-contract follow-up pattern. - -### Advanced SocialDB provenance pattern - -If the readable fact already comes from `api.near.social`, keep the follow-up small: semantic value first, `:block` second, then FastNear block and transaction lookup. Use the [dedicated SocialDB provenance pattern page](/tx/socialdb-proofs) for one canonical example of that flow. - -### Advanced: which downstream contracts did this transaction touch? - -Use this when you already have one multi-contract tx hash and the next question is simply “which contracts did this call into after the top-level action?” - -This pinned mainnet anchor still makes a good example, even though it happens to be an `intents.near` settlement: - -- transaction hash: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` -- signer and receiver: `intents.near` -- included block height: `194573310` - -The short answer for this tx is already useful: - -- the top-level method was `execute_intents` -- early downstream receipts touched `v2_1.omni.hot.tg` and `bridge-refuel.hot.tg` -- later logs included event families like `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw`, and `mt_burn` - -For most questions, Transactions API is enough: - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name - }, - downstream_receivers: ( - [.transactions[0].receipts[] | .receipt.receiver_id] - | unique - ), - first_logs: ( - [.transactions[0].receipts[] | .execution_outcome.outcome.logs[]?] - | .[:5] - ) - }' -``` - -If you need the containing block, widen once to Transactions API [`POST /v0/block`](/tx/block). If you need the canonical receipt DAG or raw `EVENT_JSON` logs, widen once more to RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status). The teaching point here is generic: start with one tx hash, list the downstream receivers, and stop unless the trace really needs more. - - ## Common mistakes - Trying to submit a transaction from the history API instead of raw RPC. @@ -1087,5 +1031,8 @@ If you need the containing block, widen once to Transactions API [`POST /v0/bloc - [RPC Reference](/rpc) - [FastNear API](/api) - [NEAR Data API](/neardata) +- [Berry Club: live board and one historical reconstruction path](/tx/examples/berry-club) +- [OutLayer: pair one request tx with one worker resolution](/tx/examples/outlayer) +- [Advanced SocialDB write lookup](/tx/socialdb-proofs) - [Choosing the Right Surface](/agents/choosing-surfaces) - [Agent Playbooks](/agents/playbooks) diff --git a/docs/tx/outlayer.mdx b/docs/tx/outlayer.mdx index 760fb31..6a7564e 100644 --- a/docs/tx/outlayer.mdx +++ b/docs/tx/outlayer.mdx @@ -1,8 +1,8 @@ --- -sidebar_label: OutLayer Case Study +sidebar_label: OutLayer slug: /tx/examples/outlayer -title: "OutLayer Case Study: Trace request and worker resolution" -description: "A case study that uses Transactions API to pair one caller-side OutLayer request with one later worker-side resolution and inspect the finish receipts." +title: "OutLayer: What did this request/resolution pair do?" +description: "Use Transactions API to read one caller-side OutLayer request, one later worker-side resolution, and the finish receipts only when needed." displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -17,28 +17,22 @@ keywords: import Link from '@site/src/components/LocalizedLink'; -{/* FASTNEAR_AI_DISCOVERY: This case study stays on observable transaction and receipt evidence. It shows how to pair one caller-side OutLayer request with one later worker-side resolution and inspect the finish receipts. It does not try to prove OutLayer's internal TEE, yield/resume, or CKD/MPC architecture from public chain data alone. */} +{/* FASTNEAR_AI_DISCOVERY: This walkthrough stays on observable transaction and receipt evidence. It shows how to read one caller-side OutLayer request together with one later worker-side resolution, then inspect the finish receipts only when needed. */} -# OutLayer Case Study: Trace request and worker resolution +# OutLayer: What did this request/resolution pair do? -Use this case study when the question is: “Which transaction opened the OutLayer request, which later transaction came from the worker, and where did the finish receipts show callback, charging, or refund behavior?” +Use this walkthrough when the question is: “What did this OutLayer request do, what later resolution belongs to it, and do I need to look at the finish receipts?” -This page intentionally stays on public chain evidence only. It shows the caller transaction, the later worker transaction, and the finish receipts. It does not try to prove OutLayer’s internal TEE, `yield/resume`, or CKD/MPC architecture from chain traces alone. +Stay on public chain evidence only: read the request tx, read the later resolution tx, and only then decide whether the finish receipts matter. -Treat this as a transaction-history problem first: - -- one caller-side `request_execution` -- one later worker-side `submit_execution_output_and_resolve` or `resolve_execution` -- receipt-level follow-up only when the finish path matters - -## Verified shell walkthrough +## Compact shell walkthrough This pair worked on April 18, 2026: - caller-side request: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` - worker-side resolution: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` -### 1. Hydrate the caller transaction and the worker transaction together +### 1. Main answer: hydrate the request tx and the resolution tx together ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -59,16 +53,20 @@ jq '{ hash: .transaction.hash, signer_id: .transaction.signer_id, receiver_id: .transaction.receiver_id, - actions: [.transaction.actions[] | keys[0]], + methods: [ + .transaction.actions[] + | .FunctionCall.method_name? + | select(. != null) + ], first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) } ] }' /tmp/outlayer-pair.json ``` -This gives you the core observable loop: caller-side request first, worker-side resolution later, with readable signer, method, and log evidence for both. +This is the core answer: one request tx, one later resolution tx, and readable signer, receiver, method, and log evidence for both. -### 2. Inspect the worker receipts only when the finish path matters +### Optional follow-up: What did the finish path do? ```bash jq --arg worker_tx_hash "$WORKER_TX_HASH" ' @@ -90,15 +88,15 @@ jq --arg worker_tx_hash "$WORKER_TX_HASH" ' ' /tmp/outlayer-pair.json ``` -Look for: +Look for the smallest readable finish-path evidence: - `FunctionCall` receipts that continue the finish path - charging logs such as `[[yNEAR charged: "..."]]` - follow-up `Transfer` receipts that suggest refund or settlement movement -This is the right point to care about receipts. Do not start here if the real question is still “which two transactions belong to the same OutLayer request?” +Do not start here if the real question is still “which two transactions belong together?” -### 3. If you do not already know the two hashes, discover them first +### Optional: discover the two hashes first ```bash curl -sS "$TX_BASE_URL/v0/account" \ @@ -110,12 +108,10 @@ curl -sS "$TX_BASE_URL/v0/account" \ }' ``` -Use this as a discovery surface only. For this example, `/v0/account` gives you candidate hashes, and `/v0/transactions` is the surface that turns those hashes into readable evidence. +Use this only when you do not already know the pair. `/v0/account` gives you candidate hashes, and `/v0/transactions` is the surface that turns them into a readable answer. -## Related pages +## Related guides - Transactions API: Account History - Transactions API: Transactions by Hash - Transactions API: Receipt by ID -- [OutLayer NEAR Integration](https://outlayer.fastnear.com/docs/near-integration) -- [OutLayer Secrets / CKD](https://outlayer.fastnear.com/docs/secrets) diff --git a/docs/tx/socialdb-proofs.mdx b/docs/tx/socialdb-proofs.mdx index f887310..d3cdf04 100644 --- a/docs/tx/socialdb-proofs.mdx +++ b/docs/tx/socialdb-proofs.mdx @@ -1,15 +1,15 @@ --- slug: /tx/socialdb-proofs -title: Advanced SocialDB Provenance Pattern -description: One advanced pattern for starting from a readable SocialDB value and recovering the write transaction behind it. +title: Advanced SocialDB Write Lookup +description: One small advanced playbook for starting from a readable SocialDB value and recovering the write transaction behind it. displayed_sidebar: transactionsApiSidebar page_actions: - markdown --- -# Advanced SocialDB Provenance Pattern +# Advanced SocialDB Write Lookup -Use this page only when the starting point is already a readable SocialDB value from `api.near.social` and the next question is historical provenance. +Use this page only when the starting point is already a readable SocialDB value from `api.near.social` and the next question is historical write lookup. For FastNear-first jobs, start with [Transactions Examples](/tx/examples). Come here only when the question has become "which write made this readable SocialDB value true?" @@ -128,71 +128,11 @@ jq '{ }' /tmp/mike-profile-transaction.json ``` -4. Finish with canonical current-state confirmation via raw RPC. +That is the whole lookup pattern: readable value, field-level block, receipt bridge, and transaction payload. -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-profile-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - finality: "final", - current_name: ( - .result.result - | implode - | fromjson - | .[$account_id].profile.name - ) -}' /tmp/mike-profile-rpc.json -``` - -That is the whole provenance pattern: readable value, field-level block, receipt bridge, transaction payload, then optional current-state confirmation. - -## Same pattern, different SocialDB keys - -Use the same bridge whenever you already have a readable SocialDB value and its write block: - -1. Read the semantic value and `:block` from NEAR Social, or start from a known widget write block. -2. Use Transactions API [`POST /v0/block`](/tx/block) to recover the `*.near -> social.near` receipt and transaction hash. -3. Use Transactions API [`POST /v0/transactions`](/tx/transactions) to decode the `social.near set` payload. -4. Use RPC [`query(call_function)`](/rpc/contract/call-function) only if you still need canonical current-state confirmation. - -### Follow edge variant - -Use the same pattern for one readable follow edge: - -- current edge: `mike.near -> mob.near` -- SocialDB write block: `79574924` -- receipt ID: `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th` -- originating transaction hash: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - -The only meaningful difference from the profile example is the key and payload shape: read `mike.near/graph/follow/mob.near`, then decode both `graph.follow` and the matching `index.graph` entry from the write payload. - -### Widget source variant - -Use the same pattern again when the readable fact is a widget source key: +The same bridge works for other readable SocialDB values too: -- widget key: `mob.near/widget/Profile` -- SocialDB write block: `86494825` -- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` -- originating transaction hash: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- follow edge variant: `mike.near -> mob.near`, block `79574924`, tx `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- widget source variant: `mob.near/widget/Profile`, block `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` -The main difference here is that you usually start from the widget's known write block, then decode `.data["mob.near"].widget.Profile[""]` from the originating `social.near set` payload. +The key idea does not change: start from the readable value and its write block, recover the `*.near -> social.near` receipt from the block, then decode the `social.near set` payload from the originating transaction. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index 4281f09..ab82629 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /api/examples title: "Примеры API" -description: "Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и классификации стейкинга." +description: "Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и проверки прямого стейкинга." displayed_sidebar: fastnearApiSidebar page_actions: - markdown @@ -144,7 +144,59 @@ jq '{ **Зачем нужен следующий шаг?** -Так вопрос остаётся узким и практическим. Если ответ `true`, следующий реальный шаг обычно связан с `unstake` или `withdraw` в конкретном пуле. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. +Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. + +#### Необязательное продолжение: Что сделал этот контрактный вызов для делегирования? + +Используйте это продолжение, когда staking-эндпоинт уже показал пул вроде `polkachu.poolv1.near`, и теперь вы хотите увидеть форму одной реальной делегационной транзакции. + +Этот зафиксированный mainnet tx хорош тем, что очень ясно показывает весь паттерн: + +- хеш транзакции: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` +- top-level receiver: `polkachu.poolv1.near` +- top-level метод: `deposit_and_stake` +- attached deposit: `34650000000000000000000000` + +Важная форма chain-истории здесь такая: + +- делегатор отправляет `FunctionCall deposit_and_stake` в pool-контракт +- pool-контракт учитывает депозит и staking shares +- затем pool выпускает self-receipt с настоящим `Stake` action + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/staking-delegation-tx.json >/dev/null + +jq '{ + top_level_call: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + attached_deposit: .transactions[0].transaction.actions[0].FunctionCall.deposit + }, + pool_side_effects: [ + .transactions[0].receipts[] + | select(.receipt.receiver_id == "polkachu.poolv1.near") + | { + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + actions: ( + .receipt.receipt.Action.actions + | map(if type == "string" then . else keys[0] end) + ), + first_logs: (.execution_outcome.outcome.logs[:3]) + } + ] +}' /tmp/staking-delegation-tx.json +``` + +Простой вывод здесь такой: делегатор не подписывал сырой `Stake` action напрямую. Он вызвал staking-pool-контракт через `deposit_and_stake` и приложил депозит, а затем уже pool-контракт сам выполнил `Stake` action на своём аккаунте. ### Какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает? diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index 4e4ea87..538c5a1 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: "Примеры KV FastData" -description: "Пошаговые сценарии для чтения точных строк FastData, проверки истории точного ключа и привязки индексированной строки к исходной транзакции." +description: "Пошаговые сценарии для чтения точных строк FastData, проверки истории точного ключа и необязательной привязки индексированной настройки к исходной транзакции." displayed_sidebar: kvFastDataSidebar page_actions: - markdown diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index 508fecc..d4a3520 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /neardata/examples title: "Примеры NEAR Data" -description: "Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных хешей для дальнейшего разбора." +description: "Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных идентификаторов для дальнейшего разбора." displayed_sidebar: nearDataApiSidebar page_actions: - markdown @@ -58,33 +58,25 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \
Стратегия -

Зафиксируйтесь на одном финализированном блоке, просканируйте всё семейство блока по целевому аккаунту, а затем оставьте только компактную сводку и идентификаторы, которые действительно стоит разбирать дальше.

+

Сначала ответьте на вопрос о контрактном touch, а затем оставьте только один tx hash или receipt id для следующего шага.

01last-block-final даёт одну стабильную высоту блока без угадывания.

02block — это главный read: он уже содержит транзакции, receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?»

-

03Только если ответ «да», расширяйтесь дальше: сохраняйте найденные shard id, tx hash и receipt id, а затем передавайте именно эти идентификаторы в [Transactions API](/tx) или [RPC Reference](/rpc).

+

03Только если ответ «да», расширяйтесь дальше: сохраните один точный tx hash или receipt id из того же сохранённого блока, а затем передайте этот идентификатор в [Transactions API](/tx) или [RPC Reference](/rpc).

**Цель** -- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только shard id, счётчики и sample-идентификаторы для следующего шага. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Последняя стабильная точка | NEAR Data [`last-block-final`](/neardata/last-block-final) | Получаем высоту одного финализированного блока без угадывания | Даёт стабильную отправную точку для всего вопроса | -| Всё семейство блока | NEAR Data [`block`](/neardata/block) | Сканируем транзакции, receipts, результаты исполнения receipts и изменения состояния по целевому аккаунту | Это главная поверхность ответа на вопрос «был ли затронут мой контракт?» | -| Лёгкая сводка по блоку | NEAR Data [`block-headers`](/neardata/block-headers) | Используем, когда нужны только высота, хеш, время или заголовки чанков | Позволяет не тянуть более широкий payload блока, когда фильтрация по контракту не нужна | -| Необязательный follow-up по шарду | NEAR Data [`block-chunk`](/neardata/block-chunk) или [`block-shard`](/neardata/block-shard) | Повторно открываем только затронутый шард, если нужен более глубокий payload | Полезно, когда вы уже знаете, какой шард mattered | -| Точные поверхности для продолжения | [Transactions API](/tx) или [RPC Reference](/rpc) | Переиспользуем найденные tx hash или receipt id только если нужна полная история исполнения | NEAR Data позволяет сначала понять, нужен ли вообще переход дальше | +- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только компактные счётчики плюс один точный идентификатор для следующего шага. **Что должен включать полезный ответ** - финализированную высоту и хеш - ответ “затронут / не затронут” - счётчики прямых транзакций, входящих receipts, outcome-hit и state changes -- по одному sample tx hash или receipt id на категорию, когда он есть +- один sample tx hash или receipt id, когда он есть ### Shell-сценарий от финализированного блока к ответу по контракту @@ -107,10 +99,7 @@ FINAL_LOCATION="$( | tr -d '\r' )" -BLOCK_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | sed -E 's#.*/([0-9]+)$#\1#')" - printf 'Final redirect target: %s\n' "$FINAL_LOCATION" -printf 'Final block height: %s\n' "$BLOCK_HEIGHT" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | tee /tmp/neardata-block.json >/dev/null @@ -173,60 +162,41 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' ' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json ``` -Если позже понадобятся более богатые списки или разбор по шардам, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. +Если позже понадобятся более богатые списки, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. -Необязательное расширение: если всё же нужны `touched_shards`, их можно вычислить из того же сохранённого блока, не утяжеляя основной ответ: - -```bash -jq --arg target "$TARGET_ACCOUNT_ID" ' - [ - .shards[] - | .shard_id as $shard_id - | select( - ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) - or ([.chunk.receipts[]? | .receiver_id] | index($target)) - or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) - or ([.state_changes[]? | .change.account_id] | index($target)) - ) - | $shard_id - ] | unique -' /tmp/neardata-block.json -``` +#### Необязательное продолжение: Какой tx hash или receipt id разбирать дальше? -Если в этом ответе `touched: true` и нужен один follow-up на уровне шарда, откройте только первый затронутый шард: +Используйте ту же сохранённую сводку и поднимите один точный идентификатор для следующей поверхности. ```bash -TOUCHED_SHARD_ID="$( - jq -r --arg target "$TARGET_ACCOUNT_ID" ' - first( - .shards[] - | .shard_id as $shard_id - | select( - ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) - or ([.chunk.receipts[]? | .receiver_id] | index($target)) - or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) - or ([.state_changes[]? | .change.account_id] | index($target)) - ) - | $shard_id - ) // empty - ' /tmp/neardata-block.json +FOLLOW_UP_KIND="$( + jq -r ' + if .sample_direct_tx != null then "tx_hash" + elif .sample_incoming_receipt != null then "receipt_id" + elif .sample_outcome_tx_hash != null then "tx_hash" + else "none" + end + ' /tmp/neardata-touch-summary.json )" -if [ -n "$TOUCHED_SHARD_ID" ]; then - curl -s "$NEARDATA_BASE_URL/v0/block/$BLOCK_HEIGHT/chunk/$TOUCHED_SHARD_ID" \ - | jq '{ - shard_id: .header.shard_id, - chunk_hash: .header.chunk_hash, - tx_hashes: ([.transactions[]? | (.transaction.hash // .hash)] | .[:5]), - receipt_ids: ([.receipts[]? | .receipt_id] | .[:5]), - receipt_receivers: ([.receipts[]? | .receiver_id] | .[:5]) - }' -fi +FOLLOW_UP_VALUE="$( + jq -r ' + .sample_direct_tx + // .sample_incoming_receipt + // .sample_outcome_tx_hash + // empty + ' /tmp/neardata-touch-summary.json +)" + +printf 'Next identifier kind: %s\n' "$FOLLOW_UP_KIND" +printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" ``` +Если идентификатор — это `tx_hash`, передайте его в [Transactions API](/tx) или RPC `tx` status. Если это `receipt_id`, передайте его в [Transactions API: Receipt by ID](/tx/receipt). И только после этого решайте, нужен ли вам вообще shard-level follow-up. + **Зачем нужен следующий шаг?** -Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один из sample-идентификаторов уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. +Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один точный tx hash или receipt id уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. ## Частые ошибки @@ -235,7 +205,7 @@ fi - Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. - Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. - Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. -- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data точные shard id, tx hash и receipt id. +- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data один точный tx hash или receipt id. ## Полезные связанные страницы diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index 66977e2..a2ac8c9 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /rpc/examples title: "Примеры RPC" -description: "Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций." +description: "Пошаговые сценарии использования FastNear RPC для отправки транзакций, проверки прав ключа доступа, предварительной проверки FT, чтения сырого состояния и поиска Rainbow Bridge." displayed_sidebar: rpcSidebar page_actions: - markdown @@ -729,143 +729,6 @@ PY Оставайтесь в RPC, пока вопрос звучит как «какие bridged token-контракты существуют и сколько одного такого токена сейчас в обращении?» Factory — это источник истины для множества bridged token-ов, а каждый token-контракт сам отвечает за свои метаданные и объём в обращении через стандартные NEP-141 view-методы. Если следующий вопрос становится «кто держит этот токен?», переключайтесь на [V1 FT Top Holders](/api/v1/ft-top), а не пытайтесь обходить holders через RPC. -## Точные чтения SocialDB - -Оставайтесь на точных чтениях через `call_function get`, когда вы уже знаете нужный ключ SocialDB. На обычном RPC raw `view_state` для `social.near` не подходит как обучающий путь, потому что состояние контракта слишком велико для прямого чтения. - -### Прочитать один пост SocialDB ровно в том виде, как он хранится сейчас - -Используйте этот сценарий, когда продукту, support-инструменту или агенту уже известен аккаунт и нужен живой payload поста из SocialDB без перехода в историю транзакций. - -
-
- Стратегия -

Сначала прочитайте текущий ключ поста, затем получите точный payload этого поста из social.near.

-
-
-

01RPC call_function get по mike.near/index/post показывает, какой ключ поста сейчас активен.

-

02RPC call_function get по mike.near/post/main возвращает точный сохранённый payload поста.

-

03Если следующий вопрос становится «какая транзакция это записала?», переключайтесь на [Transactions Examples](/tx/examples).

-
-
- -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Что вы делаете** - -- Читаете текущий указатель поста под `mike.near/index/post`. -- Используете этот ключ, чтобы получить точный payload по `mike.near/post/`. -- Останавливаетесь на точном JSON и расширяетесь в историю только тогда, когда действительно нужна provenance. - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mike.near -``` - -1. Сначала прочитайте текущий указатель поста. - -```bash -INDEX_POST_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/index/post")] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$INDEX_POST_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-index-post.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | { - account_id: $account_id, - index_entry: (.[$account_id].index.post | fromjson), - current_post_key: (.[$account_id].index.post | fromjson | .key) - } -' /tmp/social-index-post.json -``` - -На момент написания текущим ключом поста для `mike.near` был `main`. - -2. Прочитайте точный payload этого поста. - -```bash -POST_KEY="$( - jq -r --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | .[$account_id].index.post - | fromjson - | .key - ' /tmp/social-index-post.json -)" - -POST_ARGS_BASE64="$( - jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg post_key "$POST_KEY" '{ - keys: [($account_id + "/post/" + $post_key)] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$POST_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-post-main.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg post_key "$POST_KEY" ' - .result.result - | implode - | fromjson - | { - account_id: $account_id, - post_key: $post_key, - post: (.[$account_id].post[$post_key] | fromjson) - } -' /tmp/social-post-main.json -``` - -Так вы получаете точный JSON, который хранится для текущего поста, включая поля вроде `type`, `text` и `image`. - -**Зачем нужен следующий шаг?** - -Это чистый RPC-паттерн для SocialDB: спросите у контракта один точный ключ, декодируйте возвращённый JSON и остановитесь. Если вопрос превращается в «кто и когда это записал?», переходите к примерам по транзакциям, а не пытайтесь brute-force читать raw state `social.near`. - - ## Частые ошибки - Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md index c8b15ba..f9c2ce1 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /transfers/examples title: "Примеры Transfers API" -description: "Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций." +description: "Пошаговые сценарии для проверки, было ли движение средств в одном окне, и необязательного перехода от одной строки к receipt." displayed_sidebar: transfersApiSidebar page_actions: - markdown @@ -47,35 +47,34 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ }' ``` -Это самый короткий путь к вопросу «были ли здесь движения средств и какой receipt брать следующим?» +Это самый короткий путь к вопросу «были ли здесь движения средств и какую строку стоит разбирать дальше?» ## Готовый сценарий -### Найти один исходящий перевод и при необходимости перейти к деталям исполнения +### Отправлял ли этот аккаунт средства в этом окне и какую строку стоит разобрать? -Используйте этот сценарий, когда история звучит так: «я знаю, что этот аккаунт отправлял средства в этом окне, и мне может понадобиться точная опорная точка исполнения для одной строки, но я не хочу сразу тянуть всю историю аккаунта». +Используйте этот сценарий, когда история звучит так: «мне сначала нужно одно узкое окно исходящих переводов, и только после просмотра строк я решу, нужен ли одной из них follow-up по receipt».
Стратегия -

Сначала оставайтесь на узкой истории движения, а затем переходите в историю исполнения только если строки перевода уже недостаточно.

+

Сначала ответьте на вопрос о движении средств, а затем расширяйтесь только если одной строке всё ещё нужен execution-anchor.

01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять.

02Сначала выведите строки, а затем явно выберите один transfer_index перед тем, как поднимать его receipt_id.

-

03POST /v0/receipt — это необязательное расширение, когда уже нужны детали исполнения именно за этим переводом.

+

03POST /v0/receipt — необязательный follow-up, когда вы хотите понять, что именно эта строка перевода сделала on-chain.

**Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Выделяете одну строку перевода, которая действительно похожа на нужное вам движение. -- Переиспользуете его `receipt_id` в Transactions API только если нужно перейти от движения актива к истории исполнения. +- Сначала выводите строки, а затем выбираете одну строку перевода, которая действительно похожа на нужное вам движение. +- Переиспользуете её `receipt_id` только если нужно перейти от движения актива к истории исполнения. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=YOUR_ACCOUNT_ID FROM_TIMESTAMP_MS=1711929600000 TO_TIMESTAMP_MS=1712016000000 @@ -121,6 +120,16 @@ RECEIPT_ID="$( printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" +``` + +Этим вы отвечаете на первый вопрос: было ли здесь движение средств и какую строку перевода стоит разбирать дальше? + +#### Необязательное продолжение: Что сделала эта строка перевода on-chain? + +Переходите к истории receipt только если самой строки перевода уже недостаточно. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com if [ -n "$RECEIPT_ID" ]; then curl -s "$TX_BASE_URL/v0/receipt" \ @@ -137,7 +146,7 @@ fi **Зачем нужен следующий шаг?** -Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный следующий шаг, когда самой строки перевода уже недостаточно и нужна опорная точка в истории исполнения. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. +Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный второй вопрос: какая execution-anchor стоит за этой одной строкой? Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. ## Частые ошибки diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx index 8651bdd..938cbc1 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx @@ -1,8 +1,8 @@ --- -sidebar_label: Berry Club Case Study +sidebar_label: Berry Club slug: /tx/examples/berry-club -title: "Berry Club Case Study: как читать живую доску и разбирать одну эпоху" -description: "Case study, который начинается с живой доски Berry Club через RPC get_lines, а затем использует Transactions API, чтобы восстановить одну более раннюю эпоху." +title: "Berry Club: как читать живую доску и разбирать одну эпоху" +description: "Прочитайте живую доску Berry Club через RPC get_lines, а затем используйте Transactions API, чтобы восстановить одну более раннюю эпоху." displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -19,11 +19,11 @@ keywords: import Link from '@site/src/components/LocalizedLink'; import BerryClubLiveBoard from '@site/src/components/BerryClubLiveBoard'; -{/* FASTNEAR_AI_DISCOVERY: Этот case study показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} +{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} -# Berry Club Case Study: как читать живую доску и разбирать одну эпоху +# Berry Club: как читать живую доску и разбирать одну эпоху -Используйте этот case study, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. +Используйте этот walkthrough, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. Начните с живой доски. Если этого уже достаточно для ответа, на этом можно остановиться. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index e872ed8..eade930 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /tx/examples title: "Примеры Transactions API" -description: "Пошаговые расследования транзакций сначала для типовых задач разработчика, а затем для более глубоких case study, когда они действительно нужны." +description: "Пошаговые расследования транзакций сначала для типовых задач разработчика." displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -37,8 +37,6 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. -Если нужен более развёрнутый case study на той же поверхности, переходите к [Berry Club case study](/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer case study](/tx/examples/outlayer) для трассировки воркера и callback-цепочки. - ## С чего начать Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. @@ -1020,60 +1018,6 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. -## Расширенные сценарии и case study - -Примеры ниже всё ещё полезны, но они длиннее или более специализированы, чем основные start-here-сценарии выше. `Berry Club` и `OutLayer` вынесены в отдельные case-study-страницы, паттерн provenance для SocialDB теперь живёт на отдельной расширенной странице, а последний пример здесь оставляет только компактный multi-contract follow-up. - -### Расширенный паттерн provenance для SocialDB - -Если читаемый факт уже приходит из `api.near.social`, держите follow-up маленьким: сначала семантическое значение, затем `:block`, потом lookup по блоку и транзакции в FastNear. Для одного канонического примера такого сценария используйте [отдельную страницу паттерна provenance для SocialDB](/tx/socialdb-proofs). - -### Расширенный сценарий: какие downstream-контракты затронула эта транзакция? - -Используйте этот сценарий, когда у вас уже есть один multi-contract tx hash и следующий вопрос звучит просто: «в какие контракты ушёл этот вызов после top-level action?» - -Этот зафиксированный mainnet-якорь по-прежнему хорошо подходит как пример, хотя сама транзакция и относится к `intents.near`: - -- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` -- аккаунт `signer` и `receiver`: `intents.near` -- высота включающего блока: `194573310` - -Короткий ответ для этой tx уже полезен: - -- top-level метод был `execute_intents` -- ранние downstream-receipt затронули `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` -- в более поздних логах были семейства событий вроде `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` - -Для большинства вопросов достаточно Transactions API: - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name - }, - downstream_receivers: ( - [.transactions[0].receipts[] | .receipt.receiver_id] - | unique - ), - first_logs: ( - [.transactions[0].receipts[] | .execution_outcome.outcome.logs[]?] - | .[:5] - ) - }' -``` - -Если нужен включающий блок, расширяйтесь один раз до Transactions API [`POST /v0/block`](/tx/block). Если нужен канонический DAG по receipt или сырые логи `EVENT_JSON`, расширяйтесь ещё на один шаг до RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status). Учебная идея здесь общая: начинайте с одного tx hash, перечислите downstream receiver-и и останавливайтесь, пока trace действительно не требует большего. - - ## Частые ошибки - Пытаться отправлять транзакцию через history API вместо сырого RPC. @@ -1087,5 +1031,8 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - [RPC Reference](/rpc) - [FastNear API](/api) - [NEAR Data API](/neardata) +- [Berry Club: живая доска и один путь исторической реконструкции](/tx/examples/berry-club) +- [OutLayer: связать одну транзакцию запроса с одним ответом воркера](/tx/examples/outlayer) +- [Расширенный поиск записи SocialDB](/tx/socialdb-proofs) - [Choosing the Right Surface](/agents/choosing-surfaces) - [Agent Playbooks](/agents/playbooks) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx index 839ae69..c4409e4 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx @@ -1,8 +1,8 @@ --- -sidebar_label: OutLayer Case Study +sidebar_label: OutLayer slug: /tx/examples/outlayer -title: "OutLayer Case Study: трассировка запроса и разрешения воркером" -description: "Case study, который использует Transactions API, чтобы связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать завершающие receipts." +title: "OutLayer: что сделала эта пара request/resolution?" +description: "Используйте Transactions API, чтобы прочитать один caller-side запрос OutLayer, одно более позднее worker-side resolution и обращаться к finish-receipts только когда это действительно нужно." displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -17,28 +17,22 @@ keywords: import Link from '@site/src/components/LocalizedLink'; -{/* FASTNEAR_AI_DISCOVERY: Этот case study остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} +{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как прочитать один caller-side запрос OutLayer вместе с более поздним worker-side resolution, а затем разобрать finish-receipts только если это действительно нужно. */} -# OutLayer Case Study: трассировка запроса и разрешения воркером +# OutLayer: что сделала эта пара request/resolution? -Используйте этот case study, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» +Используйте этот walkthrough, когда вопрос звучит так: «что сделал этот запрос OutLayer, какое более позднее resolution к нему относится и нужно ли мне вообще смотреть на finish-receipts?» -Эта страница намеренно остаётся только в пределах публичных chain-данных. Она показывает caller-транзакцию, более позднюю worker-транзакцию и finish-receipts. Она не пытается доказывать внутреннюю TEE-, `yield/resume`- или CKD/MPC-архитектуру OutLayer только по chain-trace. +Оставайтесь в пределах публичных chain-данных: прочитайте request tx, прочитайте более поздний resolution tx и только потом решайте, нужны ли вам finish-receipts. -Сначала смотрите на это как на задачу по истории транзакций: - -- один caller-side `request_execution` -- одна более поздняя worker-side транзакция `submit_execution_output_and_resolve` или `resolve_execution` -- переход к receipts только тогда, когда уже важен путь завершения - -## Проверенный shell-сценарий +## Компактный shell-сценарий Эта пара работала 18 апреля 2026 года: - caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` - worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` -### 1. Сразу раскройте caller-транзакцию и worker-транзакцию вместе +### 1. Основной ответ: сразу раскройте request tx и resolution tx вместе ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -59,16 +53,20 @@ jq '{ hash: .transaction.hash, signer_id: .transaction.signer_id, receiver_id: .transaction.receiver_id, - actions: [.transaction.actions[] | keys[0]], + methods: [ + .transaction.actions[] + | .FunctionCall.method_name? + | select(. != null) + ], first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) } ] }' /tmp/outlayer-pair.json ``` -Это и есть главный наблюдаемый цикл: сначала caller-side запрос, затем более позднее worker-side разрешение, с читаемыми signer-, method- и log-доказательствами для обеих транзакций. +Это и есть основной ответ: один request tx, один более поздний resolution tx и читаемые signer-, receiver-, method- и log-доказательства для обеих транзакций. -### 2. Читайте worker-receipts только тогда, когда уже важен finish-путь +### Необязательное продолжение: Что сделал finish-путь? ```bash jq --arg worker_tx_hash "$WORKER_TX_HASH" ' @@ -90,15 +88,15 @@ jq --arg worker_tx_hash "$WORKER_TX_HASH" ' ' /tmp/outlayer-pair.json ``` -На что смотреть: +Смотрите на самое маленькое читаемое доказательство finish-пути: - `FunctionCall`-receipts, которые продолжают finish-путь - логи списания вроде `[[yNEAR charged: "..."]]` - последующие `Transfer`-receipts, которые похожи на refund или settlement-движение -Именно здесь receipts становятся правильной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции относятся к одному запросу OutLayer?» +Именно здесь receipts становятся нужной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции здесь относятся друг к другу?» -### 3. Если двух хешей у вас ещё нет, сначала найдите их +### Необязательный шаг: сначала найдите два хеша ```bash curl -sS "$TX_BASE_URL/v0/account" \ @@ -110,12 +108,10 @@ curl -sS "$TX_BASE_URL/v0/account" \ }' ``` -Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это surface, который превращает их в читаемое доказательство. +Используйте это только если вы ещё не знаете пару хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` превращает их в читаемый ответ. ## Полезные связанные страницы - Transactions API: история аккаунта - Transactions API: транзакции по хешу - Transactions API: receipt по ID -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx index c63ac12..0591119 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx @@ -1,15 +1,15 @@ --- slug: /tx/socialdb-proofs -title: Расширенный паттерн provenance для SocialDB -description: Один расширенный паттерн, который начинается с читаемого значения SocialDB и восстанавливает транзакцию записи за ним. +title: Расширенный поиск записи SocialDB +description: Один короткий расширенный сценарий, который начинается с читаемого значения SocialDB и восстанавливает транзакцию записи за ним. displayed_sidebar: transactionsApiSidebar page_actions: - markdown --- -# Расширенный паттерн provenance для SocialDB +# Расширенный поиск записи SocialDB -Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому provenance. +Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому поиску записи. Для FastNear-first-задач сначала откройте [Transactions Examples](/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" @@ -128,71 +128,11 @@ jq '{ }' /tmp/mike-profile-transaction.json ``` -4. Завершите каноническим подтверждением текущего состояния через raw RPC. +Это и есть весь паттерн lookup-а: читаемое значение, block уровня поля, мост через receipt и payload транзакции. -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-profile-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - finality: "final", - current_name: ( - .result.result - | implode - | fromjson - | .[$account_id].profile.name - ) -}' /tmp/mike-profile-rpc.json -``` - -Это и есть весь provenance-паттерн: читаемое значение, block уровня поля, мост через receipt, payload транзакции и затем необязательное подтверждение текущего состояния. - -## Тот же паттерн для других ключей SocialDB - -Используйте тот же мост каждый раз, когда у вас уже есть читаемое значение SocialDB и его write-block: - -1. Прочитайте семантическое значение и `:block` из NEAR Social или начните с уже известного блока записи виджета. -2. Используйте Transactions API [`POST /v0/block`](/tx/block), чтобы восстановить receipt `*.near -> social.near` и хеш транзакции. -3. Используйте Transactions API [`POST /v0/transactions`](/tx/transactions), чтобы декодировать payload `social.near set`. -4. Используйте RPC [`query(call_function)`](/rpc/contract/call-function) только если всё ещё нужно каноническое подтверждение текущего состояния. - -### Вариант для связи подписки - -Тот же паттерн работает для читаемой связи подписки: - -- текущая связь: `mike.near -> mob.near` -- блок записи SocialDB: `79574924` -- receipt ID: `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th` -- хеш исходной транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - -Главное отличие от примера с профилем только в ключе и форме payload: читайте `mike.near/graph/follow/mob.near`, а затем декодируйте и `graph.follow`, и соответствующую запись `index.graph` из payload записи. - -### Вариант для исходника виджета - -Тот же паттерн снова работает, когда читаемый факт относится к ключу с исходником виджета: +Тот же мост работает и для других читаемых значений SocialDB: -- ключ виджета: `mob.near/widget/Profile` -- блок записи SocialDB: `86494825` -- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` -- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- вариант для связи подписки: `mike.near -> mob.near`, блок `79574924`, tx `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- вариант для исходника виджета: `mob.near/widget/Profile`, блок `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` -Главное отличие здесь в том, что вы обычно стартуете с уже известного блока записи виджета, а затем декодируете `.data["mob.near"].widget.Profile[""]` из исходного payload `social.near set`. +Ключевая идея не меняется: начните с читаемого значения и его write-block, восстановите receipt `*.near -> social.near` из блока, а затем декодируйте payload `social.near set` из исходной транзакции. diff --git a/sidebars.js b/sidebars.js index 3669c7e..648be21 100644 --- a/sidebars.js +++ b/sidebars.js @@ -215,10 +215,7 @@ const transactionsApiSidebar = withExamplesFooter( 'tx/blocks', 'tx/receipt', ], - { - id: 'tx/examples', - items: ['tx/berry-club', 'tx/outlayer'], - } + 'tx/examples' ); const transfersApiSidebar = hideEarlyApiFamilies diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index 8bc884d..e994db0 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -126,7 +126,59 @@ jq '{ **Зачем нужен следующий шаг?** -Так вопрос остаётся узким и практическим. Если ответ `true`, следующий реальный шаг обычно связан с `unstake` или `withdraw` в конкретном пуле. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. +Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. + +#### Необязательное продолжение: Что сделал этот контрактный вызов для делегирования? + +Используйте это продолжение, когда staking-эндпоинт уже показал пул вроде `polkachu.poolv1.near`, и теперь вы хотите увидеть форму одной реальной делегационной транзакции. + +Этот зафиксированный mainnet tx хорош тем, что очень ясно показывает весь паттерн: + +- хеш транзакции: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` +- top-level receiver: `polkachu.poolv1.near` +- top-level метод: `deposit_and_stake` +- attached deposit: `34650000000000000000000000` + +Важная форма chain-истории здесь такая: + +- делегатор отправляет `FunctionCall deposit_and_stake` в pool-контракт +- pool-контракт учитывает депозит и staking shares +- затем pool выпускает self-receipt с настоящим `Stake` action + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/staking-delegation-tx.json >/dev/null + +jq '{ + top_level_call: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + attached_deposit: .transactions[0].transaction.actions[0].FunctionCall.deposit + }, + pool_side_effects: [ + .transactions[0].receipts[] + | select(.receipt.receiver_id == "polkachu.poolv1.near") + | { + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + actions: ( + .receipt.receipt.Action.actions + | map(if type == "string" then . else keys[0] end) + ), + first_logs: (.execution_outcome.outcome.logs[:3]) + } + ] +}' /tmp/staking-delegation-tx.json +``` + +Простой вывод здесь такой: делегатор не подписывал сырой `Stake` action напрямую. Он вызвал staking-pool-контракт через `deposit_and_stake` и приложил депозит, а затем уже pool-контракт сам выполнил `Stake` action на своём аккаунте. ### Какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает? diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index 8bc884d..e994db0 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -126,7 +126,59 @@ jq '{ **Зачем нужен следующий шаг?** -Так вопрос остаётся узким и практическим. Если ответ `true`, следующий реальный шаг обычно связан с `unstake` или `withdraw` в конкретном пуле. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. +Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. + +#### Необязательное продолжение: Что сделал этот контрактный вызов для делегирования? + +Используйте это продолжение, когда staking-эндпоинт уже показал пул вроде `polkachu.poolv1.near`, и теперь вы хотите увидеть форму одной реальной делегационной транзакции. + +Этот зафиксированный mainnet tx хорош тем, что очень ясно показывает весь паттерн: + +- хеш транзакции: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` +- top-level receiver: `polkachu.poolv1.near` +- top-level метод: `deposit_and_stake` +- attached deposit: `34650000000000000000000000` + +Важная форма chain-истории здесь такая: + +- делегатор отправляет `FunctionCall deposit_and_stake` в pool-контракт +- pool-контракт учитывает депозит и staking shares +- затем pool выпускает self-receipt с настоящим `Stake` action + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/staking-delegation-tx.json >/dev/null + +jq '{ + top_level_call: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + attached_deposit: .transactions[0].transaction.actions[0].FunctionCall.deposit + }, + pool_side_effects: [ + .transactions[0].receipts[] + | select(.receipt.receiver_id == "polkachu.poolv1.near") + | { + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + actions: ( + .receipt.receipt.Action.actions + | map(if type == "string" then . else keys[0] end) + ), + first_logs: (.execution_outcome.outcome.logs[:3]) + } + ] +}' /tmp/staking-delegation-tx.json +``` + +Простой вывод здесь такой: делегатор не подписывал сырой `Stake` action напрямую. Он вызвал staking-pool-контракт через `deposit_and_stake` и приложил депозит, а затем уже pool-контракт сам выполнил `Stake` action на своём аккаунте. ### Какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает? diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index c500ab8..6d4c709 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -13,10 +13,10 @@ - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для чтения точных строк FastData, проверки истории точного ключа и привязки индексированной строки к исходной транзакции. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для чтения точных строк FastData, проверки истории точного ключа и необязательной привязки индексированной настройки к исходной транзакции. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. -- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. +- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для отправки транзакций, проверки прав ключа доступа, предварительной проверки FT, чтения сырого состояния и поиска Rainbow Bridge. - [API переводов](https://docs.fastnear.com/ru/transfers.md): История переводов NEAR и FT-токенов по аккаунтам для продуктовых лент и инструментов расследования. - [Транзакции API](https://docs.fastnear.com/ru/tx.md): Индексированные запросы по транзакциям, квитанциям, истории аккаунтов и истории блоков для FastNear. @@ -29,15 +29,15 @@ ## Другие гайды -- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и классификации стейкинга. +- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и проверки прямого стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных хешей для дальнейшего разбора. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных идентификаторов для дальнейшего разбора. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. -- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. -- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика, а затем для более глубоких case study, когда они действительно нужны. -- [Berry Club Case Study: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Case study, который начинается с живой доски Berry Club через RPC get_lines, а затем использует Transactions API, чтобы восстановить одну более раннюю эпоху. -- [OutLayer Case Study: трассировка запроса и разрешения воркером](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Case study, который использует Transactions API, чтобы связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать завершающие receipts. -- [Расширенный паттерн provenance для SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs.md): Один расширенный паттерн, который начинается с читаемого значения SocialDB и восстанавливает транзакцию записи за ним. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для проверки, было ли движение средств в одном окне, и необязательного перехода от одной строки к receipt. +- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика. +- [Berry Club: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Прочитайте живую доску Berry Club через RPC get_lines, а затем используйте Transactions API, чтобы восстановить одну более раннюю эпоху. +- [OutLayer: что сделала эта пара request/resolution?](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы прочитать один caller-side запрос OutLayer, одно более позднее worker-side resolution и обращаться к finish-receipts только когда это действительно нужно. +- [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs.md): Один короткий расширенный сценарий, который начинается с читаемого значения SocialDB и восстанавливает транзакцию записи за ним. ## Снапшоты diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 7d88629..f448066 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -1120,7 +1120,59 @@ jq '{ **Зачем нужен следующий шаг?** -Так вопрос остаётся узким и практическим. Если ответ `true`, следующий реальный шаг обычно связан с `unstake` или `withdraw` в конкретном пуле. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. +Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. + +#### Необязательное продолжение: Что сделал этот контрактный вызов для делегирования? + +Используйте это продолжение, когда staking-эндпоинт уже показал пул вроде `polkachu.poolv1.near`, и теперь вы хотите увидеть форму одной реальной делегационной транзакции. + +Этот зафиксированный mainnet tx хорош тем, что очень ясно показывает весь паттерн: + +- хеш транзакции: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` +- top-level receiver: `polkachu.poolv1.near` +- top-level метод: `deposit_and_stake` +- attached deposit: `34650000000000000000000000` + +Важная форма chain-истории здесь такая: + +- делегатор отправляет `FunctionCall deposit_and_stake` в pool-контракт +- pool-контракт учитывает депозит и staking shares +- затем pool выпускает self-receipt с настоящим `Stake` action + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/staking-delegation-tx.json >/dev/null + +jq '{ + top_level_call: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + attached_deposit: .transactions[0].transaction.actions[0].FunctionCall.deposit + }, + pool_side_effects: [ + .transactions[0].receipts[] + | select(.receipt.receiver_id == "polkachu.poolv1.near") + | { + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + actions: ( + .receipt.receipt.Action.actions + | map(if type == "string" then . else keys[0] end) + ), + first_logs: (.execution_outcome.outcome.logs[:3]) + } + ] +}' /tmp/staking-delegation-tx.json +``` + +Простой вывод здесь такой: делегатор не подписывал сырой `Stake` action напрямую. Он вызвал staking-pool-контракт через `deposit_and_stake` и приложил депозит, а затем уже pool-контракт сам выполнил `Stake` action на своём аккаунте. ### Какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает? @@ -2044,30 +2096,22 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC. Стратегия - Зафиксируйтесь на одном финализированном блоке, просканируйте всё семейство блока по целевому аккаунту, а затем оставьте только компактную сводку и идентификаторы, которые действительно стоит разбирать дальше. + Сначала ответьте на вопрос о контрактном touch, а затем оставьте только один tx hash или receipt id для следующего шага. 01last-block-final даёт одну стабильную высоту блока без угадывания. 02block — это главный read: он уже содержит транзакции, receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» - 03Только если ответ «да», расширяйтесь дальше: сохраняйте найденные shard id, tx hash и receipt id, а затем передавайте именно эти идентификаторы в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). + 03Только если ответ «да», расширяйтесь дальше: сохраните один точный tx hash или receipt id из того же сохранённого блока, а затем передайте этот идентификатор в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). **Цель** -- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только shard id, счётчики и sample-идентификаторы для следующего шага. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Последняя стабильная точка | NEAR Data [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Получаем высоту одного финализированного блока без угадывания | Даёт стабильную отправную точку для всего вопроса | -| Всё семейство блока | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) | Сканируем транзакции, receipts, результаты исполнения receipts и изменения состояния по целевому аккаунту | Это главная поверхность ответа на вопрос «был ли затронут мой контракт?» | -| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Используем, когда нужны только высота, хеш, время или заголовки чанков | Позволяет не тянуть более широкий payload блока, когда фильтрация по контракту не нужна | -| Необязательный follow-up по шарду | NEAR Data [`block-chunk`](https://docs.fastnear.com/ru/neardata/block-chunk) или [`block-shard`](https://docs.fastnear.com/ru/neardata/block-shard) | Повторно открываем только затронутый шард, если нужен более глубокий payload | Полезно, когда вы уже знаете, какой шард mattered | -| Точные поверхности для продолжения | [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc) | Переиспользуем найденные tx hash или receipt id только если нужна полная история исполнения | NEAR Data позволяет сначала понять, нужен ли вообще переход дальше | +- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только компактные счётчики плюс один точный идентификатор для следующего шага. **Что должен включать полезный ответ** - финализированную высоту и хеш - ответ “затронут / не затронут” - счётчики прямых транзакций, входящих receipts, outcome-hit и state changes -- по одному sample tx hash или receipt id на категорию, когда он есть +- один sample tx hash или receipt id, когда он есть ### Shell-сценарий от финализированного блока к ответу по контракту @@ -2090,10 +2134,7 @@ FINAL_LOCATION="$( | tr -d '\r' )" -BLOCK_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | sed -E 's#.*/([0-9]+)$#\1#')" - printf 'Final redirect target: %s\n' "$FINAL_LOCATION" -printf 'Final block height: %s\n' "$BLOCK_HEIGHT" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | tee /tmp/neardata-block.json >/dev/null @@ -2156,60 +2197,41 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' ' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json ``` -Если позже понадобятся более богатые списки или разбор по шардам, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. +Если позже понадобятся более богатые списки, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. -Необязательное расширение: если всё же нужны `touched_shards`, их можно вычислить из того же сохранённого блока, не утяжеляя основной ответ: +#### Необязательное продолжение: Какой tx hash или receipt id разбирать дальше? -```bash -jq --arg target "$TARGET_ACCOUNT_ID" ' - [ - .shards[] - | .shard_id as $shard_id - | select( - ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) - or ([.chunk.receipts[]? | .receiver_id] | index($target)) - or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) - or ([.state_changes[]? | .change.account_id] | index($target)) - ) - | $shard_id - ] | unique -' /tmp/neardata-block.json -``` - -Если в этом ответе `touched: true` и нужен один follow-up на уровне шарда, откройте только первый затронутый шард: +Используйте ту же сохранённую сводку и поднимите один точный идентификатор для следующей поверхности. ```bash -TOUCHED_SHARD_ID="$( - jq -r --arg target "$TARGET_ACCOUNT_ID" ' - first( - .shards[] - | .shard_id as $shard_id - | select( - ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) - or ([.chunk.receipts[]? | .receiver_id] | index($target)) - or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) - or ([.state_changes[]? | .change.account_id] | index($target)) - ) - | $shard_id - ) // empty - ' /tmp/neardata-block.json +FOLLOW_UP_KIND="$( + jq -r ' + if .sample_direct_tx != null then "tx_hash" + elif .sample_incoming_receipt != null then "receipt_id" + elif .sample_outcome_tx_hash != null then "tx_hash" + else "none" + end + ' /tmp/neardata-touch-summary.json )" -if [ -n "$TOUCHED_SHARD_ID" ]; then - curl -s "$NEARDATA_BASE_URL/v0/block/$BLOCK_HEIGHT/chunk/$TOUCHED_SHARD_ID" \ - | jq '{ - shard_id: .header.shard_id, - chunk_hash: .header.chunk_hash, - tx_hashes: ([.transactions[]? | (.transaction.hash // .hash)] | .[:5]), - receipt_ids: ([.receipts[]? | .receipt_id] | .[:5]), - receipt_receivers: ([.receipts[]? | .receiver_id] | .[:5]) - }' -fi +FOLLOW_UP_VALUE="$( + jq -r ' + .sample_direct_tx + // .sample_incoming_receipt + // .sample_outcome_tx_hash + // empty + ' /tmp/neardata-touch-summary.json +)" + +printf 'Next identifier kind: %s\n' "$FOLLOW_UP_KIND" +printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" ``` +Если идентификатор — это `tx_hash`, передайте его в [Transactions API](https://docs.fastnear.com/ru/tx) или RPC `tx` status. Если это `receipt_id`, передайте его в [Transactions API: Receipt by ID](https://docs.fastnear.com/ru/tx/receipt). И только после этого решайте, нужен ли вам вообще shard-level follow-up. + **Зачем нужен следующий шаг?** -Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один из sample-идентификаторов уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. +Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один точный tx hash или receipt id уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. ## Частые ошибки @@ -2217,7 +2239,7 @@ fi - Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. - Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. - Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. -- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data точные shard id, tx hash и receipt id. +- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data один точный tx hash или receipt id. ## Полезные связанные страницы @@ -3083,137 +3105,6 @@ PY Оставайтесь в RPC, пока вопрос звучит как «какие bridged token-контракты существуют и сколько одного такого токена сейчас в обращении?» Factory — это источник истины для множества bridged token-ов, а каждый token-контракт сам отвечает за свои метаданные и объём в обращении через стандартные NEP-141 view-методы. Если следующий вопрос становится «кто держит этот токен?», переключайтесь на [V1 FT Top Holders](https://docs.fastnear.com/ru/api/v1/ft-top), а не пытайтесь обходить holders через RPC. -## Точные чтения SocialDB - -Оставайтесь на точных чтениях через `call_function get`, когда вы уже знаете нужный ключ SocialDB. На обычном RPC raw `view_state` для `social.near` не подходит как обучающий путь, потому что состояние контракта слишком велико для прямого чтения. - -### Прочитать один пост SocialDB ровно в том виде, как он хранится сейчас - -Используйте этот сценарий, когда продукту, support-инструменту или агенту уже известен аккаунт и нужен живой payload поста из SocialDB без перехода в историю транзакций. - - Стратегия - Сначала прочитайте текущий ключ поста, затем получите точный payload этого поста из social.near. - - 01RPC call_function get по mike.near/index/post показывает, какой ключ поста сейчас активен. - 02RPC call_function get по mike.near/post/main возвращает точный сохранённый payload поста. - 03Если следующий вопрос становится «какая транзакция это записала?», переключайтесь на [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Что вы делаете** - -- Читаете текущий указатель поста под `mike.near/index/post`. -- Используете этот ключ, чтобы получить точный payload по `mike.near/post/`. -- Останавливаетесь на точном JSON и расширяетесь в историю только тогда, когда действительно нужна provenance. - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mike.near -``` - -1. Сначала прочитайте текущий указатель поста. - -```bash -INDEX_POST_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/index/post")] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$INDEX_POST_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-index-post.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | { - account_id: $account_id, - index_entry: (.[$account_id].index.post | fromjson), - current_post_key: (.[$account_id].index.post | fromjson | .key) - } -' /tmp/social-index-post.json -``` - -На момент написания текущим ключом поста для `mike.near` был `main`. - -2. Прочитайте точный payload этого поста. - -```bash -POST_KEY="$( - jq -r --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | .[$account_id].index.post - | fromjson - | .key - ' /tmp/social-index-post.json -)" - -POST_ARGS_BASE64="$( - jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg post_key "$POST_KEY" '{ - keys: [($account_id + "/post/" + $post_key)] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$POST_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-post-main.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg post_key "$POST_KEY" ' - .result.result - | implode - | fromjson - | { - account_id: $account_id, - post_key: $post_key, - post: (.[$account_id].post[$post_key] | fromjson) - } -' /tmp/social-post-main.json -``` - -Так вы получаете точный JSON, который хранится для текущего поста, включая поля вроде `type`, `text` и `image`. - -**Зачем нужен следующий шаг?** - -Это чистый RPC-паттерн для SocialDB: спросите у контракта один точный ключ, декодируйте возвращённый JSON и остановитесь. Если вопрос превращается в «кто и когда это записал?», переходите к примерам по транзакциям, а не пытайтесь brute-force читать raw state `social.near`. - ## Частые ошибки - Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. @@ -3770,30 +3661,29 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ }' ``` -Это самый короткий путь к вопросу «были ли здесь движения средств и какой receipt брать следующим?» +Это самый короткий путь к вопросу «были ли здесь движения средств и какую строку стоит разбирать дальше?» ## Готовый сценарий -### Найти один исходящий перевод и при необходимости перейти к деталям исполнения +### Отправлял ли этот аккаунт средства в этом окне и какую строку стоит разобрать? -Используйте этот сценарий, когда история звучит так: «я знаю, что этот аккаунт отправлял средства в этом окне, и мне может понадобиться точная опорная точка исполнения для одной строки, но я не хочу сразу тянуть всю историю аккаунта». +Используйте этот сценарий, когда история звучит так: «мне сначала нужно одно узкое окно исходящих переводов, и только после просмотра строк я решу, нужен ли одной из них follow-up по receipt». Стратегия - Сначала оставайтесь на узкой истории движения, а затем переходите в историю исполнения только если строки перевода уже недостаточно. + Сначала ответьте на вопрос о движении средств, а затем расширяйтесь только если одной строке всё ещё нужен execution-anchor. 01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять. 02Сначала выведите строки, а затем явно выберите один transfer_index перед тем, как поднимать его receipt_id. - 03POST /v0/receipt — это необязательное расширение, когда уже нужны детали исполнения именно за этим переводом. + 03POST /v0/receipt — необязательный follow-up, когда вы хотите понять, что именно эта строка перевода сделала on-chain. **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Выделяете одну строку перевода, которая действительно похожа на нужное вам движение. -- Переиспользуете его `receipt_id` в Transactions API только если нужно перейти от движения актива к истории исполнения. +- Сначала выводите строки, а затем выбираете одну строку перевода, которая действительно похожа на нужное вам движение. +- Переиспользуете её `receipt_id` только если нужно перейти от движения актива к истории исполнения. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=YOUR_ACCOUNT_ID FROM_TIMESTAMP_MS=1711929600000 TO_TIMESTAMP_MS=1712016000000 @@ -3839,6 +3729,16 @@ RECEIPT_ID="$( printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" +``` + +Этим вы отвечаете на первый вопрос: было ли здесь движение средств и какую строку перевода стоит разбирать дальше? + +#### Необязательное продолжение: Что сделала эта строка перевода on-chain? + +Переходите к истории receipt только если самой строки перевода уже недостаточно. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com if [ -n "$RECEIPT_ID" ]; then curl -s "$TX_BASE_URL/v0/receipt" \ @@ -3855,7 +3755,7 @@ fi **Зачем нужен следующий шаг?** -Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный следующий шаг, когда самой строки перевода уже недостаточно и нужна опорная точка в истории исполнения. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. +Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный второй вопрос: какая execution-anchor стоит за этой одной строкой? Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. ## Частые ошибки @@ -3977,8 +3877,6 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. -Если нужен более развёрнутый case study на той же поверхности, переходите к [Berry Club case study](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer case study](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. - ## С чего начать Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. @@ -4930,59 +4828,6 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. -## Расширенные сценарии и case study - -Примеры ниже всё ещё полезны, но они длиннее или более специализированы, чем основные start-here-сценарии выше. `Berry Club` и `OutLayer` вынесены в отдельные case-study-страницы, паттерн provenance для SocialDB теперь живёт на отдельной расширенной странице, а последний пример здесь оставляет только компактный multi-contract follow-up. - -### Расширенный паттерн provenance для SocialDB - -Если читаемый факт уже приходит из `api.near.social`, держите follow-up маленьким: сначала семантическое значение, затем `:block`, потом lookup по блоку и транзакции в FastNear. Для одного канонического примера такого сценария используйте [отдельную страницу паттерна provenance для SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs). - -### Расширенный сценарий: какие downstream-контракты затронула эта транзакция? - -Используйте этот сценарий, когда у вас уже есть один multi-contract tx hash и следующий вопрос звучит просто: «в какие контракты ушёл этот вызов после top-level action?» - -Этот зафиксированный mainnet-якорь по-прежнему хорошо подходит как пример, хотя сама транзакция и относится к `intents.near`: - -- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` -- аккаунт `signer` и `receiver`: `intents.near` -- высота включающего блока: `194573310` - -Короткий ответ для этой tx уже полезен: - -- top-level метод был `execute_intents` -- ранние downstream-receipt затронули `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` -- в более поздних логах были семейства событий вроде `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` - -Для большинства вопросов достаточно Transactions API: - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name - }, - downstream_receivers: ( - [.transactions[0].receipts[] | .receipt.receiver_id] - | unique - ), - first_logs: ( - [.transactions[0].receipts[] | .execution_outcome.outcome.logs[]?] - | .[:5] - ) - }' -``` - -Если нужен включающий блок, расширяйтесь один раз до Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block). Если нужен канонический DAG по receipt или сырые логи `EVENT_JSON`, расширяйтесь ещё на один шаг до RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status). Учебная идея здесь общая: начинайте с одного tx hash, перечислите downstream receiver-и и останавливайтесь, пока trace действительно не требует большего. - ## Частые ошибки - Пытаться отправлять транзакцию через history API вместо сырого RPC. @@ -4996,23 +4841,26 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - [RPC Reference](https://docs.fastnear.com/ru/rpc) - [FastNear API](https://docs.fastnear.com/ru/api) - [NEAR Data API](https://docs.fastnear.com/ru/neardata) +- [Berry Club: живая доска и один путь исторической реконструкции](https://docs.fastnear.com/ru/tx/examples/berry-club) +- [OutLayer: связать одну транзакцию запроса с одним ответом воркера](https://docs.fastnear.com/ru/tx/examples/outlayer) +- [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) --- -## Berry Club Case Study: как читать живую доску и разбирать одну эпоху +## Berry Club: как читать живую доску и разбирать одну эпоху - HTML-маршрут: https://docs.fastnear.com/ru/tx/examples/berry-club - Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples/berry-club.md **Источник:** [https://docs.fastnear.com/ru/tx/examples/berry-club](https://docs.fastnear.com/ru/tx/examples/berry-club) -{/* FASTNEAR_AI_DISCOVERY: Этот case study показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} +{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} -# Berry Club Case Study: как читать живую доску и разбирать одну эпоху +# Berry Club: как читать живую доску и разбирать одну эпоху -Используйте этот case study, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. +Используйте этот walkthrough, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. Начните с живой доски. Если этого уже достаточно для ответа, на этом можно остановиться. @@ -5116,35 +4964,29 @@ for (const drawTx of drawTransactionsOldestFirst) { --- -## OutLayer Case Study: трассировка запроса и разрешения воркером +## OutLayer: что сделала эта пара request/resolution? - HTML-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer - Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer.md **Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) -{/* FASTNEAR_AI_DISCOVERY: Этот case study остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} - -# OutLayer Case Study: трассировка запроса и разрешения воркером - -Используйте этот case study, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» +{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как прочитать один caller-side запрос OutLayer вместе с более поздним worker-side resolution, а затем разобрать finish-receipts только если это действительно нужно. */} -Эта страница намеренно остаётся только в пределах публичных chain-данных. Она показывает caller-транзакцию, более позднюю worker-транзакцию и finish-receipts. Она не пытается доказывать внутреннюю TEE-, `yield/resume`- или CKD/MPC-архитектуру OutLayer только по chain-trace. +# OutLayer: что сделала эта пара request/resolution? -Сначала смотрите на это как на задачу по истории транзакций: +Используйте этот walkthrough, когда вопрос звучит так: «что сделал этот запрос OutLayer, какое более позднее resolution к нему относится и нужно ли мне вообще смотреть на finish-receipts?» -- один caller-side `request_execution` -- одна более поздняя worker-side транзакция `submit_execution_output_and_resolve` или `resolve_execution` -- переход к receipts только тогда, когда уже важен путь завершения +Оставайтесь в пределах публичных chain-данных: прочитайте request tx, прочитайте более поздний resolution tx и только потом решайте, нужны ли вам finish-receipts. -## Проверенный shell-сценарий +## Компактный shell-сценарий Эта пара работала 18 апреля 2026 года: - caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` - worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` -### 1. Сразу раскройте caller-транзакцию и worker-транзакцию вместе +### 1. Основной ответ: сразу раскройте request tx и resolution tx вместе ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -5165,16 +5007,20 @@ jq '{ hash: .transaction.hash, signer_id: .transaction.signer_id, receiver_id: .transaction.receiver_id, - actions: [.transaction.actions[] | keys[0]], + methods: [ + .transaction.actions[] + | .FunctionCall.method_name? + | select(. != null) + ], first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) } ] }' /tmp/outlayer-pair.json ``` -Это и есть главный наблюдаемый цикл: сначала caller-side запрос, затем более позднее worker-side разрешение, с читаемыми signer-, method- и log-доказательствами для обеих транзакций. +Это и есть основной ответ: один request tx, один более поздний resolution tx и читаемые signer-, receiver-, method- и log-доказательства для обеих транзакций. -### 2. Читайте worker-receipts только тогда, когда уже важен finish-путь +### Необязательное продолжение: Что сделал finish-путь? ```bash jq --arg worker_tx_hash "$WORKER_TX_HASH" ' @@ -5196,15 +5042,15 @@ jq --arg worker_tx_hash "$WORKER_TX_HASH" ' ' /tmp/outlayer-pair.json ``` -На что смотреть: +Смотрите на самое маленькое читаемое доказательство finish-пути: - `FunctionCall`-receipts, которые продолжают finish-путь - логи списания вроде `[[yNEAR charged: "..."]]` - последующие `Transfer`-receipts, которые похожи на refund или settlement-движение -Именно здесь receipts становятся правильной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции относятся к одному запросу OutLayer?» +Именно здесь receipts становятся нужной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции здесь относятся друг к другу?» -### 3. Если двух хешей у вас ещё нет, сначала найдите их +### Необязательный шаг: сначала найдите два хеша ```bash curl -sS "$TX_BASE_URL/v0/account" \ @@ -5216,28 +5062,26 @@ curl -sS "$TX_BASE_URL/v0/account" \ }' ``` -Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это surface, который превращает их в читаемое доказательство. +Используйте это только если вы ещё не знаете пару хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` превращает их в читаемый ответ. ## Полезные связанные страницы - [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) - [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) - [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) --- -## Расширенный паттерн provenance для SocialDB +## Расширенный поиск записи SocialDB - HTML-маршрут: https://docs.fastnear.com/ru/tx/socialdb-proofs - Markdown-маршрут: https://docs.fastnear.com/ru/tx/socialdb-proofs.md **Источник:** [https://docs.fastnear.com/ru/tx/socialdb-proofs](https://docs.fastnear.com/ru/tx/socialdb-proofs) -# Расширенный паттерн provenance для SocialDB +# Расширенный поиск записи SocialDB -Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому provenance. +Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому поиску записи. Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" @@ -5356,74 +5200,14 @@ jq '{ }' /tmp/mike-profile-transaction.json ``` -4. Завершите каноническим подтверждением текущего состояния через raw RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-profile-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - finality: "final", - current_name: ( - .result.result - | implode - | fromjson - | .[$account_id].profile.name - ) -}' /tmp/mike-profile-rpc.json -``` - -Это и есть весь provenance-паттерн: читаемое значение, block уровня поля, мост через receipt, payload транзакции и затем необязательное подтверждение текущего состояния. - -## Тот же паттерн для других ключей SocialDB - -Используйте тот же мост каждый раз, когда у вас уже есть читаемое значение SocialDB и его write-block: - -1. Прочитайте семантическое значение и `:block` из NEAR Social или начните с уже известного блока записи виджета. -2. Используйте Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block), чтобы восстановить receipt `*.near -> social.near` и хеш транзакции. -3. Используйте Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), чтобы декодировать payload `social.near set`. -4. Используйте RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) только если всё ещё нужно каноническое подтверждение текущего состояния. - -### Вариант для связи подписки - -Тот же паттерн работает для читаемой связи подписки: - -- текущая связь: `mike.near -> mob.near` -- блок записи SocialDB: `79574924` -- receipt ID: `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th` -- хеш исходной транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - -Главное отличие от примера с профилем только в ключе и форме payload: читайте `mike.near/graph/follow/mob.near`, а затем декодируйте и `graph.follow`, и соответствующую запись `index.graph` из payload записи. - -### Вариант для исходника виджета +Это и есть весь паттерн lookup-а: читаемое значение, block уровня поля, мост через receipt и payload транзакции. -Тот же паттерн снова работает, когда читаемый факт относится к ключу с исходником виджета: +Тот же мост работает и для других читаемых значений SocialDB: -- ключ виджета: `mob.near/widget/Profile` -- блок записи SocialDB: `86494825` -- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` -- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- вариант для связи подписки: `mike.near -> mob.near`, блок `79574924`, tx `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- вариант для исходника виджета: `mob.near/widget/Profile`, блок `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` -Главное отличие здесь в том, что вы обычно стартуете с уже известного блока записи виджета, а затем декодируете `.data["mob.near"].widget.Profile[""]` из исходного payload `social.near set`. +Ключевая идея не меняется: начните с читаемого значения и его write-block, восстановите receipt `*.near -> social.near` из блока, а затем декодируйте payload `social.near set` из исходной транзакции. --- diff --git a/static/ru/llms.txt b/static/ru/llms.txt index a46fe5d..c3b606e 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -16,10 +16,10 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для чтения точных строк FastData, проверки истории точного ключа и привязки индексированной строки к исходной транзакции. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для чтения точных строк FastData, проверки истории точного ключа и необязательной привязки индексированной настройки к исходной транзакции. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. -- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. +- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для отправки транзакций, проверки прав ключа доступа, предварительной проверки FT, чтения сырого состояния и поиска Rainbow Bridge. - [API переводов](https://docs.fastnear.com/ru/transfers.md): История переводов NEAR и FT-токенов по аккаунтам для продуктовых лент и инструментов расследования. - [Транзакции API](https://docs.fastnear.com/ru/tx.md): Индексированные запросы по транзакциям, квитанциям, истории аккаунтов и истории блоков для FastNear. @@ -32,15 +32,15 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп ## Другие гайды -- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и классификации стейкинга. +- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и проверки прямого стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных хешей для дальнейшего разбора. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных идентификаторов для дальнейшего разбора. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. -- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. -- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика, а затем для более глубоких case study, когда они действительно нужны. -- [Berry Club Case Study: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Case study, который начинается с живой доски Berry Club через RPC get_lines, а затем использует Transactions API, чтобы восстановить одну более раннюю эпоху. -- [OutLayer Case Study: трассировка запроса и разрешения воркером](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Case study, который использует Transactions API, чтобы связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать завершающие receipts. -- [Расширенный паттерн provenance для SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs.md): Один расширенный паттерн, который начинается с читаемого значения SocialDB и восстанавливает транзакцию записи за ним. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для проверки, было ли движение средств в одном окне, и необязательного перехода от одной строки к receipt. +- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика. +- [Berry Club: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Прочитайте живую доску Berry Club через RPC get_lines, а затем используйте Transactions API, чтобы восстановить одну более раннюю эпоху. +- [OutLayer: что сделала эта пара request/resolution?](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы прочитать один caller-side запрос OutLayer, одно более позднее worker-side resolution и обращаться к finish-receipts только когда это действительно нужно. +- [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs.md): Один короткий расширенный сценарий, который начинается с читаемого значения SocialDB и восстанавливает транзакцию записи за ним. ## Снапшоты diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index a911f9b..c82c3aa 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -48,30 +48,22 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC. Стратегия - Зафиксируйтесь на одном финализированном блоке, просканируйте всё семейство блока по целевому аккаунту, а затем оставьте только компактную сводку и идентификаторы, которые действительно стоит разбирать дальше. + Сначала ответьте на вопрос о контрактном touch, а затем оставьте только один tx hash или receipt id для следующего шага. 01last-block-final даёт одну стабильную высоту блока без угадывания. 02block — это главный read: он уже содержит транзакции, receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» - 03Только если ответ «да», расширяйтесь дальше: сохраняйте найденные shard id, tx hash и receipt id, а затем передавайте именно эти идентификаторы в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). + 03Только если ответ «да», расширяйтесь дальше: сохраните один точный tx hash или receipt id из того же сохранённого блока, а затем передайте этот идентификатор в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). **Цель** -- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только shard id, счётчики и sample-идентификаторы для следующего шага. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Последняя стабильная точка | NEAR Data [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Получаем высоту одного финализированного блока без угадывания | Даёт стабильную отправную точку для всего вопроса | -| Всё семейство блока | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) | Сканируем транзакции, receipts, результаты исполнения receipts и изменения состояния по целевому аккаунту | Это главная поверхность ответа на вопрос «был ли затронут мой контракт?» | -| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Используем, когда нужны только высота, хеш, время или заголовки чанков | Позволяет не тянуть более широкий payload блока, когда фильтрация по контракту не нужна | -| Необязательный follow-up по шарду | NEAR Data [`block-chunk`](https://docs.fastnear.com/ru/neardata/block-chunk) или [`block-shard`](https://docs.fastnear.com/ru/neardata/block-shard) | Повторно открываем только затронутый шард, если нужен более глубокий payload | Полезно, когда вы уже знаете, какой шард mattered | -| Точные поверхности для продолжения | [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc) | Переиспользуем найденные tx hash или receipt id только если нужна полная история исполнения | NEAR Data позволяет сначала понять, нужен ли вообще переход дальше | +- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только компактные счётчики плюс один точный идентификатор для следующего шага. **Что должен включать полезный ответ** - финализированную высоту и хеш - ответ “затронут / не затронут” - счётчики прямых транзакций, входящих receipts, outcome-hit и state changes -- по одному sample tx hash или receipt id на категорию, когда он есть +- один sample tx hash или receipt id, когда он есть ### Shell-сценарий от финализированного блока к ответу по контракту @@ -94,10 +86,7 @@ FINAL_LOCATION="$( | tr -d '\r' )" -BLOCK_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | sed -E 's#.*/([0-9]+)$#\1#')" - printf 'Final redirect target: %s\n' "$FINAL_LOCATION" -printf 'Final block height: %s\n' "$BLOCK_HEIGHT" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | tee /tmp/neardata-block.json >/dev/null @@ -160,60 +149,41 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' ' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json ``` -Если позже понадобятся более богатые списки или разбор по шардам, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. +Если позже понадобятся более богатые списки, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. -Необязательное расширение: если всё же нужны `touched_shards`, их можно вычислить из того же сохранённого блока, не утяжеляя основной ответ: - -```bash -jq --arg target "$TARGET_ACCOUNT_ID" ' - [ - .shards[] - | .shard_id as $shard_id - | select( - ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) - or ([.chunk.receipts[]? | .receiver_id] | index($target)) - or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) - or ([.state_changes[]? | .change.account_id] | index($target)) - ) - | $shard_id - ] | unique -' /tmp/neardata-block.json -``` +#### Необязательное продолжение: Какой tx hash или receipt id разбирать дальше? -Если в этом ответе `touched: true` и нужен один follow-up на уровне шарда, откройте только первый затронутый шард: +Используйте ту же сохранённую сводку и поднимите один точный идентификатор для следующей поверхности. ```bash -TOUCHED_SHARD_ID="$( - jq -r --arg target "$TARGET_ACCOUNT_ID" ' - first( - .shards[] - | .shard_id as $shard_id - | select( - ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) - or ([.chunk.receipts[]? | .receiver_id] | index($target)) - or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) - or ([.state_changes[]? | .change.account_id] | index($target)) - ) - | $shard_id - ) // empty - ' /tmp/neardata-block.json +FOLLOW_UP_KIND="$( + jq -r ' + if .sample_direct_tx != null then "tx_hash" + elif .sample_incoming_receipt != null then "receipt_id" + elif .sample_outcome_tx_hash != null then "tx_hash" + else "none" + end + ' /tmp/neardata-touch-summary.json )" -if [ -n "$TOUCHED_SHARD_ID" ]; then - curl -s "$NEARDATA_BASE_URL/v0/block/$BLOCK_HEIGHT/chunk/$TOUCHED_SHARD_ID" \ - | jq '{ - shard_id: .header.shard_id, - chunk_hash: .header.chunk_hash, - tx_hashes: ([.transactions[]? | (.transaction.hash // .hash)] | .[:5]), - receipt_ids: ([.receipts[]? | .receipt_id] | .[:5]), - receipt_receivers: ([.receipts[]? | .receiver_id] | .[:5]) - }' -fi +FOLLOW_UP_VALUE="$( + jq -r ' + .sample_direct_tx + // .sample_incoming_receipt + // .sample_outcome_tx_hash + // empty + ' /tmp/neardata-touch-summary.json +)" + +printf 'Next identifier kind: %s\n' "$FOLLOW_UP_KIND" +printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" ``` +Если идентификатор — это `tx_hash`, передайте его в [Transactions API](https://docs.fastnear.com/ru/tx) или RPC `tx` status. Если это `receipt_id`, передайте его в [Transactions API: Receipt by ID](https://docs.fastnear.com/ru/tx/receipt). И только после этого решайте, нужен ли вам вообще shard-level follow-up. + **Зачем нужен следующий шаг?** -Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один из sample-идентификаторов уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. +Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один точный tx hash или receipt id уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. ## Частые ошибки @@ -221,7 +191,7 @@ fi - Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. - Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. - Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. -- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data точные shard id, tx hash и receipt id. +- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data один точный tx hash или receipt id. ## Полезные связанные страницы diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index a911f9b..c82c3aa 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -48,30 +48,22 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC. Стратегия - Зафиксируйтесь на одном финализированном блоке, просканируйте всё семейство блока по целевому аккаунту, а затем оставьте только компактную сводку и идентификаторы, которые действительно стоит разбирать дальше. + Сначала ответьте на вопрос о контрактном touch, а затем оставьте только один tx hash или receipt id для следующего шага. 01last-block-final даёт одну стабильную высоту блока без угадывания. 02block — это главный read: он уже содержит транзакции, receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» - 03Только если ответ «да», расширяйтесь дальше: сохраняйте найденные shard id, tx hash и receipt id, а затем передавайте именно эти идентификаторы в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). + 03Только если ответ «да», расширяйтесь дальше: сохраните один точный tx hash или receipt id из того же сохранённого блока, а затем передайте этот идентификатор в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). **Цель** -- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только shard id, счётчики и sample-идентификаторы для следующего шага. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Последняя стабильная точка | NEAR Data [`last-block-final`](https://docs.fastnear.com/ru/neardata/last-block-final) | Получаем высоту одного финализированного блока без угадывания | Даёт стабильную отправную точку для всего вопроса | -| Всё семейство блока | NEAR Data [`block`](https://docs.fastnear.com/ru/neardata/block) | Сканируем транзакции, receipts, результаты исполнения receipts и изменения состояния по целевому аккаунту | Это главная поверхность ответа на вопрос «был ли затронут мой контракт?» | -| Лёгкая сводка по блоку | NEAR Data [`block-headers`](https://docs.fastnear.com/ru/neardata/block-headers) | Используем, когда нужны только высота, хеш, время или заголовки чанков | Позволяет не тянуть более широкий payload блока, когда фильтрация по контракту не нужна | -| Необязательный follow-up по шарду | NEAR Data [`block-chunk`](https://docs.fastnear.com/ru/neardata/block-chunk) или [`block-shard`](https://docs.fastnear.com/ru/neardata/block-shard) | Повторно открываем только затронутый шард, если нужен более глубокий payload | Полезно, когда вы уже знаете, какой шард mattered | -| Точные поверхности для продолжения | [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc) | Переиспользуем найденные tx hash или receipt id только если нужна полная история исполнения | NEAR Data позволяет сначала понять, нужен ли вообще переход дальше | +- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только компактные счётчики плюс один точный идентификатор для следующего шага. **Что должен включать полезный ответ** - финализированную высоту и хеш - ответ “затронут / не затронут” - счётчики прямых транзакций, входящих receipts, outcome-hit и state changes -- по одному sample tx hash или receipt id на категорию, когда он есть +- один sample tx hash или receipt id, когда он есть ### Shell-сценарий от финализированного блока к ответу по контракту @@ -94,10 +86,7 @@ FINAL_LOCATION="$( | tr -d '\r' )" -BLOCK_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | sed -E 's#.*/([0-9]+)$#\1#')" - printf 'Final redirect target: %s\n' "$FINAL_LOCATION" -printf 'Final block height: %s\n' "$BLOCK_HEIGHT" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | tee /tmp/neardata-block.json >/dev/null @@ -160,60 +149,41 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' ' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json ``` -Если позже понадобятся более богатые списки или разбор по шардам, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. +Если позже понадобятся более богатые списки, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. -Необязательное расширение: если всё же нужны `touched_shards`, их можно вычислить из того же сохранённого блока, не утяжеляя основной ответ: - -```bash -jq --arg target "$TARGET_ACCOUNT_ID" ' - [ - .shards[] - | .shard_id as $shard_id - | select( - ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) - or ([.chunk.receipts[]? | .receiver_id] | index($target)) - or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) - or ([.state_changes[]? | .change.account_id] | index($target)) - ) - | $shard_id - ] | unique -' /tmp/neardata-block.json -``` +#### Необязательное продолжение: Какой tx hash или receipt id разбирать дальше? -Если в этом ответе `touched: true` и нужен один follow-up на уровне шарда, откройте только первый затронутый шард: +Используйте ту же сохранённую сводку и поднимите один точный идентификатор для следующей поверхности. ```bash -TOUCHED_SHARD_ID="$( - jq -r --arg target "$TARGET_ACCOUNT_ID" ' - first( - .shards[] - | .shard_id as $shard_id - | select( - ([.chunk.transactions[]? | (.transaction.receiver_id // .receiver_id)] | index($target)) - or ([.chunk.receipts[]? | .receiver_id] | index($target)) - or ([.receipt_execution_outcomes[]? | .receipt.receiver_id, .execution_outcome.outcome.executor_id] | index($target)) - or ([.state_changes[]? | .change.account_id] | index($target)) - ) - | $shard_id - ) // empty - ' /tmp/neardata-block.json +FOLLOW_UP_KIND="$( + jq -r ' + if .sample_direct_tx != null then "tx_hash" + elif .sample_incoming_receipt != null then "receipt_id" + elif .sample_outcome_tx_hash != null then "tx_hash" + else "none" + end + ' /tmp/neardata-touch-summary.json )" -if [ -n "$TOUCHED_SHARD_ID" ]; then - curl -s "$NEARDATA_BASE_URL/v0/block/$BLOCK_HEIGHT/chunk/$TOUCHED_SHARD_ID" \ - | jq '{ - shard_id: .header.shard_id, - chunk_hash: .header.chunk_hash, - tx_hashes: ([.transactions[]? | (.transaction.hash // .hash)] | .[:5]), - receipt_ids: ([.receipts[]? | .receipt_id] | .[:5]), - receipt_receivers: ([.receipts[]? | .receiver_id] | .[:5]) - }' -fi +FOLLOW_UP_VALUE="$( + jq -r ' + .sample_direct_tx + // .sample_incoming_receipt + // .sample_outcome_tx_hash + // empty + ' /tmp/neardata-touch-summary.json +)" + +printf 'Next identifier kind: %s\n' "$FOLLOW_UP_KIND" +printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" ``` +Если идентификатор — это `tx_hash`, передайте его в [Transactions API](https://docs.fastnear.com/ru/tx) или RPC `tx` status. Если это `receipt_id`, передайте его в [Transactions API: Receipt by ID](https://docs.fastnear.com/ru/tx/receipt). И только после этого решайте, нужен ли вам вообще shard-level follow-up. + **Зачем нужен следующий шаг?** -Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один из sample-идентификаторов уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. +Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один точный tx hash или receipt id уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. ## Частые ошибки @@ -221,7 +191,7 @@ fi - Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. - Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. - Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. -- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data точные shard id, tx hash и receipt id. +- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data один точный tx hash или receipt id. ## Полезные связанные страницы diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index 1808b0e..937c48f 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -699,137 +699,6 @@ PY Оставайтесь в RPC, пока вопрос звучит как «какие bridged token-контракты существуют и сколько одного такого токена сейчас в обращении?» Factory — это источник истины для множества bridged token-ов, а каждый token-контракт сам отвечает за свои метаданные и объём в обращении через стандартные NEP-141 view-методы. Если следующий вопрос становится «кто держит этот токен?», переключайтесь на [V1 FT Top Holders](https://docs.fastnear.com/ru/api/v1/ft-top), а не пытайтесь обходить holders через RPC. -## Точные чтения SocialDB - -Оставайтесь на точных чтениях через `call_function get`, когда вы уже знаете нужный ключ SocialDB. На обычном RPC raw `view_state` для `social.near` не подходит как обучающий путь, потому что состояние контракта слишком велико для прямого чтения. - -### Прочитать один пост SocialDB ровно в том виде, как он хранится сейчас - -Используйте этот сценарий, когда продукту, support-инструменту или агенту уже известен аккаунт и нужен живой payload поста из SocialDB без перехода в историю транзакций. - - Стратегия - Сначала прочитайте текущий ключ поста, затем получите точный payload этого поста из social.near. - - 01RPC call_function get по mike.near/index/post показывает, какой ключ поста сейчас активен. - 02RPC call_function get по mike.near/post/main возвращает точный сохранённый payload поста. - 03Если следующий вопрос становится «какая транзакция это записала?», переключайтесь на [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Что вы делаете** - -- Читаете текущий указатель поста под `mike.near/index/post`. -- Используете этот ключ, чтобы получить точный payload по `mike.near/post/`. -- Останавливаетесь на точном JSON и расширяетесь в историю только тогда, когда действительно нужна provenance. - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mike.near -``` - -1. Сначала прочитайте текущий указатель поста. - -```bash -INDEX_POST_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/index/post")] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$INDEX_POST_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-index-post.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | { - account_id: $account_id, - index_entry: (.[$account_id].index.post | fromjson), - current_post_key: (.[$account_id].index.post | fromjson | .key) - } -' /tmp/social-index-post.json -``` - -На момент написания текущим ключом поста для `mike.near` был `main`. - -2. Прочитайте точный payload этого поста. - -```bash -POST_KEY="$( - jq -r --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | .[$account_id].index.post - | fromjson - | .key - ' /tmp/social-index-post.json -)" - -POST_ARGS_BASE64="$( - jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg post_key "$POST_KEY" '{ - keys: [($account_id + "/post/" + $post_key)] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$POST_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-post-main.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg post_key "$POST_KEY" ' - .result.result - | implode - | fromjson - | { - account_id: $account_id, - post_key: $post_key, - post: (.[$account_id].post[$post_key] | fromjson) - } -' /tmp/social-post-main.json -``` - -Так вы получаете точный JSON, который хранится для текущего поста, включая поля вроде `type`, `text` и `image`. - -**Зачем нужен следующий шаг?** - -Это чистый RPC-паттерн для SocialDB: спросите у контракта один точный ключ, декодируйте возвращённый JSON и остановитесь. Если вопрос превращается в «кто и когда это записал?», переходите к примерам по транзакциям, а не пытайтесь brute-force читать raw state `social.near`. - ## Частые ошибки - Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index 1808b0e..937c48f 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -699,137 +699,6 @@ PY Оставайтесь в RPC, пока вопрос звучит как «какие bridged token-контракты существуют и сколько одного такого токена сейчас в обращении?» Factory — это источник истины для множества bridged token-ов, а каждый token-контракт сам отвечает за свои метаданные и объём в обращении через стандартные NEP-141 view-методы. Если следующий вопрос становится «кто держит этот токен?», переключайтесь на [V1 FT Top Holders](https://docs.fastnear.com/ru/api/v1/ft-top), а не пытайтесь обходить holders через RPC. -## Точные чтения SocialDB - -Оставайтесь на точных чтениях через `call_function get`, когда вы уже знаете нужный ключ SocialDB. На обычном RPC raw `view_state` для `social.near` не подходит как обучающий путь, потому что состояние контракта слишком велико для прямого чтения. - -### Прочитать один пост SocialDB ровно в том виде, как он хранится сейчас - -Используйте этот сценарий, когда продукту, support-инструменту или агенту уже известен аккаунт и нужен живой payload поста из SocialDB без перехода в историю транзакций. - - Стратегия - Сначала прочитайте текущий ключ поста, затем получите точный payload этого поста из social.near. - - 01RPC call_function get по mike.near/index/post показывает, какой ключ поста сейчас активен. - 02RPC call_function get по mike.near/post/main возвращает точный сохранённый payload поста. - 03Если следующий вопрос становится «какая транзакция это записала?», переключайтесь на [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Что вы делаете** - -- Читаете текущий указатель поста под `mike.near/index/post`. -- Используете этот ключ, чтобы получить точный payload по `mike.near/post/`. -- Останавливаетесь на точном JSON и расширяетесь в историю только тогда, когда действительно нужна provenance. - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mike.near -``` - -1. Сначала прочитайте текущий указатель поста. - -```bash -INDEX_POST_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/index/post")] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$INDEX_POST_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-index-post.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | { - account_id: $account_id, - index_entry: (.[$account_id].index.post | fromjson), - current_post_key: (.[$account_id].index.post | fromjson | .key) - } -' /tmp/social-index-post.json -``` - -На момент написания текущим ключом поста для `mike.near` был `main`. - -2. Прочитайте точный payload этого поста. - -```bash -POST_KEY="$( - jq -r --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | .[$account_id].index.post - | fromjson - | .key - ' /tmp/social-index-post.json -)" - -POST_ARGS_BASE64="$( - jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg post_key "$POST_KEY" '{ - keys: [($account_id + "/post/" + $post_key)] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$POST_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-post-main.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg post_key "$POST_KEY" ' - .result.result - | implode - | fromjson - | { - account_id: $account_id, - post_key: $post_key, - post: (.[$account_id].post[$post_key] | fromjson) - } -' /tmp/social-post-main.json -``` - -Так вы получаете точный JSON, который хранится для текущего поста, включая поля вроде `type`, `text` и `image`. - -**Зачем нужен следующий шаг?** - -Это чистый RPC-паттерн для SocialDB: спросите у контракта один точный ключ, декодируйте возвращённый JSON и остановитесь. Если вопрос превращается в «кто и когда это записал?», переходите к примерам по транзакциям, а не пытайтесь brute-force читать raw state `social.near`. - ## Частые ошибки - Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. diff --git a/static/ru/transfers/examples.md b/static/ru/transfers/examples.md index 3c1096a..dccd77c 100644 --- a/static/ru/transfers/examples.md +++ b/static/ru/transfers/examples.md @@ -39,30 +39,29 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ }' ``` -Это самый короткий путь к вопросу «были ли здесь движения средств и какой receipt брать следующим?» +Это самый короткий путь к вопросу «были ли здесь движения средств и какую строку стоит разбирать дальше?» ## Готовый сценарий -### Найти один исходящий перевод и при необходимости перейти к деталям исполнения +### Отправлял ли этот аккаунт средства в этом окне и какую строку стоит разобрать? -Используйте этот сценарий, когда история звучит так: «я знаю, что этот аккаунт отправлял средства в этом окне, и мне может понадобиться точная опорная точка исполнения для одной строки, но я не хочу сразу тянуть всю историю аккаунта». +Используйте этот сценарий, когда история звучит так: «мне сначала нужно одно узкое окно исходящих переводов, и только после просмотра строк я решу, нужен ли одной из них follow-up по receipt». Стратегия - Сначала оставайтесь на узкой истории движения, а затем переходите в историю исполнения только если строки перевода уже недостаточно. + Сначала ответьте на вопрос о движении средств, а затем расширяйтесь только если одной строке всё ещё нужен execution-anchor. 01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять. 02Сначала выведите строки, а затем явно выберите один transfer_index перед тем, как поднимать его receipt_id. - 03POST /v0/receipt — это необязательное расширение, когда уже нужны детали исполнения именно за этим переводом. + 03POST /v0/receipt — необязательный follow-up, когда вы хотите понять, что именно эта строка перевода сделала on-chain. **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Выделяете одну строку перевода, которая действительно похожа на нужное вам движение. -- Переиспользуете его `receipt_id` в Transactions API только если нужно перейти от движения актива к истории исполнения. +- Сначала выводите строки, а затем выбираете одну строку перевода, которая действительно похожа на нужное вам движение. +- Переиспользуете её `receipt_id` только если нужно перейти от движения актива к истории исполнения. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=YOUR_ACCOUNT_ID FROM_TIMESTAMP_MS=1711929600000 TO_TIMESTAMP_MS=1712016000000 @@ -108,6 +107,16 @@ RECEIPT_ID="$( printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" +``` + +Этим вы отвечаете на первый вопрос: было ли здесь движение средств и какую строку перевода стоит разбирать дальше? + +#### Необязательное продолжение: Что сделала эта строка перевода on-chain? + +Переходите к истории receipt только если самой строки перевода уже недостаточно. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com if [ -n "$RECEIPT_ID" ]; then curl -s "$TX_BASE_URL/v0/receipt" \ @@ -124,7 +133,7 @@ fi **Зачем нужен следующий шаг?** -Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный следующий шаг, когда самой строки перевода уже недостаточно и нужна опорная точка в истории исполнения. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. +Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный второй вопрос: какая execution-anchor стоит за этой одной строкой? Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. ## Частые ошибки diff --git a/static/ru/transfers/examples/index.md b/static/ru/transfers/examples/index.md index 3c1096a..dccd77c 100644 --- a/static/ru/transfers/examples/index.md +++ b/static/ru/transfers/examples/index.md @@ -39,30 +39,29 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ }' ``` -Это самый короткий путь к вопросу «были ли здесь движения средств и какой receipt брать следующим?» +Это самый короткий путь к вопросу «были ли здесь движения средств и какую строку стоит разбирать дальше?» ## Готовый сценарий -### Найти один исходящий перевод и при необходимости перейти к деталям исполнения +### Отправлял ли этот аккаунт средства в этом окне и какую строку стоит разобрать? -Используйте этот сценарий, когда история звучит так: «я знаю, что этот аккаунт отправлял средства в этом окне, и мне может понадобиться точная опорная точка исполнения для одной строки, но я не хочу сразу тянуть всю историю аккаунта». +Используйте этот сценарий, когда история звучит так: «мне сначала нужно одно узкое окно исходящих переводов, и только после просмотра строк я решу, нужен ли одной из них follow-up по receipt». Стратегия - Сначала оставайтесь на узкой истории движения, а затем переходите в историю исполнения только если строки перевода уже недостаточно. + Сначала ответьте на вопрос о движении средств, а затем расширяйтесь только если одной строке всё ещё нужен execution-anchor. 01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять. 02Сначала выведите строки, а затем явно выберите один transfer_index перед тем, как поднимать его receipt_id. - 03POST /v0/receipt — это необязательное расширение, когда уже нужны детали исполнения именно за этим переводом. + 03POST /v0/receipt — необязательный follow-up, когда вы хотите понять, что именно эта строка перевода сделала on-chain. **Что вы делаете** - Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Выделяете одну строку перевода, которая действительно похожа на нужное вам движение. -- Переиспользуете его `receipt_id` в Transactions API только если нужно перейти от движения актива к истории исполнения. +- Сначала выводите строки, а затем выбираете одну строку перевода, которая действительно похожа на нужное вам движение. +- Переиспользуете её `receipt_id` только если нужно перейти от движения актива к истории исполнения. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=YOUR_ACCOUNT_ID FROM_TIMESTAMP_MS=1711929600000 TO_TIMESTAMP_MS=1712016000000 @@ -108,6 +107,16 @@ RECEIPT_ID="$( printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" +``` + +Этим вы отвечаете на первый вопрос: было ли здесь движение средств и какую строку перевода стоит разбирать дальше? + +#### Необязательное продолжение: Что сделала эта строка перевода on-chain? + +Переходите к истории receipt только если самой строки перевода уже недостаточно. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com if [ -n "$RECEIPT_ID" ]; then curl -s "$TX_BASE_URL/v0/receipt" \ @@ -124,7 +133,7 @@ fi **Зачем нужен следующий шаг?** -Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный следующий шаг, когда самой строки перевода уже недостаточно и нужна опорная точка в истории исполнения. Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. +Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный второй вопрос: какая execution-anchor стоит за этой одной строкой? Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. ## Частые ошибки diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index a99c80c..6232719 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -29,8 +29,6 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. -Если нужен более развёрнутый case study на той же поверхности, переходите к [Berry Club case study](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer case study](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. - ## С чего начать Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. @@ -982,59 +980,6 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. -## Расширенные сценарии и case study - -Примеры ниже всё ещё полезны, но они длиннее или более специализированы, чем основные start-here-сценарии выше. `Berry Club` и `OutLayer` вынесены в отдельные case-study-страницы, паттерн provenance для SocialDB теперь живёт на отдельной расширенной странице, а последний пример здесь оставляет только компактный multi-contract follow-up. - -### Расширенный паттерн provenance для SocialDB - -Если читаемый факт уже приходит из `api.near.social`, держите follow-up маленьким: сначала семантическое значение, затем `:block`, потом lookup по блоку и транзакции в FastNear. Для одного канонического примера такого сценария используйте [отдельную страницу паттерна provenance для SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs). - -### Расширенный сценарий: какие downstream-контракты затронула эта транзакция? - -Используйте этот сценарий, когда у вас уже есть один multi-contract tx hash и следующий вопрос звучит просто: «в какие контракты ушёл этот вызов после top-level action?» - -Этот зафиксированный mainnet-якорь по-прежнему хорошо подходит как пример, хотя сама транзакция и относится к `intents.near`: - -- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` -- аккаунт `signer` и `receiver`: `intents.near` -- высота включающего блока: `194573310` - -Короткий ответ для этой tx уже полезен: - -- top-level метод был `execute_intents` -- ранние downstream-receipt затронули `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` -- в более поздних логах были семейства событий вроде `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` - -Для большинства вопросов достаточно Transactions API: - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name - }, - downstream_receivers: ( - [.transactions[0].receipts[] | .receipt.receiver_id] - | unique - ), - first_logs: ( - [.transactions[0].receipts[] | .execution_outcome.outcome.logs[]?] - | .[:5] - ) - }' -``` - -Если нужен включающий блок, расширяйтесь один раз до Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block). Если нужен канонический DAG по receipt или сырые логи `EVENT_JSON`, расширяйтесь ещё на один шаг до RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status). Учебная идея здесь общая: начинайте с одного tx hash, перечислите downstream receiver-и и останавливайтесь, пока trace действительно не требует большего. - ## Частые ошибки - Пытаться отправлять транзакцию через history API вместо сырого RPC. @@ -1048,5 +993,8 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - [RPC Reference](https://docs.fastnear.com/ru/rpc) - [FastNear API](https://docs.fastnear.com/ru/api) - [NEAR Data API](https://docs.fastnear.com/ru/neardata) +- [Berry Club: живая доска и один путь исторической реконструкции](https://docs.fastnear.com/ru/tx/examples/berry-club) +- [OutLayer: связать одну транзакцию запроса с одним ответом воркера](https://docs.fastnear.com/ru/tx/examples/outlayer) +- [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/tx/examples/berry-club.md b/static/ru/tx/examples/berry-club.md index dfa2a85..f20231b 100644 --- a/static/ru/tx/examples/berry-club.md +++ b/static/ru/tx/examples/berry-club.md @@ -1,10 +1,10 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples/berry-club](https://docs.fastnear.com/ru/tx/examples/berry-club) -{/* FASTNEAR_AI_DISCOVERY: Этот case study показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} +{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} -# Berry Club Case Study: как читать живую доску и разбирать одну эпоху +# Berry Club: как читать живую доску и разбирать одну эпоху -Используйте этот case study, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. +Используйте этот walkthrough, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. Начните с живой доски. Если этого уже достаточно для ответа, на этом можно остановиться. diff --git a/static/ru/tx/examples/berry-club/index.md b/static/ru/tx/examples/berry-club/index.md index dfa2a85..f20231b 100644 --- a/static/ru/tx/examples/berry-club/index.md +++ b/static/ru/tx/examples/berry-club/index.md @@ -1,10 +1,10 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples/berry-club](https://docs.fastnear.com/ru/tx/examples/berry-club) -{/* FASTNEAR_AI_DISCOVERY: Этот case study показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} +{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} -# Berry Club Case Study: как читать живую доску и разбирать одну эпоху +# Berry Club: как читать живую доску и разбирать одну эпоху -Используйте этот case study, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. +Используйте этот walkthrough, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. Начните с живой доски. Если этого уже достаточно для ответа, на этом можно остановиться. diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index a99c80c..6232719 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -29,8 +29,6 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. -Если нужен более развёрнутый case study на той же поверхности, переходите к [Berry Club case study](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer case study](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. - ## С чего начать Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. @@ -982,59 +980,6 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. -## Расширенные сценарии и case study - -Примеры ниже всё ещё полезны, но они длиннее или более специализированы, чем основные start-here-сценарии выше. `Berry Club` и `OutLayer` вынесены в отдельные case-study-страницы, паттерн provenance для SocialDB теперь живёт на отдельной расширенной странице, а последний пример здесь оставляет только компактный multi-contract follow-up. - -### Расширенный паттерн provenance для SocialDB - -Если читаемый факт уже приходит из `api.near.social`, держите follow-up маленьким: сначала семантическое значение, затем `:block`, потом lookup по блоку и транзакции в FastNear. Для одного канонического примера такого сценария используйте [отдельную страницу паттерна provenance для SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs). - -### Расширенный сценарий: какие downstream-контракты затронула эта транзакция? - -Используйте этот сценарий, когда у вас уже есть один multi-contract tx hash и следующий вопрос звучит просто: «в какие контракты ушёл этот вызов после top-level action?» - -Этот зафиксированный mainnet-якорь по-прежнему хорошо подходит как пример, хотя сама транзакция и относится к `intents.near`: - -- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` -- аккаунт `signer` и `receiver`: `intents.near` -- высота включающего блока: `194573310` - -Короткий ответ для этой tx уже полезен: - -- top-level метод был `execute_intents` -- ранние downstream-receipt затронули `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` -- в более поздних логах были семейства событий вроде `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` - -Для большинства вопросов достаточно Transactions API: - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name - }, - downstream_receivers: ( - [.transactions[0].receipts[] | .receipt.receiver_id] - | unique - ), - first_logs: ( - [.transactions[0].receipts[] | .execution_outcome.outcome.logs[]?] - | .[:5] - ) - }' -``` - -Если нужен включающий блок, расширяйтесь один раз до Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block). Если нужен канонический DAG по receipt или сырые логи `EVENT_JSON`, расширяйтесь ещё на один шаг до RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status). Учебная идея здесь общая: начинайте с одного tx hash, перечислите downstream receiver-и и останавливайтесь, пока trace действительно не требует большего. - ## Частые ошибки - Пытаться отправлять транзакцию через history API вместо сырого RPC. @@ -1048,5 +993,8 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - [RPC Reference](https://docs.fastnear.com/ru/rpc) - [FastNear API](https://docs.fastnear.com/ru/api) - [NEAR Data API](https://docs.fastnear.com/ru/neardata) +- [Berry Club: живая доска и один путь исторической реконструкции](https://docs.fastnear.com/ru/tx/examples/berry-club) +- [OutLayer: связать одну транзакцию запроса с одним ответом воркера](https://docs.fastnear.com/ru/tx/examples/outlayer) +- [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/tx/examples/outlayer.md b/static/ru/tx/examples/outlayer.md index fa8cee9..74a8f53 100644 --- a/static/ru/tx/examples/outlayer.md +++ b/static/ru/tx/examples/outlayer.md @@ -1,27 +1,21 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) -{/* FASTNEAR_AI_DISCOVERY: Этот case study остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} +{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как прочитать один caller-side запрос OutLayer вместе с более поздним worker-side resolution, а затем разобрать finish-receipts только если это действительно нужно. */} -# OutLayer Case Study: трассировка запроса и разрешения воркером +# OutLayer: что сделала эта пара request/resolution? -Используйте этот case study, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» +Используйте этот walkthrough, когда вопрос звучит так: «что сделал этот запрос OutLayer, какое более позднее resolution к нему относится и нужно ли мне вообще смотреть на finish-receipts?» -Эта страница намеренно остаётся только в пределах публичных chain-данных. Она показывает caller-транзакцию, более позднюю worker-транзакцию и finish-receipts. Она не пытается доказывать внутреннюю TEE-, `yield/resume`- или CKD/MPC-архитектуру OutLayer только по chain-trace. +Оставайтесь в пределах публичных chain-данных: прочитайте request tx, прочитайте более поздний resolution tx и только потом решайте, нужны ли вам finish-receipts. -Сначала смотрите на это как на задачу по истории транзакций: - -- один caller-side `request_execution` -- одна более поздняя worker-side транзакция `submit_execution_output_and_resolve` или `resolve_execution` -- переход к receipts только тогда, когда уже важен путь завершения - -## Проверенный shell-сценарий +## Компактный shell-сценарий Эта пара работала 18 апреля 2026 года: - caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` - worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` -### 1. Сразу раскройте caller-транзакцию и worker-транзакцию вместе +### 1. Основной ответ: сразу раскройте request tx и resolution tx вместе ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -42,16 +36,20 @@ jq '{ hash: .transaction.hash, signer_id: .transaction.signer_id, receiver_id: .transaction.receiver_id, - actions: [.transaction.actions[] | keys[0]], + methods: [ + .transaction.actions[] + | .FunctionCall.method_name? + | select(. != null) + ], first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) } ] }' /tmp/outlayer-pair.json ``` -Это и есть главный наблюдаемый цикл: сначала caller-side запрос, затем более позднее worker-side разрешение, с читаемыми signer-, method- и log-доказательствами для обеих транзакций. +Это и есть основной ответ: один request tx, один более поздний resolution tx и читаемые signer-, receiver-, method- и log-доказательства для обеих транзакций. -### 2. Читайте worker-receipts только тогда, когда уже важен finish-путь +### Необязательное продолжение: Что сделал finish-путь? ```bash jq --arg worker_tx_hash "$WORKER_TX_HASH" ' @@ -73,15 +71,15 @@ jq --arg worker_tx_hash "$WORKER_TX_HASH" ' ' /tmp/outlayer-pair.json ``` -На что смотреть: +Смотрите на самое маленькое читаемое доказательство finish-пути: - `FunctionCall`-receipts, которые продолжают finish-путь - логи списания вроде `[[yNEAR charged: "..."]]` - последующие `Transfer`-receipts, которые похожи на refund или settlement-движение -Именно здесь receipts становятся правильной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции относятся к одному запросу OutLayer?» +Именно здесь receipts становятся нужной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции здесь относятся друг к другу?» -### 3. Если двух хешей у вас ещё нет, сначала найдите их +### Необязательный шаг: сначала найдите два хеша ```bash curl -sS "$TX_BASE_URL/v0/account" \ @@ -93,12 +91,10 @@ curl -sS "$TX_BASE_URL/v0/account" \ }' ``` -Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это surface, который превращает их в читаемое доказательство. +Используйте это только если вы ещё не знаете пару хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` превращает их в читаемый ответ. ## Полезные связанные страницы - [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) - [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) - [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) diff --git a/static/ru/tx/examples/outlayer/index.md b/static/ru/tx/examples/outlayer/index.md index fa8cee9..74a8f53 100644 --- a/static/ru/tx/examples/outlayer/index.md +++ b/static/ru/tx/examples/outlayer/index.md @@ -1,27 +1,21 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) -{/* FASTNEAR_AI_DISCOVERY: Этот case study остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как связать один caller-side запрос OutLayer с более поздним worker-side разрешением и разобрать finish-receipts. Он не пытается доказывать внутреннюю TEE-, yield/resume- или CKD/MPC-архитектуру OutLayer только по публичным chain-данным. */} +{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как прочитать один caller-side запрос OutLayer вместе с более поздним worker-side resolution, а затем разобрать finish-receipts только если это действительно нужно. */} -# OutLayer Case Study: трассировка запроса и разрешения воркером +# OutLayer: что сделала эта пара request/resolution? -Используйте этот case study, когда вопрос звучит так: «какая транзакция открыла запрос OutLayer, какая более поздняя транзакция пришла от воркера и где в завершающих receipts видны callback, списание или возврат средств?» +Используйте этот walkthrough, когда вопрос звучит так: «что сделал этот запрос OutLayer, какое более позднее resolution к нему относится и нужно ли мне вообще смотреть на finish-receipts?» -Эта страница намеренно остаётся только в пределах публичных chain-данных. Она показывает caller-транзакцию, более позднюю worker-транзакцию и finish-receipts. Она не пытается доказывать внутреннюю TEE-, `yield/resume`- или CKD/MPC-архитектуру OutLayer только по chain-trace. +Оставайтесь в пределах публичных chain-данных: прочитайте request tx, прочитайте более поздний resolution tx и только потом решайте, нужны ли вам finish-receipts. -Сначала смотрите на это как на задачу по истории транзакций: - -- один caller-side `request_execution` -- одна более поздняя worker-side транзакция `submit_execution_output_and_resolve` или `resolve_execution` -- переход к receipts только тогда, когда уже важен путь завершения - -## Проверенный shell-сценарий +## Компактный shell-сценарий Эта пара работала 18 апреля 2026 года: - caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` - worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` -### 1. Сразу раскройте caller-транзакцию и worker-транзакцию вместе +### 1. Основной ответ: сразу раскройте request tx и resolution tx вместе ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -42,16 +36,20 @@ jq '{ hash: .transaction.hash, signer_id: .transaction.signer_id, receiver_id: .transaction.receiver_id, - actions: [.transaction.actions[] | keys[0]], + methods: [ + .transaction.actions[] + | .FunctionCall.method_name? + | select(. != null) + ], first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) } ] }' /tmp/outlayer-pair.json ``` -Это и есть главный наблюдаемый цикл: сначала caller-side запрос, затем более позднее worker-side разрешение, с читаемыми signer-, method- и log-доказательствами для обеих транзакций. +Это и есть основной ответ: один request tx, один более поздний resolution tx и читаемые signer-, receiver-, method- и log-доказательства для обеих транзакций. -### 2. Читайте worker-receipts только тогда, когда уже важен finish-путь +### Необязательное продолжение: Что сделал finish-путь? ```bash jq --arg worker_tx_hash "$WORKER_TX_HASH" ' @@ -73,15 +71,15 @@ jq --arg worker_tx_hash "$WORKER_TX_HASH" ' ' /tmp/outlayer-pair.json ``` -На что смотреть: +Смотрите на самое маленькое читаемое доказательство finish-пути: - `FunctionCall`-receipts, которые продолжают finish-путь - логи списания вроде `[[yNEAR charged: "..."]]` - последующие `Transfer`-receipts, которые похожи на refund или settlement-движение -Именно здесь receipts становятся правильной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции относятся к одному запросу OutLayer?» +Именно здесь receipts становятся нужной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции здесь относятся друг к другу?» -### 3. Если двух хешей у вас ещё нет, сначала найдите их +### Необязательный шаг: сначала найдите два хеша ```bash curl -sS "$TX_BASE_URL/v0/account" \ @@ -93,12 +91,10 @@ curl -sS "$TX_BASE_URL/v0/account" \ }' ``` -Используйте это только как surface для поиска хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` — это surface, который превращает их в читаемое доказательство. +Используйте это только если вы ещё не знаете пару хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` превращает их в читаемый ответ. ## Полезные связанные страницы - [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) - [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) - [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) diff --git a/static/ru/tx/socialdb-proofs.md b/static/ru/tx/socialdb-proofs.md index 4f654dd..e67f8ee 100644 --- a/static/ru/tx/socialdb-proofs.md +++ b/static/ru/tx/socialdb-proofs.md @@ -1,8 +1,8 @@ **Источник:** [https://docs.fastnear.com/ru/tx/socialdb-proofs](https://docs.fastnear.com/ru/tx/socialdb-proofs) -# Расширенный паттерн provenance для SocialDB +# Расширенный поиск записи SocialDB -Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому provenance. +Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому поиску записи. Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" @@ -121,71 +121,11 @@ jq '{ }' /tmp/mike-profile-transaction.json ``` -4. Завершите каноническим подтверждением текущего состояния через raw RPC. +Это и есть весь паттерн lookup-а: читаемое значение, block уровня поля, мост через receipt и payload транзакции. -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-profile-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - finality: "final", - current_name: ( - .result.result - | implode - | fromjson - | .[$account_id].profile.name - ) -}' /tmp/mike-profile-rpc.json -``` - -Это и есть весь provenance-паттерн: читаемое значение, block уровня поля, мост через receipt, payload транзакции и затем необязательное подтверждение текущего состояния. - -## Тот же паттерн для других ключей SocialDB - -Используйте тот же мост каждый раз, когда у вас уже есть читаемое значение SocialDB и его write-block: - -1. Прочитайте семантическое значение и `:block` из NEAR Social или начните с уже известного блока записи виджета. -2. Используйте Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block), чтобы восстановить receipt `*.near -> social.near` и хеш транзакции. -3. Используйте Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), чтобы декодировать payload `social.near set`. -4. Используйте RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) только если всё ещё нужно каноническое подтверждение текущего состояния. - -### Вариант для связи подписки - -Тот же паттерн работает для читаемой связи подписки: - -- текущая связь: `mike.near -> mob.near` -- блок записи SocialDB: `79574924` -- receipt ID: `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th` -- хеш исходной транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - -Главное отличие от примера с профилем только в ключе и форме payload: читайте `mike.near/graph/follow/mob.near`, а затем декодируйте и `graph.follow`, и соответствующую запись `index.graph` из payload записи. - -### Вариант для исходника виджета - -Тот же паттерн снова работает, когда читаемый факт относится к ключу с исходником виджета: +Тот же мост работает и для других читаемых значений SocialDB: -- ключ виджета: `mob.near/widget/Profile` -- блок записи SocialDB: `86494825` -- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` -- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- вариант для связи подписки: `mike.near -> mob.near`, блок `79574924`, tx `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- вариант для исходника виджета: `mob.near/widget/Profile`, блок `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` -Главное отличие здесь в том, что вы обычно стартуете с уже известного блока записи виджета, а затем декодируете `.data["mob.near"].widget.Profile[""]` из исходного payload `social.near set`. +Ключевая идея не меняется: начните с читаемого значения и его write-block, восстановите receipt `*.near -> social.near` из блока, а затем декодируйте payload `social.near set` из исходной транзакции. diff --git a/static/ru/tx/socialdb-proofs/index.md b/static/ru/tx/socialdb-proofs/index.md index 4f654dd..e67f8ee 100644 --- a/static/ru/tx/socialdb-proofs/index.md +++ b/static/ru/tx/socialdb-proofs/index.md @@ -1,8 +1,8 @@ **Источник:** [https://docs.fastnear.com/ru/tx/socialdb-proofs](https://docs.fastnear.com/ru/tx/socialdb-proofs) -# Расширенный паттерн provenance для SocialDB +# Расширенный поиск записи SocialDB -Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому provenance. +Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому поиску записи. Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" @@ -121,71 +121,11 @@ jq '{ }' /tmp/mike-profile-transaction.json ``` -4. Завершите каноническим подтверждением текущего состояния через raw RPC. +Это и есть весь паттерн lookup-а: читаемое значение, block уровня поля, мост через receipt и payload транзакции. -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-profile-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - finality: "final", - current_name: ( - .result.result - | implode - | fromjson - | .[$account_id].profile.name - ) -}' /tmp/mike-profile-rpc.json -``` - -Это и есть весь provenance-паттерн: читаемое значение, block уровня поля, мост через receipt, payload транзакции и затем необязательное подтверждение текущего состояния. - -## Тот же паттерн для других ключей SocialDB - -Используйте тот же мост каждый раз, когда у вас уже есть читаемое значение SocialDB и его write-block: - -1. Прочитайте семантическое значение и `:block` из NEAR Social или начните с уже известного блока записи виджета. -2. Используйте Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block), чтобы восстановить receipt `*.near -> social.near` и хеш транзакции. -3. Используйте Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), чтобы декодировать payload `social.near set`. -4. Используйте RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) только если всё ещё нужно каноническое подтверждение текущего состояния. - -### Вариант для связи подписки - -Тот же паттерн работает для читаемой связи подписки: - -- текущая связь: `mike.near -> mob.near` -- блок записи SocialDB: `79574924` -- receipt ID: `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th` -- хеш исходной транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - -Главное отличие от примера с профилем только в ключе и форме payload: читайте `mike.near/graph/follow/mob.near`, а затем декодируйте и `graph.follow`, и соответствующую запись `index.graph` из payload записи. - -### Вариант для исходника виджета - -Тот же паттерн снова работает, когда читаемый факт относится к ключу с исходником виджета: +Тот же мост работает и для других читаемых значений SocialDB: -- ключ виджета: `mob.near/widget/Profile` -- блок записи SocialDB: `86494825` -- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` -- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- вариант для связи подписки: `mike.near -> mob.near`, блок `79574924`, tx `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- вариант для исходника виджета: `mob.near/widget/Profile`, блок `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` -Главное отличие здесь в том, что вы обычно стартуете с уже известного блока записи виджета, а затем декодируете `.data["mob.near"].widget.Profile[""]` из исходного payload `social.near set`. +Ключевая идея не меняется: начните с читаемого значения и его write-block, восстановите receipt `*.near -> social.near` из блока, а затем декодируйте payload `social.near set` из исходной транзакции. From b28211852e1dc497463bd9d75754f91bab275f13 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sun, 19 Apr 2026 11:09:00 -0700 Subject: [PATCH 18/35] docs: clarify raw state layout caveat --- docs/rpc/examples.md | 2 +- i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md | 2 +- static/ru/llms-full.txt | 2 +- static/ru/rpc/examples.md | 2 +- static/ru/rpc/examples/index.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index 51c6bd1..9110e8d 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -479,7 +479,7 @@ print(int.from_bytes(raw, "little", signed=True)) PY ``` -For this contract, `STATE` is a one-byte signed counter, so decoding is trivial. On other contracts the layout may be more complex, but the rule stays the same: bytes first, schema second. +For this contract, `STATE` is a one-byte signed counter, so decoding is trivial. On other contracts the layout may be much less friendly: near-sdk collections and Borsh-serialized structs often derive storage keys from prefixes and internal key schemes, so `view_state` only stays practical when you already know the exact layout you want. The rule stays the same: bytes first, schema second. 3. Ask the contract the friendly way and compare. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index a2ac8c9..ee965de 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -479,7 +479,7 @@ print(int.from_bytes(raw, "little", signed=True)) PY ``` -Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть сложнее, но правило то же: сначала байты, потом схема. +Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть гораздо менее дружелюбным: near-sdk-коллекции и Borsh-сериализованные структуры часто выводят storage-ключи из префиксов и внутренних схем ключей, поэтому `view_state` остаётся практичным только когда вы уже знаете точный layout, который хотите читать. Правило остаётся тем же: сначала байты, потом схема. 3. Теперь спросите контракт привычным способом и сравните. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index f448066..3983e0d 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -2865,7 +2865,7 @@ print(int.from_bytes(raw, "little", signed=True)) PY ``` -Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть сложнее, но правило то же: сначала байты, потом схема. +Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть гораздо менее дружелюбным: near-sdk-коллекции и Borsh-сериализованные структуры часто выводят storage-ключи из префиксов и внутренних схем ключей, поэтому `view_state` остаётся практичным только когда вы уже знаете точный layout, который хотите читать. Правило остаётся тем же: сначала байты, потом схема. 3. Теперь спросите контракт привычным способом и сравните. diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index 937c48f..acda451 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -459,7 +459,7 @@ print(int.from_bytes(raw, "little", signed=True)) PY ``` -Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть сложнее, но правило то же: сначала байты, потом схема. +Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть гораздо менее дружелюбным: near-sdk-коллекции и Borsh-сериализованные структуры часто выводят storage-ключи из префиксов и внутренних схем ключей, поэтому `view_state` остаётся практичным только когда вы уже знаете точный layout, который хотите читать. Правило остаётся тем же: сначала байты, потом схема. 3. Теперь спросите контракт привычным способом и сравните. diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index 937c48f..acda451 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -459,7 +459,7 @@ print(int.from_bytes(raw, "little", signed=True)) PY ``` -Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть сложнее, но правило то же: сначала байты, потом схема. +Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть гораздо менее дружелюбным: near-sdk-коллекции и Borsh-сериализованные структуры часто выводят storage-ключи из префиксов и внутренних схем ключей, поэтому `view_state` остаётся практичным только когда вы уже знаете точный layout, который хотите читать. Правило остаётся тем же: сначала байты, потом схема. 3. Теперь спросите контракт привычным способом и сравните. From d0fa149f3b4ffc31e9f74a75958626a252523cfb Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sun, 19 Apr 2026 11:18:56 -0700 Subject: [PATCH 19/35] docs: polish api and tx examples --- docs/api/examples.md | 53 ++++++--- docs/tx/examples.md | 56 ++++++++- .../current/api/examples.md | 53 ++++++--- .../current/tx/examples.md | 58 +++++++-- static/ru/api/examples.md | 53 ++++++--- static/ru/api/examples/index.md | 53 ++++++--- static/ru/llms-full.txt | 111 ++++++++++++++---- static/ru/tx/examples.md | 58 +++++++-- static/ru/tx/examples/index.md | 58 +++++++-- 9 files changed, 444 insertions(+), 109 deletions(-) diff --git a/docs/api/examples.md b/docs/api/examples.md index 848bf30..84cdcaf 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -46,7 +46,7 @@ Use this when you have a public key first and the next user-facing question is
Strategy -

Resolve identity first, then reuse the same account ID for one readable wallet snapshot.

+

Resolve identity first, then either inspect one account immediately or fan out across the returned list when the key maps to more than one account.

01GET /v1/public_key gives the candidate account_id values for the key.

@@ -58,8 +58,8 @@ Use this when you have a public key first and the next user-facing question is **What you're doing** - Resolve the public key to one or more account IDs. -- Extract the first matching account ID with `jq`. -- Reuse that value in the full account snapshot endpoint. +- Count how many account IDs came back before you commit to one. +- Reuse one account ID immediately, or loop through the full list when the key maps to multiple accounts. ```bash API_BASE_URL=https://api.fastnear.com @@ -75,21 +75,42 @@ ACCOUNT_ID="$( | jq -r '.account_ids[0]' )" -jq '{account_ids}' /tmp/fastnear-public-key.json +ACCOUNT_COUNT="$( + jq -r '.account_ids | length' /tmp/fastnear-public-key.json +)" -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' +jq '{ + account_ids, + account_count: (.account_ids | length) +}' /tmp/fastnear-public-key.json + +if [ "$ACCOUNT_COUNT" -eq 1 ]; then + curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +else + jq -r '.account_ids[]' /tmp/fastnear-public-key.json \ + | while read -r candidate_account_id; do + curl -s "$API_BASE_URL/v1/account/$candidate_account_id/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' + done +fi ``` **Why this next step?** -The public-key lookup tells you which account you are dealing with. The full account snapshot is the natural next read when you want balances, NFTs, staking, and pools in one response. If the key maps to multiple accounts instead of one, move to [V1 Public Key Lookup All](/api/v1/public-key-all) or loop through each returned `account_id`. +The public-key lookup tells you which account or accounts you are dealing with. The full account snapshot is the natural next read when you want balances, NFTs, staking, and pools in one response. If the key maps to multiple accounts instead of one, this is the point where you either inspect each returned `account_id` or move to [V1 Public Key Lookup All](/api/v1/public-key-all) for the broader historical view. ### Does this account have direct staking right now? @@ -144,7 +165,7 @@ At the time of writing, `mike.near` returned visible direct staking pools here. **Why this next step?** -This keeps the question narrow and operational. If the answer is `true`, remember what that means on chain: the account usually delegated into a staking-pool contract such as `polkachu.poolv1.near` by sending a `FunctionCall` like `deposit_and_stake` with attached deposit. The pool contract later performs the actual `Stake` action on its own account. If the answer is `false`, do not infer liquid staking from this example alone; this example is only about direct pool positions. +This keeps the question narrow and operational. If the answer is `true`, remember what that means on chain: the account usually delegated into a staking-pool contract such as `polkachu.poolv1.near` by sending a `FunctionCall` like `deposit_and_stake` with attached deposit. The pool contract later performs the actual `Stake` action on its own account. If the answer is `false`, do not infer liquid staking from this example alone; liquid staking positions usually show up first as FT holdings in specific LST contracts, so the right follow-up is the FT holdings example below. Also note the scope boundary here: this endpoint does not currently surface pending-unstake or withdraw-ready amounts, so it is not the place to answer epoch-delay timing questions. #### Optional follow-up: What did this contract call for delegation do? @@ -155,7 +176,7 @@ This pinned mainnet tx is useful because it shows the full pattern clearly: - transaction hash: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` - top-level receiver: `polkachu.poolv1.near` - top-level method: `deposit_and_stake` -- attached deposit: `34650000000000000000000000` +- attached deposit: `34650000000000000000000000` (≈34.65 NEAR) The important chain shape is: @@ -222,6 +243,8 @@ Use this when a wallet view, support tool, or agent already has an `account_id` This example does not answer native balance, staking, pools, exact NFT token IDs, or metadata. +The FT endpoint here is balance-first. It does not include display metadata such as token `symbol` or `decimals`; when you need to format a balance for UI, call the token contract’s `ft_metadata` read method over RPC. + The NFT endpoint here is collection-level. Treat it as “which NFT contracts does this account currently hold from?” rather than a full per-token crawl. ```bash diff --git a/docs/tx/examples.md b/docs/tx/examples.md index d92c514..cc9eb33 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -284,7 +284,12 @@ jq --arg fragment "$LOG_FRAGMENT" '{ receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), block_height: .execution_outcome.block_height, logs: .execution_outcome.outcome.logs } @@ -308,7 +313,12 @@ jq '{ | { receipt_id: .receipt.receipt_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), logs: .execution_outcome.outcome.logs } ] @@ -438,8 +448,19 @@ jq '{ ```bash jq -r ' + def zeros($n): + reduce range(0; $n) as $i (""; . + "0"); + def yocto_to_near($yocto): + ($yocto | tostring) as $digits + | if ($digits | length) <= 24 then + ("0." + zeros(24 - ($digits | length)) + $digits) + else + ($digits[0:(($digits | length) - 24)] + "." + $digits[-24:]) + end + | sub("0+$"; "") + | sub("\\.$"; ""); .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) belongs to tx \($tx.transaction.hash): \($tx.transaction.signer_id) sent 5 NEAR to \($tx.transaction.receiver_id). The tx landed in block \($tx.execution_outcome.block_height), and the receipt executed successfully in block \($tx.receipts[0].execution_outcome.block_height)." + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) belongs to tx \($tx.transaction.hash): \($tx.transaction.signer_id) sent \(yocto_to_near($tx.transaction.actions[0].Transfer.deposit)) NEAR to \($tx.transaction.receiver_id). The tx landed in block \($tx.execution_outcome.block_height), and the receipt executed successfully in block \($tx.receipts[0].execution_outcome.block_height)." ' /tmp/receipt-parent-transaction.json ``` @@ -901,6 +922,25 @@ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | tee /tmp/callback-check-transaction.json >/dev/null +``` + +2. Answer the smallest useful question first: did the callback come back at all? + +```bash +jq --arg origin "$ORIGIN_CONTRACT_ID" ' + [ + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + ] | length > 0 +' /tmp/callback-check-transaction.json +``` + +3. If the answer is `true`, print the downstream receipt plus the callback receipt. + +```bash CALLBACK_RECEIPT_ID="$( jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' @@ -931,7 +971,12 @@ jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), status: .execution_outcome.outcome.status, block_height: .execution_outcome.block_height } @@ -973,7 +1018,7 @@ jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" # - callback_ran is true even though the downstream receipt failed ``` -2. If you want the canonical callback outcome and refund log, confirm the same receipt in RPC. +4. If you want the canonical callback outcome and refund log, confirm the same receipt in RPC. ```bash curl -s "$RPC_URL" \ @@ -1023,7 +1068,6 @@ For callback questions, the important proof is not “did every receipt succeed? - Trying to submit a transaction from the history API instead of raw RPC. - Using Transactions API when the user only wants current balances or holdings. - Dropping to raw RPC before indexed history has answered the readable "what happened?" question. -- Reusing opaque pagination tokens in a different endpoint or filter context. ## Related guides diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index ab82629..3e269c6 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -46,7 +46,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \
Стратегия -

Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку.

+

Сначала определите личность, а затем либо сразу проверьте один аккаунт, либо пройдитесь по всему списку, если ключ сопоставляется с несколькими аккаунтами.

01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа.

@@ -58,8 +58,8 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Что вы делаете** - Ищете по публичному ключу один или несколько `account_id`. -- Извлекаете первый найденный `account_id` через `jq`. -- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. +- Сначала считаете, сколько `account_id` вернулось, прежде чем выбирать один. +- Сразу переиспользуете один аккаунт или проходите по всему списку, если ключ сопоставляется с несколькими аккаунтами. ```bash API_BASE_URL=https://api.fastnear.com @@ -75,21 +75,42 @@ ACCOUNT_ID="$( | jq -r '.account_ids[0]' )" -jq '{account_ids}' /tmp/fastnear-public-key.json +ACCOUNT_COUNT="$( + jq -r '.account_ids | length' /tmp/fastnear-public-key.json +)" -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' +jq '{ + account_ids, + account_count: (.account_ids | length) +}' /tmp/fastnear-public-key.json + +if [ "$ACCOUNT_COUNT" -eq 1 ]; then + curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +else + jq -r '.account_ids[]' /tmp/fastnear-public-key.json \ + | while read -r candidate_account_id; do + curl -s "$API_BASE_URL/v1/account/$candidate_account_id/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' + done +fi ``` **Зачем нужен следующий шаг?** -Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. +Поиск по публичному ключу говорит, с каким аккаунтом или аккаунтами вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, именно здесь стоит либо пройтись по каждому найденному `account_id`, либо перейти к [V1 Public Key Lookup All](/api/v1/public-key-all) для более широкого исторического ответа. ### Есть ли у этого аккаунта прямой стейкинг прямо сейчас? @@ -144,7 +165,7 @@ jq '{ **Зачем нужен следующий шаг?** -Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. +Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: liquid staking-позиции обычно сначала видны как FT-holdings в конкретных LST-контрактах, поэтому правильный follow-up здесь — FT-пример ниже. И ещё одна граница этой поверхности: этот эндпоинт сейчас не показывает pending-unstake или withdraw-ready amount, так что по нему не стоит отвечать на вопросы о задержках по эпохам. #### Необязательное продолжение: Что сделал этот контрактный вызов для делегирования? @@ -155,7 +176,7 @@ jq '{ - хеш транзакции: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` - top-level receiver: `polkachu.poolv1.near` - top-level метод: `deposit_and_stake` -- attached deposit: `34650000000000000000000000` +- attached deposit: `34650000000000000000000000` (≈34.65 NEAR) Важная форма chain-истории здесь такая: @@ -222,6 +243,8 @@ jq '{ Этот пример не отвечает на вопросы про нативный баланс, стейкинг, пулы, точные NFT token ID или метаданные. +FT-эндпоинт здесь решает задачу балансов. Он не включает display-метаданные вроде `symbol` или `decimals`; когда нужно форматировать баланс для UI, вызовите у токен-контракта read-метод `ft_metadata` через RPC. + NFT-эндпоинт здесь работает на уровне коллекций. Воспринимайте его как ответ на вопрос «из каких NFT-контрактов этот аккаунт сейчас что-то держит?», а не как полный per-token crawl. ```bash diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index eade930..5262f15 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -284,7 +284,12 @@ jq --arg fragment "$LOG_FRAGMENT" '{ receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), block_height: .execution_outcome.block_height, logs: .execution_outcome.outcome.logs } @@ -308,7 +313,12 @@ jq '{ | { receipt_id: .receipt.receipt_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), logs: .execution_outcome.outcome.logs } ] @@ -438,8 +448,19 @@ jq '{ ```bash jq -r ' + def zeros($n): + reduce range(0; $n) as $i (""; . + "0"); + def yocto_to_near($yocto): + ($yocto | tostring) as $digits + | if ($digits | length) <= 24 then + ("0." + zeros(24 - ($digits | length)) + $digits) + else + ($digits[0:(($digits | length) - 24)] + "." + $digits[-24:]) + end + | sub("0+$"; "") + | sub("\\.$"; ""); .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил \(yocto_to_near($tx.transaction.actions[0].Transfer.deposit)) NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." ' /tmp/receipt-parent-transaction.json ``` @@ -894,13 +915,32 @@ ORIGIN_CONTRACT_ID=wrap.near DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near ``` -1. Получите транзакцию и распечатайте downstream-receipt вместе с callback-receipt. +1. Получите транзакцию и сохраните receipt-цепочку. ```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | tee /tmp/callback-check-transaction.json >/dev/null +``` + +2. Сначала ответьте на самый короткий полезный вопрос: вернулся ли callback вообще? + +```bash +jq --arg origin "$ORIGIN_CONTRACT_ID" ' + [ + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + ] | length > 0 +' /tmp/callback-check-transaction.json +``` + +3. Если ответ `true`, распечатайте downstream-receipt вместе с callback-receipt. + +```bash CALLBACK_RECEIPT_ID="$( jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' @@ -931,7 +971,12 @@ jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), status: .execution_outcome.outcome.status, block_height: .execution_outcome.block_height } @@ -973,7 +1018,7 @@ jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" # - callback_ran равно true, даже несмотря на downstream-сбой ``` -2. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. +4. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. ```bash curl -s "$RPC_URL" \ @@ -1023,7 +1068,6 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ - Пытаться отправлять транзакцию через history API вместо сырого RPC. - Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. - Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». -- Повторно использовать непрозрачные токены пагинации с другим эндпоинтом или другим набором фильтров. ## Полезные связанные страницы diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index e994db0..35df6e2 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -36,7 +36,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» Стратегия - Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. + Сначала определите личность, а затем либо сразу проверьте один аккаунт, либо пройдитесь по всему списку, если ключ сопоставляется с несколькими аккаунтами. 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. @@ -45,8 +45,8 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Что вы делаете** - Ищете по публичному ключу один или несколько `account_id`. -- Извлекаете первый найденный `account_id` через `jq`. -- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. +- Сначала считаете, сколько `account_id` вернулось, прежде чем выбирать один. +- Сразу переиспользуете один аккаунт или проходите по всему списку, если ключ сопоставляется с несколькими аккаунтами. ```bash API_BASE_URL=https://api.fastnear.com @@ -62,21 +62,42 @@ ACCOUNT_ID="$( | jq -r '.account_ids[0]' )" -jq '{account_ids}' /tmp/fastnear-public-key.json +ACCOUNT_COUNT="$( + jq -r '.account_ids | length' /tmp/fastnear-public-key.json +)" -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' +jq '{ + account_ids, + account_count: (.account_ids | length) +}' /tmp/fastnear-public-key.json + +if [ "$ACCOUNT_COUNT" -eq 1 ]; then + curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +else + jq -r '.account_ids[]' /tmp/fastnear-public-key.json \ + | while read -r candidate_account_id; do + curl -s "$API_BASE_URL/v1/account/$candidate_account_id/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' + done +fi ``` **Зачем нужен следующий шаг?** -Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. +Поиск по публичному ключу говорит, с каким аккаунтом или аккаунтами вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, именно здесь стоит либо пройтись по каждому найденному `account_id`, либо перейти к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) для более широкого исторического ответа. ### Есть ли у этого аккаунта прямой стейкинг прямо сейчас? @@ -126,7 +147,7 @@ jq '{ **Зачем нужен следующий шаг?** -Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. +Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: liquid staking-позиции обычно сначала видны как FT-holdings в конкретных LST-контрактах, поэтому правильный follow-up здесь — FT-пример ниже. И ещё одна граница этой поверхности: этот эндпоинт сейчас не показывает pending-unstake или withdraw-ready amount, так что по нему не стоит отвечать на вопросы о задержках по эпохам. #### Необязательное продолжение: Что сделал этот контрактный вызов для делегирования? @@ -137,7 +158,7 @@ jq '{ - хеш транзакции: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` - top-level receiver: `polkachu.poolv1.near` - top-level метод: `deposit_and_stake` -- attached deposit: `34650000000000000000000000` +- attached deposit: `34650000000000000000000000` (≈34.65 NEAR) Важная форма chain-истории здесь такая: @@ -199,6 +220,8 @@ jq '{ Этот пример не отвечает на вопросы про нативный баланс, стейкинг, пулы, точные NFT token ID или метаданные. +FT-эндпоинт здесь решает задачу балансов. Он не включает display-метаданные вроде `symbol` или `decimals`; когда нужно форматировать баланс для UI, вызовите у токен-контракта read-метод `ft_metadata` через RPC. + NFT-эндпоинт здесь работает на уровне коллекций. Воспринимайте его как ответ на вопрос «из каких NFT-контрактов этот аккаунт сейчас что-то держит?», а не как полный per-token crawl. ```bash diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index e994db0..35df6e2 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -36,7 +36,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» Стратегия - Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. + Сначала определите личность, а затем либо сразу проверьте один аккаунт, либо пройдитесь по всему списку, если ключ сопоставляется с несколькими аккаунтами. 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. @@ -45,8 +45,8 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Что вы делаете** - Ищете по публичному ключу один или несколько `account_id`. -- Извлекаете первый найденный `account_id` через `jq`. -- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. +- Сначала считаете, сколько `account_id` вернулось, прежде чем выбирать один. +- Сразу переиспользуете один аккаунт или проходите по всему списку, если ключ сопоставляется с несколькими аккаунтами. ```bash API_BASE_URL=https://api.fastnear.com @@ -62,21 +62,42 @@ ACCOUNT_ID="$( | jq -r '.account_ids[0]' )" -jq '{account_ids}' /tmp/fastnear-public-key.json +ACCOUNT_COUNT="$( + jq -r '.account_ids | length' /tmp/fastnear-public-key.json +)" -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' +jq '{ + account_ids, + account_count: (.account_ids | length) +}' /tmp/fastnear-public-key.json + +if [ "$ACCOUNT_COUNT" -eq 1 ]; then + curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +else + jq -r '.account_ids[]' /tmp/fastnear-public-key.json \ + | while read -r candidate_account_id; do + curl -s "$API_BASE_URL/v1/account/$candidate_account_id/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' + done +fi ``` **Зачем нужен следующий шаг?** -Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. +Поиск по публичному ключу говорит, с каким аккаунтом или аккаунтами вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, именно здесь стоит либо пройтись по каждому найденному `account_id`, либо перейти к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) для более широкого исторического ответа. ### Есть ли у этого аккаунта прямой стейкинг прямо сейчас? @@ -126,7 +147,7 @@ jq '{ **Зачем нужен следующий шаг?** -Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. +Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: liquid staking-позиции обычно сначала видны как FT-holdings в конкретных LST-контрактах, поэтому правильный follow-up здесь — FT-пример ниже. И ещё одна граница этой поверхности: этот эндпоинт сейчас не показывает pending-unstake или withdraw-ready amount, так что по нему не стоит отвечать на вопросы о задержках по эпохам. #### Необязательное продолжение: Что сделал этот контрактный вызов для делегирования? @@ -137,7 +158,7 @@ jq '{ - хеш транзакции: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` - top-level receiver: `polkachu.poolv1.near` - top-level метод: `deposit_and_stake` -- attached deposit: `34650000000000000000000000` +- attached deposit: `34650000000000000000000000` (≈34.65 NEAR) Важная форма chain-истории здесь такая: @@ -199,6 +220,8 @@ jq '{ Этот пример не отвечает на вопросы про нативный баланс, стейкинг, пулы, точные NFT token ID или метаданные. +FT-эндпоинт здесь решает задачу балансов. Он не включает display-метаданные вроде `symbol` или `decimals`; когда нужно форматировать баланс для UI, вызовите у токен-контракта read-метод `ft_metadata` через RPC. + NFT-эндпоинт здесь работает на уровне коллекций. Воспринимайте его как ответ на вопрос «из каких NFT-контрактов этот аккаунт сейчас что-то держит?», а не как полный per-token crawl. ```bash diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 3983e0d..e197ce4 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -1030,7 +1030,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» Стратегия - Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. + Сначала определите личность, а затем либо сразу проверьте один аккаунт, либо пройдитесь по всему списку, если ключ сопоставляется с несколькими аккаунтами. 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. @@ -1039,8 +1039,8 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Что вы делаете** - Ищете по публичному ключу один или несколько `account_id`. -- Извлекаете первый найденный `account_id` через `jq`. -- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. +- Сначала считаете, сколько `account_id` вернулось, прежде чем выбирать один. +- Сразу переиспользуете один аккаунт или проходите по всему списку, если ключ сопоставляется с несколькими аккаунтами. ```bash API_BASE_URL=https://api.fastnear.com @@ -1056,21 +1056,42 @@ ACCOUNT_ID="$( | jq -r '.account_ids[0]' )" -jq '{account_ids}' /tmp/fastnear-public-key.json +ACCOUNT_COUNT="$( + jq -r '.account_ids | length' /tmp/fastnear-public-key.json +)" -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' +jq '{ + account_ids, + account_count: (.account_ids | length) +}' /tmp/fastnear-public-key.json + +if [ "$ACCOUNT_COUNT" -eq 1 ]; then + curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' +else + jq -r '.account_ids[]' /tmp/fastnear-public-key.json \ + | while read -r candidate_account_id; do + curl -s "$API_BASE_URL/v1/account/$candidate_account_id/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' + done +fi ``` **Зачем нужен следующий шаг?** -Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. +Поиск по публичному ключу говорит, с каким аккаунтом или аккаунтами вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, именно здесь стоит либо пройтись по каждому найденному `account_id`, либо перейти к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) для более широкого исторического ответа. ### Есть ли у этого аккаунта прямой стейкинг прямо сейчас? @@ -1120,7 +1141,7 @@ jq '{ **Зачем нужен следующий шаг?** -Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: этот сценарий касается только прямых пулов. +Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: liquid staking-позиции обычно сначала видны как FT-holdings в конкретных LST-контрактах, поэтому правильный follow-up здесь — FT-пример ниже. И ещё одна граница этой поверхности: этот эндпоинт сейчас не показывает pending-unstake или withdraw-ready amount, так что по нему не стоит отвечать на вопросы о задержках по эпохам. #### Необязательное продолжение: Что сделал этот контрактный вызов для делегирования? @@ -1131,7 +1152,7 @@ jq '{ - хеш транзакции: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` - top-level receiver: `polkachu.poolv1.near` - top-level метод: `deposit_and_stake` -- attached deposit: `34650000000000000000000000` +- attached deposit: `34650000000000000000000000` (≈34.65 NEAR) Важная форма chain-истории здесь такая: @@ -1193,6 +1214,8 @@ jq '{ Этот пример не отвечает на вопросы про нативный баланс, стейкинг, пулы, точные NFT token ID или метаданные. +FT-эндпоинт здесь решает задачу балансов. Он не включает display-метаданные вроде `symbol` или `decimals`; когда нужно форматировать баланс для UI, вызовите у токен-контракта read-метод `ft_metadata` через RPC. + NFT-эндпоинт здесь работает на уровне коллекций. Воспринимайте его как ответ на вопрос «из каких NFT-контрактов этот аккаунт сейчас что-то держит?», а не как полный per-token crawl. ```bash @@ -4114,7 +4137,12 @@ jq --arg fragment "$LOG_FRAGMENT" '{ receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), block_height: .execution_outcome.block_height, logs: .execution_outcome.outcome.logs } @@ -4138,7 +4166,12 @@ jq '{ | { receipt_id: .receipt.receipt_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), logs: .execution_outcome.outcome.logs } ] @@ -4263,8 +4296,19 @@ jq '{ ```bash jq -r ' + def zeros($n): + reduce range(0; $n) as $i (""; . + "0"); + def yocto_to_near($yocto): + ($yocto | tostring) as $digits + | if ($digits | length) <= 24 then + ("0." + zeros(24 - ($digits | length)) + $digits) + else + ($digits[0:(($digits | length) - 24)] + "." + $digits[-24:]) + end + | sub("0+$"; "") + | sub("\\.$"; ""); .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил \(yocto_to_near($tx.transaction.actions[0].Transfer.deposit)) NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." ' /tmp/receipt-parent-transaction.json ``` @@ -4704,13 +4748,32 @@ ORIGIN_CONTRACT_ID=wrap.near DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near ``` -1. Получите транзакцию и распечатайте downstream-receipt вместе с callback-receipt. +1. Получите транзакцию и сохраните receipt-цепочку. ```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | tee /tmp/callback-check-transaction.json >/dev/null +``` + +2. Сначала ответьте на самый короткий полезный вопрос: вернулся ли callback вообще? + +```bash +jq --arg origin "$ORIGIN_CONTRACT_ID" ' + [ + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + ] | length > 0 +' /tmp/callback-check-transaction.json +``` + +3. Если ответ `true`, распечатайте downstream-receipt вместе с callback-receipt. + +```bash CALLBACK_RECEIPT_ID="$( jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' @@ -4741,7 +4804,12 @@ jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), status: .execution_outcome.outcome.status, block_height: .execution_outcome.block_height } @@ -4783,7 +4851,7 @@ jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" # - callback_ran равно true, даже несмотря на downstream-сбой ``` -2. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. +4. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. ```bash curl -s "$RPC_URL" \ @@ -4833,7 +4901,6 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ - Пытаться отправлять транзакцию через history API вместо сырого RPC. - Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. - Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». -- Повторно использовать непрозрачные токены пагинации с другим эндпоинтом или другим набором фильтров. ## Полезные связанные страницы diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index 6232719..95b5e4b 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -266,7 +266,12 @@ jq --arg fragment "$LOG_FRAGMENT" '{ receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), block_height: .execution_outcome.block_height, logs: .execution_outcome.outcome.logs } @@ -290,7 +295,12 @@ jq '{ | { receipt_id: .receipt.receipt_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), logs: .execution_outcome.outcome.logs } ] @@ -415,8 +425,19 @@ jq '{ ```bash jq -r ' + def zeros($n): + reduce range(0; $n) as $i (""; . + "0"); + def yocto_to_near($yocto): + ($yocto | tostring) as $digits + | if ($digits | length) <= 24 then + ("0." + zeros(24 - ($digits | length)) + $digits) + else + ($digits[0:(($digits | length) - 24)] + "." + $digits[-24:]) + end + | sub("0+$"; "") + | sub("\\.$"; ""); .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил \(yocto_to_near($tx.transaction.actions[0].Transfer.deposit)) NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." ' /tmp/receipt-parent-transaction.json ``` @@ -856,13 +877,32 @@ ORIGIN_CONTRACT_ID=wrap.near DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near ``` -1. Получите транзакцию и распечатайте downstream-receipt вместе с callback-receipt. +1. Получите транзакцию и сохраните receipt-цепочку. ```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | tee /tmp/callback-check-transaction.json >/dev/null +``` + +2. Сначала ответьте на самый короткий полезный вопрос: вернулся ли callback вообще? + +```bash +jq --arg origin "$ORIGIN_CONTRACT_ID" ' + [ + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + ] | length > 0 +' /tmp/callback-check-transaction.json +``` + +3. Если ответ `true`, распечатайте downstream-receipt вместе с callback-receipt. + +```bash CALLBACK_RECEIPT_ID="$( jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' @@ -893,7 +933,12 @@ jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), status: .execution_outcome.outcome.status, block_height: .execution_outcome.block_height } @@ -935,7 +980,7 @@ jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" # - callback_ran равно true, даже несмотря на downstream-сбой ``` -2. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. +4. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. ```bash curl -s "$RPC_URL" \ @@ -985,7 +1030,6 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ - Пытаться отправлять транзакцию через history API вместо сырого RPC. - Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. - Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». -- Повторно использовать непрозрачные токены пагинации с другим эндпоинтом или другим набором фильтров. ## Полезные связанные страницы diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index 6232719..95b5e4b 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -266,7 +266,12 @@ jq --arg fragment "$LOG_FRAGMENT" '{ receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), block_height: .execution_outcome.block_height, logs: .execution_outcome.outcome.logs } @@ -290,7 +295,12 @@ jq '{ | { receipt_id: .receipt.receipt_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), logs: .execution_outcome.outcome.logs } ] @@ -415,8 +425,19 @@ jq '{ ```bash jq -r ' + def zeros($n): + reduce range(0; $n) as $i (""; . + "0"); + def yocto_to_near($yocto): + ($yocto | tostring) as $digits + | if ($digits | length) <= 24 then + ("0." + zeros(24 - ($digits | length)) + $digits) + else + ($digits[0:(($digits | length) - 24)] + "." + $digits[-24:]) + end + | sub("0+$"; "") + | sub("\\.$"; ""); .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил \(yocto_to_near($tx.transaction.actions[0].Transfer.deposit)) NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." ' /tmp/receipt-parent-transaction.json ``` @@ -856,13 +877,32 @@ ORIGIN_CONTRACT_ID=wrap.near DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near ``` -1. Получите транзакцию и распечатайте downstream-receipt вместе с callback-receipt. +1. Получите транзакцию и сохраните receipt-цепочку. ```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | tee /tmp/callback-check-transaction.json >/dev/null +``` + +2. Сначала ответьте на самый короткий полезный вопрос: вернулся ли callback вообще? + +```bash +jq --arg origin "$ORIGIN_CONTRACT_ID" ' + [ + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + ] | length > 0 +' /tmp/callback-check-transaction.json +``` + +3. Если ответ `true`, распечатайте downstream-receipt вместе с callback-receipt. + +```bash CALLBACK_RECEIPT_ID="$( jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' @@ -893,7 +933,12 @@ jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" receipt_id: .receipt.receipt_id, predecessor_id: .receipt.predecessor_id, receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "transfer"), + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), status: .execution_outcome.outcome.status, block_height: .execution_outcome.block_height } @@ -935,7 +980,7 @@ jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" # - callback_ran равно true, даже несмотря на downstream-сбой ``` -2. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. +4. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. ```bash curl -s "$RPC_URL" \ @@ -985,7 +1030,6 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ - Пытаться отправлять транзакцию через history API вместо сырого RPC. - Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. - Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». -- Повторно использовать непрозрачные токены пагинации с другим эндпоинтом или другим набором фильтров. ## Полезные связанные страницы From 9740d32bbd0a854092321a06c068b13d1a3d57d0 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sun, 19 Apr 2026 11:25:49 -0700 Subject: [PATCH 20/35] docs: expand transfers examples around filtered flows --- docs/transfers/examples.md | 174 ++++++++++++++---- .../current/transfers/examples.md | 174 ++++++++++++++---- static/ru/guides/llms.txt | 2 +- static/ru/llms-full.txt | 172 +++++++++++++---- static/ru/llms.txt | 2 +- static/ru/transfers/examples.md | 172 +++++++++++++---- static/ru/transfers/examples/index.md | 172 +++++++++++++---- 7 files changed, 684 insertions(+), 184 deletions(-) diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index 49661e2..76a6e28 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /transfers/examples title: Transfers Examples -description: Plain-language workflows for checking whether funds moved in one window and optionally anchoring one row to a receipt. +description: Plain-language workflows for filtering transfers, reading humanized amounts and running balances, and pivoting into receipt or transaction context. displayed_sidebar: transfersApiSidebar page_actions: - markdown @@ -10,26 +10,27 @@ page_actions: ## Quick start -Start with one tight outgoing window and print the rows before you chase receipts. +Start with one filtered incoming query and surface the fields that make Transfers API worth using. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -FROM_TIMESTAMP_MS=1711929600000 -TO_TIMESTAMP_MS=1712016000000 +ACCOUNT_ID=intents.near +ASSET_ID=native:near +MIN_AMOUNT_YOCTO=1000000000000000000000000 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" '{ account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, desc: true, - limit: 10 + limit: 5 }')" \ | jq '{ resume_token, @@ -40,6 +41,21 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ receipt_id, asset_id, amount, + human_amount: ( + if .human_amount == null then null + else (.human_amount * 1000 | round / 1000) + end + ), + usd_amount: ( + if .usd_amount == null then null + else (.usd_amount * 100 | round / 100) + end + ), + block_timestamp, + method_name, + transfer_type, + start_of_block_balance, + end_of_block_balance, other_account_id, block_height } @@ -47,51 +63,55 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ }' ``` -This is the shortest way to answer “did funds move here, and which row should I inspect next?” +This is the shortest way to answer “which 1+ NEAR transfers hit this account, what were they worth, and which row should I inspect next?” `usd_amount` can be `null` when pricing is not available for that row. ## Worked walkthrough -### Did this account send funds in this window, and which row should I inspect? +### Which incoming transfers of 1+ NEAR hit this account, and which row should I inspect? -Use this when the user story is “I need one narrow outgoing window first, and only after I see the rows will I decide whether one of them needs a receipt-level follow-up.” +Use this when the user story is “I need one narrow transfer search first, I want the row fields that already look like wallet or analytics data, and only after that will I decide whether one row needs deeper follow-up.”
Strategy -

Answer the movement question first, then widen once only if one row still needs an execution anchor.

+

Use Transfers API for the filtered movement answer first, then widen only when one row still needs chain context.

-

01POST /v0/transfers gives you the tight outgoing window and the specific movement worth chasing.

-

02Print the rows first, then choose one transfer_index before lifting its receipt_id.

-

03POST /v0/receipt is the optional follow-up when you want to know what one transfer row did on chain.

+

01POST /v0/transfers does the filtering work first: receiver-side rows, one asset, system transfers hidden, and a minimum amount threshold.

+

02Print the distinctive row fields first: human_amount, usd_amount, method_name, transfer_type, and the running balances.

+

03If you need more rows, reuse the opaque resume_token with the exact same filters.

+

04Only then choose one row and decide whether you want its receipt_id as an execution anchor or its transaction_id as a readable story anchor.

**What you're doing** -- Query a bounded outgoing transfer window for one account on mainnet. -- Print the rows first, then choose one transfer row that looks like the movement you care about. -- Reuse its `receipt_id` only if you need to move from balance movement into execution history. +- Query a filtered incoming transfer window for one active mainnet account. +- Print the row fields that Transfers API already normalizes for you. +- Reuse the same filters with `resume_token` if you need another page. +- Lift either `receipt_id` or `transaction_id` only when one row still needs a deeper story. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -FROM_TIMESTAMP_MS=1711929600000 -TO_TIMESTAMP_MS=1712016000000 +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=intents.near +ASSET_ID=native:near +MIN_AMOUNT_YOCTO=1000000000000000000000000 TRANSFER_INDEX=0 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" '{ account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, desc: true, - limit: 10 + limit: 5 }')" \ | tee /tmp/transfers-window.json >/dev/null @@ -106,12 +126,74 @@ jq '{ receipt_id: .value.receipt_id, asset_id: .value.asset_id, amount: .value.amount, + human_amount: ( + if .value.human_amount == null then null + else (.value.human_amount * 1000 | round / 1000) + end + ), + usd_amount: ( + if .value.usd_amount == null then null + else (.value.usd_amount * 100 | round / 100) + end + ), + block_timestamp: .value.block_timestamp, + method_name: .value.method_name, + transfer_type: .value.transfer_type, + start_of_block_balance: .value.start_of_block_balance, + end_of_block_balance: .value.end_of_block_balance, other_account_id: .value.other_account_id, block_height: .value.block_height } ] }' /tmp/transfers-window.json +RESUME_TOKEN="$( + jq -r '.resume_token // empty' /tmp/transfers-window.json +)" + +if [ -n "$RESUME_TOKEN" ]; then + curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" \ + --arg resume_token "$RESUME_TOKEN" '{ + account_id: $account_id, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, + desc: true, + limit: 5, + resume_token: $resume_token + }')" \ + | jq '{ + next_page_resume_token: .resume_token, + next_transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + human_amount: ( + if .human_amount == null then null + else (.human_amount * 1000 | round / 1000) + end + ), + transfer_type, + other_account_id, + block_height + } + ] + }' +fi + +TRANSACTION_ID="$( + jq -r --argjson transfer_index "$TRANSFER_INDEX" \ + '.transfers[$transfer_index].transaction_id // empty' \ + /tmp/transfers-window.json +)" + RECEIPT_ID="$( jq -r --argjson transfer_index "$TRANSFER_INDEX" \ '.transfers[$transfer_index].receipt_id // empty' \ @@ -119,18 +201,17 @@ RECEIPT_ID="$( )" printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" +printf 'Chosen transaction id: %s\n' "$TRANSACTION_ID" printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" ``` -That answers the first question: did funds move here, and which transfer row should you inspect next? +That answers the first question: which filtered rows match, what were they worth, and which transfer row should you inspect next? -#### Optional follow-up: What did this transfer row do on chain? +#### Optional follow-up: Receipt anchor or transaction story? -Only widen to receipt history if the transfer row itself is not enough. +Use `receipt_id` when you want the execution anchor for the row itself. Use `transaction_id` when you want the readable story of what the signer submitted. ```bash -TX_BASE_URL=https://tx.main.fastnear.com - if [ -n "$RECEIPT_ID" ]; then curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ @@ -142,18 +223,37 @@ if [ -n "$RECEIPT_ID" ]; then tx_block_height: .receipt.tx_block_height }' fi + +if [ -n "$TRANSACTION_ID" ]; then + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TRANSACTION_ID" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ) + }' +fi ``` **Why this next step?** -The transfer query answers the first question quickly: did this account send funds in this window, and to whom? Looking up the `receipt_id` is the optional second question: what execution anchor sits behind this one row? If you still need more rows afterward, keep paginating with the same `resume_token` and unchanged filters. +This is where Transfers API earns its keep. The first query already answers the movement question in wallet- or analytics-friendly terms: filtered rows, humanized amounts, transfer type, method clue, and running balances. If you still need another page, reuse the same `resume_token` with the same filters. If you need chain context, follow `receipt_id` for the execution anchor or `transaction_id` for the readable transaction story. ## Common mistakes - Using Transfers API when the user really wants balances, holdings, or account summaries. -- Treating transfer history as full execution history. +- Treating transfer history as full execution history instead of a filtered movement view. - Reusing a `resume_token` with different filters. +- Ignoring `method_name`, `transfer_type`, or running balances even though they are often the reason to use this API over raw transaction history. - Starting here for testnet questions; this API is mainnet-only today. ## Related guides diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md index f9c2ce1..c1f339d 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /transfers/examples title: "Примеры Transfers API" -description: "Пошаговые сценарии для проверки, было ли движение средств в одном окне, и необязательного перехода от одной строки к receipt." +description: "Пошаговые сценарии для фильтрации переводов, чтения humanized amount и running balances, а также перехода к receipt- или transaction-контексту." displayed_sidebar: transfersApiSidebar page_actions: - markdown @@ -10,26 +10,27 @@ page_actions: ## Быстрый старт -Начните с узкого окна исходящих переводов и сначала выведите строки, а уже потом переходите к receipts. +Начните с одного отфильтрованного входящего запроса и сразу выведите поля, ради которых вообще стоит использовать Transfers API. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -FROM_TIMESTAMP_MS=1711929600000 -TO_TIMESTAMP_MS=1712016000000 +ACCOUNT_ID=intents.near +ASSET_ID=native:near +MIN_AMOUNT_YOCTO=1000000000000000000000000 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" '{ account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, desc: true, - limit: 10 + limit: 5 }')" \ | jq '{ resume_token, @@ -40,6 +41,21 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ receipt_id, asset_id, amount, + human_amount: ( + if .human_amount == null then null + else (.human_amount * 1000 | round / 1000) + end + ), + usd_amount: ( + if .usd_amount == null then null + else (.usd_amount * 100 | round / 100) + end + ), + block_timestamp, + method_name, + transfer_type, + start_of_block_balance, + end_of_block_balance, other_account_id, block_height } @@ -47,51 +63,55 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ }' ``` -Это самый короткий путь к вопросу «были ли здесь движения средств и какую строку стоит разбирать дальше?» +Это самый короткий путь к вопросу «какие переводы от 1+ NEAR пришли на этот аккаунт, чего они стоили и какую строку стоит разбирать дальше?» `usd_amount` может быть `null`, если для этой строки нет ценового покрытия. ## Готовый сценарий -### Отправлял ли этот аккаунт средства в этом окне и какую строку стоит разобрать? +### Какие входящие переводы от 1+ NEAR попали на этот аккаунт и какую строку стоит разобрать? -Используйте этот сценарий, когда история звучит так: «мне сначала нужно одно узкое окно исходящих переводов, и только после просмотра строк я решу, нужен ли одной из них follow-up по receipt». +Используйте этот сценарий, когда история звучит так: «сначала мне нужен один узкий поиск переводов, я хочу поля, которые уже похожи на wallet- или analytics-данные, и только после этого решу, нужна ли одной строке более глубокая расшифровка».
Стратегия -

Сначала ответьте на вопрос о движении средств, а затем расширяйтесь только если одной строке всё ещё нужен execution-anchor.

+

Сначала используйте Transfers API ради отфильтрованного ответа о движении средств, а расширяйтесь только если одной строке всё ещё нужен chain-контекст.

-

01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять.

-

02Сначала выведите строки, а затем явно выберите один transfer_index перед тем, как поднимать его receipt_id.

-

03POST /v0/receipt — необязательный follow-up, когда вы хотите понять, что именно эта строка перевода сделала on-chain.

+

01POST /v0/transfers сначала делает всю фильтрацию: входящая сторона, один asset, скрытие system-переводов и порог по минимальной сумме.

+

02Сначала выведите отличительные поля строки: human_amount, usd_amount, method_name, transfer_type и running balances.

+

03Если нужны ещё строки, переиспользуйте непрозрачный resume_token с точно теми же фильтрами.

+

04И только потом выбирайте одну строку и решайте, нужен ли вам её receipt_id как execution-anchor или её transaction_id как якорь для читаемой истории.

**Что вы делаете** -- Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Сначала выводите строки, а затем выбираете одну строку перевода, которая действительно похожа на нужное вам движение. -- Переиспользуете её `receipt_id` только если нужно перейти от движения актива к истории исполнения. +- Запрашиваете отфильтрованное окно входящих переводов для одного активного mainnet-аккаунта. +- Сначала печатаете поля строки, которые Transfers API уже нормализует за вас. +- Переиспользуете тот же `resume_token`, если вам нужна следующая страница. +- Поднимаете либо `receipt_id`, либо `transaction_id` только тогда, когда одной строке всё ещё нужна более глубокая история. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -FROM_TIMESTAMP_MS=1711929600000 -TO_TIMESTAMP_MS=1712016000000 +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=intents.near +ASSET_ID=native:near +MIN_AMOUNT_YOCTO=1000000000000000000000000 TRANSFER_INDEX=0 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" '{ account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, desc: true, - limit: 10 + limit: 5 }')" \ | tee /tmp/transfers-window.json >/dev/null @@ -106,12 +126,74 @@ jq '{ receipt_id: .value.receipt_id, asset_id: .value.asset_id, amount: .value.amount, + human_amount: ( + if .value.human_amount == null then null + else (.value.human_amount * 1000 | round / 1000) + end + ), + usd_amount: ( + if .value.usd_amount == null then null + else (.value.usd_amount * 100 | round / 100) + end + ), + block_timestamp: .value.block_timestamp, + method_name: .value.method_name, + transfer_type: .value.transfer_type, + start_of_block_balance: .value.start_of_block_balance, + end_of_block_balance: .value.end_of_block_balance, other_account_id: .value.other_account_id, block_height: .value.block_height } ] }' /tmp/transfers-window.json +RESUME_TOKEN="$( + jq -r '.resume_token // empty' /tmp/transfers-window.json +)" + +if [ -n "$RESUME_TOKEN" ]; then + curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" \ + --arg resume_token "$RESUME_TOKEN" '{ + account_id: $account_id, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, + desc: true, + limit: 5, + resume_token: $resume_token + }')" \ + | jq '{ + next_page_resume_token: .resume_token, + next_transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + human_amount: ( + if .human_amount == null then null + else (.human_amount * 1000 | round / 1000) + end + ), + transfer_type, + other_account_id, + block_height + } + ] + }' +fi + +TRANSACTION_ID="$( + jq -r --argjson transfer_index "$TRANSFER_INDEX" \ + '.transfers[$transfer_index].transaction_id // empty' \ + /tmp/transfers-window.json +)" + RECEIPT_ID="$( jq -r --argjson transfer_index "$TRANSFER_INDEX" \ '.transfers[$transfer_index].receipt_id // empty' \ @@ -119,18 +201,17 @@ RECEIPT_ID="$( )" printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" +printf 'Chosen transaction id: %s\n' "$TRANSACTION_ID" printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" ``` -Этим вы отвечаете на первый вопрос: было ли здесь движение средств и какую строку перевода стоит разбирать дальше? +Этим вы отвечаете на первый вопрос: какие отфильтрованные строки совпали, чего они стоили и какую строку перевода стоит разбирать дальше? -#### Необязательное продолжение: Что сделала эта строка перевода on-chain? +#### Необязательное продолжение: execution-anchor или transaction-story? -Переходите к истории receipt только если самой строки перевода уже недостаточно. +Используйте `receipt_id`, когда нужен execution-anchor именно для этой строки. Используйте `transaction_id`, когда нужна читаемая история того, что именно подписал signer. ```bash -TX_BASE_URL=https://tx.main.fastnear.com - if [ -n "$RECEIPT_ID" ]; then curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ @@ -142,18 +223,37 @@ if [ -n "$RECEIPT_ID" ]; then tx_block_height: .receipt.tx_block_height }' fi + +if [ -n "$TRANSACTION_ID" ]; then + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TRANSACTION_ID" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ) + }' +fi ``` **Зачем нужен следующий шаг?** -Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный второй вопрос: какая execution-anchor стоит за этой одной строкой? Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. +Именно здесь Transfers API показывает свою ценность. Первый запрос уже отвечает на вопрос о движении средств в терминах, удобных для wallet- и analytics-сценариев: отфильтрованные строки, humanized amount, тип перевода, method-clue и running balances. Если всё ещё нужна следующая страница, переиспользуйте тот же `resume_token` с теми же фильтрами. Если нужен chain-контекст, следуйте по `receipt_id` ради execution-anchor или по `transaction_id` ради читаемой истории транзакции. ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. -- Считать историю переводов полной историей исполнения. +- Считать историю переводов полной историей исполнения вместо отфильтрованного movement-view. - Переиспользовать `resume_token` с другими фильтрами. +- Игнорировать `method_name`, `transfer_type` или running balances, хотя именно из-за них этот API часто удобнее сырой transaction-history. - Начинать здесь с вопросов про testnet, хотя этот API сегодня работает только в mainnet. ## Полезные связанные страницы diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index 6d4c709..b21e408 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -33,7 +33,7 @@ - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. - [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных идентификаторов для дальнейшего разбора. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. -- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для проверки, было ли движение средств в одном окне, и необязательного перехода от одной строки к receipt. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для фильтрации переводов, чтения humanized amount и running balances, а также перехода к receipt- или transaction-контексту. - [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика. - [Berry Club: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Прочитайте живую доску Berry Club через RPC get_lines, а затем используйте Transactions API, чтобы восстановить одну более раннюю эпоху. - [OutLayer: что сделала эта пара request/resolution?](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы прочитать один caller-side запрос OutLayer, одно более позднее worker-side resolution и обращаться к finish-receipts только когда это действительно нужно. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index e197ce4..cd4f891 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -3647,26 +3647,27 @@ https://transfers.main.fastnear.com ## Быстрый старт -Начните с узкого окна исходящих переводов и сначала выведите строки, а уже потом переходите к receipts. +Начните с одного отфильтрованного входящего запроса и сразу выведите поля, ради которых вообще стоит использовать Transfers API. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -FROM_TIMESTAMP_MS=1711929600000 -TO_TIMESTAMP_MS=1712016000000 +ACCOUNT_ID=intents.near +ASSET_ID=native:near +MIN_AMOUNT_YOCTO=1000000000000000000000000 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" '{ account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, desc: true, - limit: 10 + limit: 5 }')" \ | jq '{ resume_token, @@ -3677,6 +3678,21 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ receipt_id, asset_id, amount, + human_amount: ( + if .human_amount == null then null + else (.human_amount * 1000 | round / 1000) + end + ), + usd_amount: ( + if .usd_amount == null then null + else (.usd_amount * 100 | round / 100) + end + ), + block_timestamp, + method_name, + transfer_type, + start_of_block_balance, + end_of_block_balance, other_account_id, block_height } @@ -3684,46 +3700,50 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ }' ``` -Это самый короткий путь к вопросу «были ли здесь движения средств и какую строку стоит разбирать дальше?» +Это самый короткий путь к вопросу «какие переводы от 1+ NEAR пришли на этот аккаунт, чего они стоили и какую строку стоит разбирать дальше?» `usd_amount` может быть `null`, если для этой строки нет ценового покрытия. ## Готовый сценарий -### Отправлял ли этот аккаунт средства в этом окне и какую строку стоит разобрать? +### Какие входящие переводы от 1+ NEAR попали на этот аккаунт и какую строку стоит разобрать? -Используйте этот сценарий, когда история звучит так: «мне сначала нужно одно узкое окно исходящих переводов, и только после просмотра строк я решу, нужен ли одной из них follow-up по receipt». +Используйте этот сценарий, когда история звучит так: «сначала мне нужен один узкий поиск переводов, я хочу поля, которые уже похожи на wallet- или analytics-данные, и только после этого решу, нужна ли одной строке более глубокая расшифровка». Стратегия - Сначала ответьте на вопрос о движении средств, а затем расширяйтесь только если одной строке всё ещё нужен execution-anchor. + Сначала используйте Transfers API ради отфильтрованного ответа о движении средств, а расширяйтесь только если одной строке всё ещё нужен chain-контекст. - 01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять. - 02Сначала выведите строки, а затем явно выберите один transfer_index перед тем, как поднимать его receipt_id. - 03POST /v0/receipt — необязательный follow-up, когда вы хотите понять, что именно эта строка перевода сделала on-chain. + 01POST /v0/transfers сначала делает всю фильтрацию: входящая сторона, один asset, скрытие system-переводов и порог по минимальной сумме. + 02Сначала выведите отличительные поля строки: human_amount, usd_amount, method_name, transfer_type и running balances. + 03Если нужны ещё строки, переиспользуйте непрозрачный resume_token с точно теми же фильтрами. + 04И только потом выбирайте одну строку и решайте, нужен ли вам её receipt_id как execution-anchor или её transaction_id как якорь для читаемой истории. **Что вы делаете** -- Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Сначала выводите строки, а затем выбираете одну строку перевода, которая действительно похожа на нужное вам движение. -- Переиспользуете её `receipt_id` только если нужно перейти от движения актива к истории исполнения. +- Запрашиваете отфильтрованное окно входящих переводов для одного активного mainnet-аккаунта. +- Сначала печатаете поля строки, которые Transfers API уже нормализует за вас. +- Переиспользуете тот же `resume_token`, если вам нужна следующая страница. +- Поднимаете либо `receipt_id`, либо `transaction_id` только тогда, когда одной строке всё ещё нужна более глубокая история. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -FROM_TIMESTAMP_MS=1711929600000 -TO_TIMESTAMP_MS=1712016000000 +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=intents.near +ASSET_ID=native:near +MIN_AMOUNT_YOCTO=1000000000000000000000000 TRANSFER_INDEX=0 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" '{ account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, desc: true, - limit: 10 + limit: 5 }')" \ | tee /tmp/transfers-window.json >/dev/null @@ -3738,12 +3758,74 @@ jq '{ receipt_id: .value.receipt_id, asset_id: .value.asset_id, amount: .value.amount, + human_amount: ( + if .value.human_amount == null then null + else (.value.human_amount * 1000 | round / 1000) + end + ), + usd_amount: ( + if .value.usd_amount == null then null + else (.value.usd_amount * 100 | round / 100) + end + ), + block_timestamp: .value.block_timestamp, + method_name: .value.method_name, + transfer_type: .value.transfer_type, + start_of_block_balance: .value.start_of_block_balance, + end_of_block_balance: .value.end_of_block_balance, other_account_id: .value.other_account_id, block_height: .value.block_height } ] }' /tmp/transfers-window.json +RESUME_TOKEN="$( + jq -r '.resume_token // empty' /tmp/transfers-window.json +)" + +if [ -n "$RESUME_TOKEN" ]; then + curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" \ + --arg resume_token "$RESUME_TOKEN" '{ + account_id: $account_id, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, + desc: true, + limit: 5, + resume_token: $resume_token + }')" \ + | jq '{ + next_page_resume_token: .resume_token, + next_transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + human_amount: ( + if .human_amount == null then null + else (.human_amount * 1000 | round / 1000) + end + ), + transfer_type, + other_account_id, + block_height + } + ] + }' +fi + +TRANSACTION_ID="$( + jq -r --argjson transfer_index "$TRANSFER_INDEX" \ + '.transfers[$transfer_index].transaction_id // empty' \ + /tmp/transfers-window.json +)" + RECEIPT_ID="$( jq -r --argjson transfer_index "$TRANSFER_INDEX" \ '.transfers[$transfer_index].receipt_id // empty' \ @@ -3751,18 +3833,17 @@ RECEIPT_ID="$( )" printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" +printf 'Chosen transaction id: %s\n' "$TRANSACTION_ID" printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" ``` -Этим вы отвечаете на первый вопрос: было ли здесь движение средств и какую строку перевода стоит разбирать дальше? +Этим вы отвечаете на первый вопрос: какие отфильтрованные строки совпали, чего они стоили и какую строку перевода стоит разбирать дальше? -#### Необязательное продолжение: Что сделала эта строка перевода on-chain? +#### Необязательное продолжение: execution-anchor или transaction-story? -Переходите к истории receipt только если самой строки перевода уже недостаточно. +Используйте `receipt_id`, когда нужен execution-anchor именно для этой строки. Используйте `transaction_id`, когда нужна читаемая история того, что именно подписал signer. ```bash -TX_BASE_URL=https://tx.main.fastnear.com - if [ -n "$RECEIPT_ID" ]; then curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ @@ -3774,17 +3855,36 @@ if [ -n "$RECEIPT_ID" ]; then tx_block_height: .receipt.tx_block_height }' fi + +if [ -n "$TRANSACTION_ID" ]; then + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TRANSACTION_ID" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ) + }' +fi ``` **Зачем нужен следующий шаг?** -Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный второй вопрос: какая execution-anchor стоит за этой одной строкой? Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. +Именно здесь Transfers API показывает свою ценность. Первый запрос уже отвечает на вопрос о движении средств в терминах, удобных для wallet- и analytics-сценариев: отфильтрованные строки, humanized amount, тип перевода, method-clue и running balances. Если всё ещё нужна следующая страница, переиспользуйте тот же `resume_token` с теми же фильтрами. Если нужен chain-контекст, следуйте по `receipt_id` ради execution-anchor или по `transaction_id` ради читаемой истории транзакции. ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. -- Считать историю переводов полной историей исполнения. +- Считать историю переводов полной историей исполнения вместо отфильтрованного movement-view. - Переиспользовать `resume_token` с другими фильтрами. +- Игнорировать `method_name`, `transfer_type` или running balances, хотя именно из-за них этот API часто удобнее сырой transaction-history. - Начинать здесь с вопросов про testnet, хотя этот API сегодня работает только в mainnet. ## Полезные связанные страницы diff --git a/static/ru/llms.txt b/static/ru/llms.txt index c3b606e..40853ad 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -36,7 +36,7 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. - [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных идентификаторов для дальнейшего разбора. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. -- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для проверки, было ли движение средств в одном окне, и необязательного перехода от одной строки к receipt. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для фильтрации переводов, чтения humanized amount и running balances, а также перехода к receipt- или transaction-контексту. - [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика. - [Berry Club: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Прочитайте живую доску Berry Club через RPC get_lines, а затем используйте Transactions API, чтобы восстановить одну более раннюю эпоху. - [OutLayer: что сделала эта пара request/resolution?](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы прочитать один caller-side запрос OutLayer, одно более позднее worker-side resolution и обращаться к finish-receipts только когда это действительно нужно. diff --git a/static/ru/transfers/examples.md b/static/ru/transfers/examples.md index dccd77c..452fcc3 100644 --- a/static/ru/transfers/examples.md +++ b/static/ru/transfers/examples.md @@ -2,26 +2,27 @@ ## Быстрый старт -Начните с узкого окна исходящих переводов и сначала выведите строки, а уже потом переходите к receipts. +Начните с одного отфильтрованного входящего запроса и сразу выведите поля, ради которых вообще стоит использовать Transfers API. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -FROM_TIMESTAMP_MS=1711929600000 -TO_TIMESTAMP_MS=1712016000000 +ACCOUNT_ID=intents.near +ASSET_ID=native:near +MIN_AMOUNT_YOCTO=1000000000000000000000000 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" '{ account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, desc: true, - limit: 10 + limit: 5 }')" \ | jq '{ resume_token, @@ -32,6 +33,21 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ receipt_id, asset_id, amount, + human_amount: ( + if .human_amount == null then null + else (.human_amount * 1000 | round / 1000) + end + ), + usd_amount: ( + if .usd_amount == null then null + else (.usd_amount * 100 | round / 100) + end + ), + block_timestamp, + method_name, + transfer_type, + start_of_block_balance, + end_of_block_balance, other_account_id, block_height } @@ -39,46 +55,50 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ }' ``` -Это самый короткий путь к вопросу «были ли здесь движения средств и какую строку стоит разбирать дальше?» +Это самый короткий путь к вопросу «какие переводы от 1+ NEAR пришли на этот аккаунт, чего они стоили и какую строку стоит разбирать дальше?» `usd_amount` может быть `null`, если для этой строки нет ценового покрытия. ## Готовый сценарий -### Отправлял ли этот аккаунт средства в этом окне и какую строку стоит разобрать? +### Какие входящие переводы от 1+ NEAR попали на этот аккаунт и какую строку стоит разобрать? -Используйте этот сценарий, когда история звучит так: «мне сначала нужно одно узкое окно исходящих переводов, и только после просмотра строк я решу, нужен ли одной из них follow-up по receipt». +Используйте этот сценарий, когда история звучит так: «сначала мне нужен один узкий поиск переводов, я хочу поля, которые уже похожи на wallet- или analytics-данные, и только после этого решу, нужна ли одной строке более глубокая расшифровка». Стратегия - Сначала ответьте на вопрос о движении средств, а затем расширяйтесь только если одной строке всё ещё нужен execution-anchor. + Сначала используйте Transfers API ради отфильтрованного ответа о движении средств, а расширяйтесь только если одной строке всё ещё нужен chain-контекст. - 01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять. - 02Сначала выведите строки, а затем явно выберите один transfer_index перед тем, как поднимать его receipt_id. - 03POST /v0/receipt — необязательный follow-up, когда вы хотите понять, что именно эта строка перевода сделала on-chain. + 01POST /v0/transfers сначала делает всю фильтрацию: входящая сторона, один asset, скрытие system-переводов и порог по минимальной сумме. + 02Сначала выведите отличительные поля строки: human_amount, usd_amount, method_name, transfer_type и running balances. + 03Если нужны ещё строки, переиспользуйте непрозрачный resume_token с точно теми же фильтрами. + 04И только потом выбирайте одну строку и решайте, нужен ли вам её receipt_id как execution-anchor или её transaction_id как якорь для читаемой истории. **Что вы делаете** -- Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Сначала выводите строки, а затем выбираете одну строку перевода, которая действительно похожа на нужное вам движение. -- Переиспользуете её `receipt_id` только если нужно перейти от движения актива к истории исполнения. +- Запрашиваете отфильтрованное окно входящих переводов для одного активного mainnet-аккаунта. +- Сначала печатаете поля строки, которые Transfers API уже нормализует за вас. +- Переиспользуете тот же `resume_token`, если вам нужна следующая страница. +- Поднимаете либо `receipt_id`, либо `transaction_id` только тогда, когда одной строке всё ещё нужна более глубокая история. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -FROM_TIMESTAMP_MS=1711929600000 -TO_TIMESTAMP_MS=1712016000000 +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=intents.near +ASSET_ID=native:near +MIN_AMOUNT_YOCTO=1000000000000000000000000 TRANSFER_INDEX=0 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" '{ account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, desc: true, - limit: 10 + limit: 5 }')" \ | tee /tmp/transfers-window.json >/dev/null @@ -93,12 +113,74 @@ jq '{ receipt_id: .value.receipt_id, asset_id: .value.asset_id, amount: .value.amount, + human_amount: ( + if .value.human_amount == null then null + else (.value.human_amount * 1000 | round / 1000) + end + ), + usd_amount: ( + if .value.usd_amount == null then null + else (.value.usd_amount * 100 | round / 100) + end + ), + block_timestamp: .value.block_timestamp, + method_name: .value.method_name, + transfer_type: .value.transfer_type, + start_of_block_balance: .value.start_of_block_balance, + end_of_block_balance: .value.end_of_block_balance, other_account_id: .value.other_account_id, block_height: .value.block_height } ] }' /tmp/transfers-window.json +RESUME_TOKEN="$( + jq -r '.resume_token // empty' /tmp/transfers-window.json +)" + +if [ -n "$RESUME_TOKEN" ]; then + curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" \ + --arg resume_token "$RESUME_TOKEN" '{ + account_id: $account_id, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, + desc: true, + limit: 5, + resume_token: $resume_token + }')" \ + | jq '{ + next_page_resume_token: .resume_token, + next_transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + human_amount: ( + if .human_amount == null then null + else (.human_amount * 1000 | round / 1000) + end + ), + transfer_type, + other_account_id, + block_height + } + ] + }' +fi + +TRANSACTION_ID="$( + jq -r --argjson transfer_index "$TRANSFER_INDEX" \ + '.transfers[$transfer_index].transaction_id // empty' \ + /tmp/transfers-window.json +)" + RECEIPT_ID="$( jq -r --argjson transfer_index "$TRANSFER_INDEX" \ '.transfers[$transfer_index].receipt_id // empty' \ @@ -106,18 +188,17 @@ RECEIPT_ID="$( )" printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" +printf 'Chosen transaction id: %s\n' "$TRANSACTION_ID" printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" ``` -Этим вы отвечаете на первый вопрос: было ли здесь движение средств и какую строку перевода стоит разбирать дальше? +Этим вы отвечаете на первый вопрос: какие отфильтрованные строки совпали, чего они стоили и какую строку перевода стоит разбирать дальше? -#### Необязательное продолжение: Что сделала эта строка перевода on-chain? +#### Необязательное продолжение: execution-anchor или transaction-story? -Переходите к истории receipt только если самой строки перевода уже недостаточно. +Используйте `receipt_id`, когда нужен execution-anchor именно для этой строки. Используйте `transaction_id`, когда нужна читаемая история того, что именно подписал signer. ```bash -TX_BASE_URL=https://tx.main.fastnear.com - if [ -n "$RECEIPT_ID" ]; then curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ @@ -129,17 +210,36 @@ if [ -n "$RECEIPT_ID" ]; then tx_block_height: .receipt.tx_block_height }' fi + +if [ -n "$TRANSACTION_ID" ]; then + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TRANSACTION_ID" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ) + }' +fi ``` **Зачем нужен следующий шаг?** -Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный второй вопрос: какая execution-anchor стоит за этой одной строкой? Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. +Именно здесь Transfers API показывает свою ценность. Первый запрос уже отвечает на вопрос о движении средств в терминах, удобных для wallet- и analytics-сценариев: отфильтрованные строки, humanized amount, тип перевода, method-clue и running balances. Если всё ещё нужна следующая страница, переиспользуйте тот же `resume_token` с теми же фильтрами. Если нужен chain-контекст, следуйте по `receipt_id` ради execution-anchor или по `transaction_id` ради читаемой истории транзакции. ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. -- Считать историю переводов полной историей исполнения. +- Считать историю переводов полной историей исполнения вместо отфильтрованного movement-view. - Переиспользовать `resume_token` с другими фильтрами. +- Игнорировать `method_name`, `transfer_type` или running balances, хотя именно из-за них этот API часто удобнее сырой transaction-history. - Начинать здесь с вопросов про testnet, хотя этот API сегодня работает только в mainnet. ## Полезные связанные страницы diff --git a/static/ru/transfers/examples/index.md b/static/ru/transfers/examples/index.md index dccd77c..452fcc3 100644 --- a/static/ru/transfers/examples/index.md +++ b/static/ru/transfers/examples/index.md @@ -2,26 +2,27 @@ ## Быстрый старт -Начните с узкого окна исходящих переводов и сначала выведите строки, а уже потом переходите к receipts. +Начните с одного отфильтрованного входящего запроса и сразу выведите поля, ради которых вообще стоит использовать Transfers API. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -FROM_TIMESTAMP_MS=1711929600000 -TO_TIMESTAMP_MS=1712016000000 +ACCOUNT_ID=intents.near +ASSET_ID=native:near +MIN_AMOUNT_YOCTO=1000000000000000000000000 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" '{ account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, desc: true, - limit: 10 + limit: 5 }')" \ | jq '{ resume_token, @@ -32,6 +33,21 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ receipt_id, asset_id, amount, + human_amount: ( + if .human_amount == null then null + else (.human_amount * 1000 | round / 1000) + end + ), + usd_amount: ( + if .usd_amount == null then null + else (.usd_amount * 100 | round / 100) + end + ), + block_timestamp, + method_name, + transfer_type, + start_of_block_balance, + end_of_block_balance, other_account_id, block_height } @@ -39,46 +55,50 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ }' ``` -Это самый короткий путь к вопросу «были ли здесь движения средств и какую строку стоит разбирать дальше?» +Это самый короткий путь к вопросу «какие переводы от 1+ NEAR пришли на этот аккаунт, чего они стоили и какую строку стоит разбирать дальше?» `usd_amount` может быть `null`, если для этой строки нет ценового покрытия. ## Готовый сценарий -### Отправлял ли этот аккаунт средства в этом окне и какую строку стоит разобрать? +### Какие входящие переводы от 1+ NEAR попали на этот аккаунт и какую строку стоит разобрать? -Используйте этот сценарий, когда история звучит так: «мне сначала нужно одно узкое окно исходящих переводов, и только после просмотра строк я решу, нужен ли одной из них follow-up по receipt». +Используйте этот сценарий, когда история звучит так: «сначала мне нужен один узкий поиск переводов, я хочу поля, которые уже похожи на wallet- или analytics-данные, и только после этого решу, нужна ли одной строке более глубокая расшифровка». Стратегия - Сначала ответьте на вопрос о движении средств, а затем расширяйтесь только если одной строке всё ещё нужен execution-anchor. + Сначала используйте Transfers API ради отфильтрованного ответа о движении средств, а расширяйтесь только если одной строке всё ещё нужен chain-контекст. - 01POST /v0/transfers даёт узкое исходящее окно и конкретное движение, которое стоит догонять. - 02Сначала выведите строки, а затем явно выберите один transfer_index перед тем, как поднимать его receipt_id. - 03POST /v0/receipt — необязательный follow-up, когда вы хотите понять, что именно эта строка перевода сделала on-chain. + 01POST /v0/transfers сначала делает всю фильтрацию: входящая сторона, один asset, скрытие system-переводов и порог по минимальной сумме. + 02Сначала выведите отличительные поля строки: human_amount, usd_amount, method_name, transfer_type и running balances. + 03Если нужны ещё строки, переиспользуйте непрозрачный resume_token с точно теми же фильтрами. + 04И только потом выбирайте одну строку и решайте, нужен ли вам её receipt_id как execution-anchor или её transaction_id как якорь для читаемой истории. **Что вы делаете** -- Запрашиваете ограниченное окно исходящих переводов одного аккаунта в mainnet. -- Сначала выводите строки, а затем выбираете одну строку перевода, которая действительно похожа на нужное вам движение. -- Переиспользуете её `receipt_id` только если нужно перейти от движения актива к истории исполнения. +- Запрашиваете отфильтрованное окно входящих переводов для одного активного mainnet-аккаунта. +- Сначала печатаете поля строки, которые Transfers API уже нормализует за вас. +- Переиспользуете тот же `resume_token`, если вам нужна следующая страница. +- Поднимаете либо `receipt_id`, либо `transaction_id` только тогда, когда одной строке всё ещё нужна более глубокая история. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -FROM_TIMESTAMP_MS=1711929600000 -TO_TIMESTAMP_MS=1712016000000 +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=intents.near +ASSET_ID=native:near +MIN_AMOUNT_YOCTO=1000000000000000000000000 TRANSFER_INDEX=0 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ - --argjson from_timestamp_ms "$FROM_TIMESTAMP_MS" \ - --argjson to_timestamp_ms "$TO_TIMESTAMP_MS" '{ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" '{ account_id: $account_id, - direction: "sender", - from_timestamp_ms: $from_timestamp_ms, - to_timestamp_ms: $to_timestamp_ms, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, desc: true, - limit: 10 + limit: 5 }')" \ | tee /tmp/transfers-window.json >/dev/null @@ -93,12 +113,74 @@ jq '{ receipt_id: .value.receipt_id, asset_id: .value.asset_id, amount: .value.amount, + human_amount: ( + if .value.human_amount == null then null + else (.value.human_amount * 1000 | round / 1000) + end + ), + usd_amount: ( + if .value.usd_amount == null then null + else (.value.usd_amount * 100 | round / 100) + end + ), + block_timestamp: .value.block_timestamp, + method_name: .value.method_name, + transfer_type: .value.transfer_type, + start_of_block_balance: .value.start_of_block_balance, + end_of_block_balance: .value.end_of_block_balance, other_account_id: .value.other_account_id, block_height: .value.block_height } ] }' /tmp/transfers-window.json +RESUME_TOKEN="$( + jq -r '.resume_token // empty' /tmp/transfers-window.json +)" + +if [ -n "$RESUME_TOKEN" ]; then + curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT_YOCTO" \ + --arg resume_token "$RESUME_TOKEN" '{ + account_id: $account_id, + direction: "receiver", + asset_id: $asset_id, + ignore_system: true, + min_amount: $min_amount, + desc: true, + limit: 5, + resume_token: $resume_token + }')" \ + | jq '{ + next_page_resume_token: .resume_token, + next_transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + human_amount: ( + if .human_amount == null then null + else (.human_amount * 1000 | round / 1000) + end + ), + transfer_type, + other_account_id, + block_height + } + ] + }' +fi + +TRANSACTION_ID="$( + jq -r --argjson transfer_index "$TRANSFER_INDEX" \ + '.transfers[$transfer_index].transaction_id // empty' \ + /tmp/transfers-window.json +)" + RECEIPT_ID="$( jq -r --argjson transfer_index "$TRANSFER_INDEX" \ '.transfers[$transfer_index].receipt_id // empty' \ @@ -106,18 +188,17 @@ RECEIPT_ID="$( )" printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" +printf 'Chosen transaction id: %s\n' "$TRANSACTION_ID" printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" ``` -Этим вы отвечаете на первый вопрос: было ли здесь движение средств и какую строку перевода стоит разбирать дальше? +Этим вы отвечаете на первый вопрос: какие отфильтрованные строки совпали, чего они стоили и какую строку перевода стоит разбирать дальше? -#### Необязательное продолжение: Что сделала эта строка перевода on-chain? +#### Необязательное продолжение: execution-anchor или transaction-story? -Переходите к истории receipt только если самой строки перевода уже недостаточно. +Используйте `receipt_id`, когда нужен execution-anchor именно для этой строки. Используйте `transaction_id`, когда нужна читаемая история того, что именно подписал signer. ```bash -TX_BASE_URL=https://tx.main.fastnear.com - if [ -n "$RECEIPT_ID" ]; then curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ @@ -129,17 +210,36 @@ if [ -n "$RECEIPT_ID" ]; then tx_block_height: .receipt.tx_block_height }' fi + +if [ -n "$TRANSACTION_ID" ]; then + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TRANSACTION_ID" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ) + }' +fi ``` **Зачем нужен следующий шаг?** -Запрос переводов быстро отвечает на первый вопрос: отправлял ли этот аккаунт средства в этом окне и кому именно? Переход по `receipt_id` — это необязательный второй вопрос: какая execution-anchor стоит за этой одной строкой? Если после этого всё ещё нужно больше строк, продолжайте пагинацию тем же `resume_token` и теми же фильтрами. +Именно здесь Transfers API показывает свою ценность. Первый запрос уже отвечает на вопрос о движении средств в терминах, удобных для wallet- и analytics-сценариев: отфильтрованные строки, humanized amount, тип перевода, method-clue и running balances. Если всё ещё нужна следующая страница, переиспользуйте тот же `resume_token` с теми же фильтрами. Если нужен chain-контекст, следуйте по `receipt_id` ради execution-anchor или по `transaction_id` ради читаемой истории транзакции. ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. -- Считать историю переводов полной историей исполнения. +- Считать историю переводов полной историей исполнения вместо отфильтрованного movement-view. - Переиспользовать `resume_token` с другими фильтрами. +- Игнорировать `method_name`, `transfer_type` или running balances, хотя именно из-за них этот API часто удобнее сырой transaction-history. - Начинать здесь с вопросов про testnet, хотя этот API сегодня работает только в mainnet. ## Полезные связанные страницы From 0ef686fc45fb7fa75afe4d3dbc6e96238ae6f637 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sun, 19 Apr 2026 11:34:34 -0700 Subject: [PATCH 21/35] docs: expand near data examples around polling --- docs/neardata/examples.md | 105 +++++++++++++++-- .../current/neardata/examples.md | 109 ++++++++++++++++-- static/ru/guides/llms.txt | 2 +- static/ru/llms-full.txt | 107 +++++++++++++++-- static/ru/llms.txt | 2 +- static/ru/neardata/examples.md | 107 +++++++++++++++-- static/ru/neardata/examples/index.md | 107 +++++++++++++++-- 7 files changed, 482 insertions(+), 57 deletions(-) diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index 0ad5d4c..d502c74 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /neardata/examples title: NEAR Data Examples -description: Plain-language workflows for checking whether a contract was touched in the latest finalized block and extracting the exact identifiers worth following up. +description: Plain-language workflows for checking contract touches, comparing optimistic and final heads, and walking forward block by block. displayed_sidebar: nearDataApiSidebar page_actions: - markdown @@ -14,7 +14,7 @@ Start with one recent finalized block and ask for the smallest possible touch su ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID +TARGET_ACCOUNT_ID=intents.near FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -27,7 +27,7 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ height: .block.header.height, hash: .block.header.hash, direct_tx_count: ([.shards[].chunk.transactions[]? - | select((.transaction.receiver_id // .receiver_id) == $target)] | length), + | select(.transaction.receiver_id == $target)] | length), incoming_receipt_count: ([.shards[].chunk.receipts[]? | select(.receiver_id == $target)] | length), outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? @@ -36,7 +36,10 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ or (.execution_outcome.outcome.executor_id // "") == $target )] | length), state_change_count: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target)] | length) + | select((.change.account_id // "") == $target)] | length), + state_change_types: ([.shards[].state_changes[]? + | select((.change.account_id // "") == $target) + | .type] | unique | sort) } | . + { touched: ( (.direct_tx_count > 0) @@ -47,7 +50,9 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ }' ``` -This is the smallest useful NEAR Data summary for an app team: one finalized block, one yes-or-no answer, and a few counts before you widen. +This is the smallest useful NEAR Data summary for an app team: one finalized block, one yes-or-no answer, and a few counts before you widen. `intents.near` is pinned here so the first run is likely to return a real touched block before you swap in your own contract. + +NEAR blocks are sharded, so the filter walks `.shards[]` before it inspects transactions, receipts, outcomes, or state changes. `chunk.receipts` means work that landed in this block; `receipt_execution_outcomes` means work that executed in this block, even if it was scheduled earlier. ## Worked investigation @@ -62,7 +67,7 @@ Use this investigation when you want a concrete yes/no answer before you widen i

01last-block-final gives you one stable block height without guessing.

-

02block is the main read: it already contains the transactions, receipts, receipt execution outcomes, and state changes you need to answer “touched or not?”.

+

02block is the main read: it already contains the transactions, incoming receipts, receipt execution outcomes, and state changes you need to answer “touched or not?”.

03Only if the answer is “yes” do you widen: keep one exact tx hash or receipt id from the same cached block, then hand that identifier to [Transactions API](/tx) or [RPC Reference](/rpc).

@@ -76,6 +81,7 @@ Use this investigation when you want a concrete yes/no answer before you widen i - finalized height and hash - touched or not touched - counts for direct txs, incoming receipts, outcome hits, and state changes +- a compact `state_change_types` list - one sample tx hash or receipt id when present ### Final block to contract-touch answer shell walkthrough @@ -87,11 +93,11 @@ Use this when the target account is already known and you want one recent finali - Get the latest finalized block redirect target. - Fetch the full block document once. - Build one small touch summary for one `TARGET_ACCOUNT_ID`. -- Return a yes/no answer plus the smallest useful counts and sample identifiers. +- Return a yes/no answer plus the smallest useful counts, state-change types, and sample identifiers. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID +TARGET_ACCOUNT_ID=intents.near FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -109,8 +115,8 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' [ .shards[] | .chunk.transactions[]? - | select((.transaction.receiver_id // .receiver_id) == $target) - | (.transaction.hash // .hash) + | select(.transaction.receiver_id == $target) + | .transaction.hash ] ) as $txs | ( @@ -142,6 +148,11 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' | .type ] ) as $state_changes + | ( + $state_changes + | unique + | sort + ) as $state_change_types | { height: .block.header.height, hash: .block.header.hash, @@ -155,6 +166,7 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' incoming_receipt_count: ($receipts | length), outcome_hit_count: ($outcomes | length), state_change_count: ($state_changes | length), + state_change_types: $state_change_types, sample_direct_tx: ($txs[0] // null), sample_incoming_receipt: ($receipts[0] // null), sample_outcome_tx_hash: ($outcomes[0] // null) @@ -164,6 +176,8 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' If you need richer detail later, keep reusing `/tmp/neardata-block.json`. The point of this first pass is to answer “touched or not?” before you widen into longer arrays or deeper traces. +Common `state_change_types` include `account_update`, `access_key_update`, `data_update`, and the corresponding `*_deletion` variants. That is often enough to tell whether you are looking at storage writes, key churn, or broader account-level changes before you leave NEAR Data. + #### Optional follow-up: Which tx hash or receipt id should I inspect next? Keep the same cached block and summary, then lift one exact identifier for the next surface. @@ -198,12 +212,83 @@ If the identifier is a `tx_hash`, hand it to [Transactions API](/tx) or RPC `tx` This keeps the question as small as possible: first answer “was my contract touched?”, then widen only if one exact tx hash or receipt id justifies a deeper trace. NEAR Data is the discovery layer here, not just a block monitor. +### How far ahead is optimistic right now? + +Use this when you need to choose between low-latency reads and settled reads before you start polling. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +OPTIMISTIC_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +jq -n \ + --arg final_location "$FINAL_LOCATION" \ + --arg optimistic_location "$OPTIMISTIC_LOCATION" '{ + final_location: $final_location, + optimistic_location: $optimistic_location, + final_height: ($final_location | split("/") | last | tonumber), + optimistic_height: ($optimistic_location | split("/") | last | tonumber) + } | . + { + optimistic_minus_final: (.optimistic_height - .final_height) + }' +``` + +Use `last_block/optimistic` when the app values speed more than settled finality, for example reactive status views or early alerting. Use `last_block/final` when the answer feeds accounting, reconciliation, or any workflow that should not rewind. + +### How do I walk forward block by block? + +Use this when the job is “start at height N, fetch, process, increment, repeat.” For a deterministic bootstrap floor, read [`first-block`](/neardata/first-block) once before you begin. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +FINAL_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | awk -F/ '{print $4}')" +NEXT_HEIGHT=$((FINAL_HEIGHT + 1)) + +while true; do + HTTP_CODE="$( + curl -s -o /tmp/neardata-next-block.json -w '%{http_code}' \ + "$NEARDATA_BASE_URL/v0/block/$NEXT_HEIGHT" + )" + + if [ "$HTTP_CODE" = "200" ]; then + jq '{height: .block.header.height, hash: .block.header.hash}' \ + /tmp/neardata-next-block.json + NEXT_HEIGHT=$((NEXT_HEIGHT + 1)) + elif [ "$HTTP_CODE" = "404" ]; then + sleep 2 + else + printf 'Unexpected status: %s\n' "$HTTP_CODE" >&2 + break + fi +done +``` + +That is the canonical polling shape for finalized data: fetch by height, process one block, advance, and treat `404` as “not finalized yet, back off and try again.” If you need the same loop at optimistic speed, switch to `/v0/block_opt/` and accept optimistic semantics instead of final ones. + ## Common mistakes - Treating NEAR Data like a push stream instead of a polling or point-read API. - Starting with RPC before checking whether one finalized block already answers the contract-touch question. - Looking only for direct transactions and forgetting that contracts are often touched through receipts or state changes. +- Using optimistic data for settled accounting or reconciliation. - Assuming one hard-coded shard id should be checked before you inspect the block family itself. - Widening to Transactions API or RPC before extracting one exact tx hash or receipt id from NEAR Data. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index d4a3520..e6ff8ba 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /neardata/examples title: "Примеры NEAR Data" -description: "Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных идентификаторов для дальнейшего разбора." +description: "Пошаговые сценарии для проверки contract touch, сравнения optimistic и final head, а также прохода по блокам вперёд." displayed_sidebar: nearDataApiSidebar page_actions: - markdown @@ -14,7 +14,7 @@ page_actions: ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID +TARGET_ACCOUNT_ID=intents.near FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -27,7 +27,7 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ height: .block.header.height, hash: .block.header.hash, direct_tx_count: ([.shards[].chunk.transactions[]? - | select((.transaction.receiver_id // .receiver_id) == $target)] | length), + | select(.transaction.receiver_id == $target)] | length), incoming_receipt_count: ([.shards[].chunk.receipts[]? | select(.receiver_id == $target)] | length), outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? @@ -36,7 +36,10 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ or (.execution_outcome.outcome.executor_id // "") == $target )] | length), state_change_count: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target)] | length) + | select((.change.account_id // "") == $target)] | length), + state_change_types: ([.shards[].state_changes[]? + | select((.change.account_id // "") == $target) + | .type] | unique | sort) } | . + { touched: ( (.direct_tx_count > 0) @@ -47,7 +50,9 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ }' ``` -Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. +Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. Здесь закреплён `intents.near`, чтобы первый запуск с высокой вероятностью сразу вернул реальный touched-блок, а уже потом вы сможете подставить свой контракт. + +Блоки NEAR шардингованы, поэтому фильтр проходит по `.shards[]`, прежде чем смотреть транзакции, receipts, outcomes или изменения состояния. `chunk.receipts` означает работу, которая приземлилась в этом блоке; `receipt_execution_outcomes` означает работу, которая исполнилась в этом блоке, даже если была запланирована раньше. ## Готовое расследование @@ -58,11 +63,11 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \
Стратегия -

Сначала ответьте на вопрос о контрактном touch, а затем оставьте только один tx hash или receipt id для следующего шага.

+

Сначала ответьте на вопрос о contract touch, а затем оставьте только один tx hash или receipt id для следующего шага.

01last-block-final даёт одну стабильную высоту блока без угадывания.

-

02block — это главный read: он уже содержит транзакции, receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?»

+

02block — это главный read: он уже содержит транзакции, входящие receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?»

03Только если ответ «да», расширяйтесь дальше: сохраните один точный tx hash или receipt id из того же сохранённого блока, а затем передайте этот идентификатор в [Transactions API](/tx) или [RPC Reference](/rpc).

@@ -76,9 +81,10 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - финализированную высоту и хеш - ответ “затронут / не затронут” - счётчики прямых транзакций, входящих receipts, outcome-hit и state changes +- компактный список `state_change_types` - один sample tx hash или receipt id, когда он есть -### Shell-сценарий от финализированного блока к ответу по контракту +### Shell-сценарий от финализированного блока к ответу по contract touch Используйте этот сценарий, когда целевой аккаунт уже известен и нужен один свежий финализированный ответ, а не длинный polling-цикл. @@ -87,11 +93,11 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - Получаете redirect target для последнего финализированного блока. - Один раз загружаете полный документ блока. - Собираете один компактный ответ по одному `TARGET_ACCOUNT_ID`. -- Получаете ответ “да / нет” плюс минимально полезные счётчики и sample-идентификаторы. +- Получаете ответ “да / нет” плюс минимально полезные счётчики, типы изменений состояния и sample-идентификаторы. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID +TARGET_ACCOUNT_ID=intents.near FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -109,8 +115,8 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' [ .shards[] | .chunk.transactions[]? - | select((.transaction.receiver_id // .receiver_id) == $target) - | (.transaction.hash // .hash) + | select(.transaction.receiver_id == $target) + | .transaction.hash ] ) as $txs | ( @@ -142,6 +148,11 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' | .type ] ) as $state_changes + | ( + $state_changes + | unique + | sort + ) as $state_change_types | { height: .block.header.height, hash: .block.header.hash, @@ -155,6 +166,7 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' incoming_receipt_count: ($receipts | length), outcome_hit_count: ($outcomes | length), state_change_count: ($state_changes | length), + state_change_types: $state_change_types, sample_direct_tx: ($txs[0] // null), sample_incoming_receipt: ($receipts[0] // null), sample_outcome_tx_hash: ($outcomes[0] // null) @@ -164,6 +176,8 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' Если позже понадобятся более богатые списки, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. +Типичные `state_change_types` — это `account_update`, `access_key_update`, `data_update` и соответствующие варианты `*_deletion`. Этого часто достаточно, чтобы ещё внутри NEAR Data понять, смотрите ли вы на запись в storage, churn ключей или более широкое изменение аккаунта. + #### Необязательное продолжение: Какой tx hash или receipt id разбирать дальше? Используйте ту же сохранённую сводку и поднимите один точный идентификатор для следующей поверхности. @@ -198,12 +212,83 @@ printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один точный tx hash или receipt id уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. +### Насколько optimistic head опережает final прямо сейчас? + +Используйте это, когда нужно выбрать между low-latency read и settled read ещё до запуска polling. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +OPTIMISTIC_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +jq -n \ + --arg final_location "$FINAL_LOCATION" \ + --arg optimistic_location "$OPTIMISTIC_LOCATION" '{ + final_location: $final_location, + optimistic_location: $optimistic_location, + final_height: ($final_location | split("/") | last | tonumber), + optimistic_height: ($optimistic_location | split("/") | last | tonumber) + } | . + { + optimistic_minus_final: (.optimistic_height - .final_height) + }' +``` + +Используйте `last_block/optimistic`, когда приложению важнее скорость, чем settled finality, например для реактивных status-view или ранних алертов. Используйте `last_block/final`, когда ответ пойдёт в accounting, reconciliation или любой workflow, который не должен откатываться назад. + +### Как идти вперёд блок за блоком? + +Используйте этот шаблон, когда задача звучит как «начать с высоты N, получить блок, обработать его, увеличить высоту и повторить». Для детерминированной стартовой точки один раз прочитайте [`first-block`](/neardata/first-block), а затем идите вперёд. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +FINAL_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | awk -F/ '{print $4}')" +NEXT_HEIGHT=$((FINAL_HEIGHT + 1)) + +while true; do + HTTP_CODE="$( + curl -s -o /tmp/neardata-next-block.json -w '%{http_code}' \ + "$NEARDATA_BASE_URL/v0/block/$NEXT_HEIGHT" + )" + + if [ "$HTTP_CODE" = "200" ]; then + jq '{height: .block.header.height, hash: .block.header.hash}' \ + /tmp/neardata-next-block.json + NEXT_HEIGHT=$((NEXT_HEIGHT + 1)) + elif [ "$HTTP_CODE" = "404" ]; then + sleep 2 + else + printf 'Unexpected status: %s\n' "$HTTP_CODE" >&2 + break + fi +done +``` + +Это каноническая форма polling для финализированных данных: получить блок по высоте, обработать один блок, перейти к следующему и трактовать `404` как «ещё не финализирован, подождите и повторите». Если нужен тот же цикл на optimistic-скорости, переключитесь на `/v0/block_opt/` и примите optimistic semantics вместо final. + ## Частые ошибки - Воспринимать NEAR Data как push-стрим, а не как polling- или point-read API. - Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. - Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. +- Использовать optimistic-данные для settled accounting или reconciliation. - Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. - Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data один точный tx hash или receipt id. diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index b21e408..c92e161 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -31,7 +31,7 @@ - [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и проверки прямого стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных идентификаторов для дальнейшего разбора. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки contract touch, сравнения optimistic и final head, а также прохода по блокам вперёд. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. - [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для фильтрации переводов, чтения humanized amount и running balances, а также перехода к receipt- или transaction-контексту. - [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index cd4f891..08470e6 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -2077,7 +2077,7 @@ https://testnet.neardata.xyz ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID +TARGET_ACCOUNT_ID=intents.near FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -2090,7 +2090,7 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ height: .block.header.height, hash: .block.header.hash, direct_tx_count: ([.shards[].chunk.transactions[]? - | select((.transaction.receiver_id // .receiver_id) == $target)] | length), + | select(.transaction.receiver_id == $target)] | length), incoming_receipt_count: ([.shards[].chunk.receipts[]? | select(.receiver_id == $target)] | length), outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? @@ -2099,7 +2099,10 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ or (.execution_outcome.outcome.executor_id // "") == $target )] | length), state_change_count: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target)] | length) + | select((.change.account_id // "") == $target)] | length), + state_change_types: ([.shards[].state_changes[]? + | select((.change.account_id // "") == $target) + | .type] | unique | sort) } | . + { touched: ( (.direct_tx_count > 0) @@ -2110,7 +2113,9 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ }' ``` -Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. +Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. Здесь закреплён `intents.near`, чтобы первый запуск с высокой вероятностью сразу вернул реальный touched-блок, а уже потом вы сможете подставить свой контракт. + +Блоки NEAR шардингованы, поэтому фильтр проходит по `.shards[]`, прежде чем смотреть транзакции, receipts, outcomes или изменения состояния. `chunk.receipts` означает работу, которая приземлилась в этом блоке; `receipt_execution_outcomes` означает работу, которая исполнилась в этом блоке, даже если была запланирована раньше. ## Готовое расследование @@ -2119,10 +2124,10 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC. Стратегия - Сначала ответьте на вопрос о контрактном touch, а затем оставьте только один tx hash или receipt id для следующего шага. + Сначала ответьте на вопрос о contract touch, а затем оставьте только один tx hash или receipt id для следующего шага. 01last-block-final даёт одну стабильную высоту блока без угадывания. - 02block — это главный read: он уже содержит транзакции, receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» + 02block — это главный read: он уже содержит транзакции, входящие receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» 03Только если ответ «да», расширяйтесь дальше: сохраните один точный tx hash или receipt id из того же сохранённого блока, а затем передайте этот идентификатор в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). **Цель** @@ -2134,9 +2139,10 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - финализированную высоту и хеш - ответ “затронут / не затронут” - счётчики прямых транзакций, входящих receipts, outcome-hit и state changes +- компактный список `state_change_types` - один sample tx hash или receipt id, когда он есть -### Shell-сценарий от финализированного блока к ответу по контракту +### Shell-сценарий от финализированного блока к ответу по contract touch Используйте этот сценарий, когда целевой аккаунт уже известен и нужен один свежий финализированный ответ, а не длинный polling-цикл. @@ -2145,11 +2151,11 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - Получаете redirect target для последнего финализированного блока. - Один раз загружаете полный документ блока. - Собираете один компактный ответ по одному `TARGET_ACCOUNT_ID`. -- Получаете ответ “да / нет” плюс минимально полезные счётчики и sample-идентификаторы. +- Получаете ответ “да / нет” плюс минимально полезные счётчики, типы изменений состояния и sample-идентификаторы. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID +TARGET_ACCOUNT_ID=intents.near FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -2167,8 +2173,8 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' [ .shards[] | .chunk.transactions[]? - | select((.transaction.receiver_id // .receiver_id) == $target) - | (.transaction.hash // .hash) + | select(.transaction.receiver_id == $target) + | .transaction.hash ] ) as $txs | ( @@ -2200,6 +2206,11 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' | .type ] ) as $state_changes + | ( + $state_changes + | unique + | sort + ) as $state_change_types | { height: .block.header.height, hash: .block.header.hash, @@ -2213,6 +2224,7 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' incoming_receipt_count: ($receipts | length), outcome_hit_count: ($outcomes | length), state_change_count: ($state_changes | length), + state_change_types: $state_change_types, sample_direct_tx: ($txs[0] // null), sample_incoming_receipt: ($receipts[0] // null), sample_outcome_tx_hash: ($outcomes[0] // null) @@ -2222,6 +2234,8 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' Если позже понадобятся более богатые списки, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. +Типичные `state_change_types` — это `account_update`, `access_key_update`, `data_update` и соответствующие варианты `*_deletion`. Этого часто достаточно, чтобы ещё внутри NEAR Data понять, смотрите ли вы на запись в storage, churn ключей или более широкое изменение аккаунта. + #### Необязательное продолжение: Какой tx hash или receipt id разбирать дальше? Используйте ту же сохранённую сводку и поднимите один точный идентификатор для следующей поверхности. @@ -2256,11 +2270,82 @@ printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один точный tx hash или receipt id уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. +### Насколько optimistic head опережает final прямо сейчас? + +Используйте это, когда нужно выбрать между low-latency read и settled read ещё до запуска polling. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +OPTIMISTIC_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +jq -n \ + --arg final_location "$FINAL_LOCATION" \ + --arg optimistic_location "$OPTIMISTIC_LOCATION" '{ + final_location: $final_location, + optimistic_location: $optimistic_location, + final_height: ($final_location | split("/") | last | tonumber), + optimistic_height: ($optimistic_location | split("/") | last | tonumber) + } | . + { + optimistic_minus_final: (.optimistic_height - .final_height) + }' +``` + +Используйте `last_block/optimistic`, когда приложению важнее скорость, чем settled finality, например для реактивных status-view или ранних алертов. Используйте `last_block/final`, когда ответ пойдёт в accounting, reconciliation или любой workflow, который не должен откатываться назад. + +### Как идти вперёд блок за блоком? + +Используйте этот шаблон, когда задача звучит как «начать с высоты N, получить блок, обработать его, увеличить высоту и повторить». Для детерминированной стартовой точки один раз прочитайте [`first-block`](https://docs.fastnear.com/ru/neardata/first-block), а затем идите вперёд. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +FINAL_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | awk -F/ '{print $4}')" +NEXT_HEIGHT=$((FINAL_HEIGHT + 1)) + +while true; do + HTTP_CODE="$( + curl -s -o /tmp/neardata-next-block.json -w '%{http_code}' \ + "$NEARDATA_BASE_URL/v0/block/$NEXT_HEIGHT" + )" + + if [ "$HTTP_CODE" = "200" ]; then + jq '{height: .block.header.height, hash: .block.header.hash}' \ + /tmp/neardata-next-block.json + NEXT_HEIGHT=$((NEXT_HEIGHT + 1)) + elif [ "$HTTP_CODE" = "404" ]; then + sleep 2 + else + printf 'Unexpected status: %s\n' "$HTTP_CODE" >&2 + break + fi +done +``` + +Это каноническая форма polling для финализированных данных: получить блок по высоте, обработать один блок, перейти к следующему и трактовать `404` как «ещё не финализирован, подождите и повторите». Если нужен тот же цикл на optimistic-скорости, переключитесь на `/v0/block_opt/` и примите optimistic semantics вместо final. + ## Частые ошибки - Воспринимать NEAR Data как push-стрим, а не как polling- или point-read API. - Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. - Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. +- Использовать optimistic-данные для settled accounting или reconciliation. - Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. - Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data один точный tx hash или receipt id. diff --git a/static/ru/llms.txt b/static/ru/llms.txt index 40853ad..0a47bc8 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -34,7 +34,7 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и проверки прямого стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки, был ли контракт затронут в последнем финализированном блоке, и извлечения точных идентификаторов для дальнейшего разбора. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки contract touch, сравнения optimistic и final head, а также прохода по блокам вперёд. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. - [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для фильтрации переводов, чтения humanized amount и running balances, а также перехода к receipt- или transaction-контексту. - [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика. diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index c82c3aa..163c9b8 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -6,7 +6,7 @@ ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID +TARGET_ACCOUNT_ID=intents.near FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -19,7 +19,7 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ height: .block.header.height, hash: .block.header.hash, direct_tx_count: ([.shards[].chunk.transactions[]? - | select((.transaction.receiver_id // .receiver_id) == $target)] | length), + | select(.transaction.receiver_id == $target)] | length), incoming_receipt_count: ([.shards[].chunk.receipts[]? | select(.receiver_id == $target)] | length), outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? @@ -28,7 +28,10 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ or (.execution_outcome.outcome.executor_id // "") == $target )] | length), state_change_count: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target)] | length) + | select((.change.account_id // "") == $target)] | length), + state_change_types: ([.shards[].state_changes[]? + | select((.change.account_id // "") == $target) + | .type] | unique | sort) } | . + { touched: ( (.direct_tx_count > 0) @@ -39,7 +42,9 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ }' ``` -Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. +Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. Здесь закреплён `intents.near`, чтобы первый запуск с высокой вероятностью сразу вернул реальный touched-блок, а уже потом вы сможете подставить свой контракт. + +Блоки NEAR шардингованы, поэтому фильтр проходит по `.shards[]`, прежде чем смотреть транзакции, receipts, outcomes или изменения состояния. `chunk.receipts` означает работу, которая приземлилась в этом блоке; `receipt_execution_outcomes` означает работу, которая исполнилась в этом блоке, даже если была запланирована раньше. ## Готовое расследование @@ -48,10 +53,10 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC. Стратегия - Сначала ответьте на вопрос о контрактном touch, а затем оставьте только один tx hash или receipt id для следующего шага. + Сначала ответьте на вопрос о contract touch, а затем оставьте только один tx hash или receipt id для следующего шага. 01last-block-final даёт одну стабильную высоту блока без угадывания. - 02block — это главный read: он уже содержит транзакции, receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» + 02block — это главный read: он уже содержит транзакции, входящие receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» 03Только если ответ «да», расширяйтесь дальше: сохраните один точный tx hash или receipt id из того же сохранённого блока, а затем передайте этот идентификатор в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). **Цель** @@ -63,9 +68,10 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - финализированную высоту и хеш - ответ “затронут / не затронут” - счётчики прямых транзакций, входящих receipts, outcome-hit и state changes +- компактный список `state_change_types` - один sample tx hash или receipt id, когда он есть -### Shell-сценарий от финализированного блока к ответу по контракту +### Shell-сценарий от финализированного блока к ответу по contract touch Используйте этот сценарий, когда целевой аккаунт уже известен и нужен один свежий финализированный ответ, а не длинный polling-цикл. @@ -74,11 +80,11 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - Получаете redirect target для последнего финализированного блока. - Один раз загружаете полный документ блока. - Собираете один компактный ответ по одному `TARGET_ACCOUNT_ID`. -- Получаете ответ “да / нет” плюс минимально полезные счётчики и sample-идентификаторы. +- Получаете ответ “да / нет” плюс минимально полезные счётчики, типы изменений состояния и sample-идентификаторы. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID +TARGET_ACCOUNT_ID=intents.near FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -96,8 +102,8 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' [ .shards[] | .chunk.transactions[]? - | select((.transaction.receiver_id // .receiver_id) == $target) - | (.transaction.hash // .hash) + | select(.transaction.receiver_id == $target) + | .transaction.hash ] ) as $txs | ( @@ -129,6 +135,11 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' | .type ] ) as $state_changes + | ( + $state_changes + | unique + | sort + ) as $state_change_types | { height: .block.header.height, hash: .block.header.hash, @@ -142,6 +153,7 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' incoming_receipt_count: ($receipts | length), outcome_hit_count: ($outcomes | length), state_change_count: ($state_changes | length), + state_change_types: $state_change_types, sample_direct_tx: ($txs[0] // null), sample_incoming_receipt: ($receipts[0] // null), sample_outcome_tx_hash: ($outcomes[0] // null) @@ -151,6 +163,8 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' Если позже понадобятся более богатые списки, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. +Типичные `state_change_types` — это `account_update`, `access_key_update`, `data_update` и соответствующие варианты `*_deletion`. Этого часто достаточно, чтобы ещё внутри NEAR Data понять, смотрите ли вы на запись в storage, churn ключей или более широкое изменение аккаунта. + #### Необязательное продолжение: Какой tx hash или receipt id разбирать дальше? Используйте ту же сохранённую сводку и поднимите один точный идентификатор для следующей поверхности. @@ -185,11 +199,82 @@ printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один точный tx hash или receipt id уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. +### Насколько optimistic head опережает final прямо сейчас? + +Используйте это, когда нужно выбрать между low-latency read и settled read ещё до запуска polling. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +OPTIMISTIC_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +jq -n \ + --arg final_location "$FINAL_LOCATION" \ + --arg optimistic_location "$OPTIMISTIC_LOCATION" '{ + final_location: $final_location, + optimistic_location: $optimistic_location, + final_height: ($final_location | split("/") | last | tonumber), + optimistic_height: ($optimistic_location | split("/") | last | tonumber) + } | . + { + optimistic_minus_final: (.optimistic_height - .final_height) + }' +``` + +Используйте `last_block/optimistic`, когда приложению важнее скорость, чем settled finality, например для реактивных status-view или ранних алертов. Используйте `last_block/final`, когда ответ пойдёт в accounting, reconciliation или любой workflow, который не должен откатываться назад. + +### Как идти вперёд блок за блоком? + +Используйте этот шаблон, когда задача звучит как «начать с высоты N, получить блок, обработать его, увеличить высоту и повторить». Для детерминированной стартовой точки один раз прочитайте [`first-block`](https://docs.fastnear.com/ru/neardata/first-block), а затем идите вперёд. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +FINAL_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | awk -F/ '{print $4}')" +NEXT_HEIGHT=$((FINAL_HEIGHT + 1)) + +while true; do + HTTP_CODE="$( + curl -s -o /tmp/neardata-next-block.json -w '%{http_code}' \ + "$NEARDATA_BASE_URL/v0/block/$NEXT_HEIGHT" + )" + + if [ "$HTTP_CODE" = "200" ]; then + jq '{height: .block.header.height, hash: .block.header.hash}' \ + /tmp/neardata-next-block.json + NEXT_HEIGHT=$((NEXT_HEIGHT + 1)) + elif [ "$HTTP_CODE" = "404" ]; then + sleep 2 + else + printf 'Unexpected status: %s\n' "$HTTP_CODE" >&2 + break + fi +done +``` + +Это каноническая форма polling для финализированных данных: получить блок по высоте, обработать один блок, перейти к следующему и трактовать `404` как «ещё не финализирован, подождите и повторите». Если нужен тот же цикл на optimistic-скорости, переключитесь на `/v0/block_opt/` и примите optimistic semantics вместо final. + ## Частые ошибки - Воспринимать NEAR Data как push-стрим, а не как polling- или point-read API. - Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. - Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. +- Использовать optimistic-данные для settled accounting или reconciliation. - Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. - Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data один точный tx hash или receipt id. diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index c82c3aa..163c9b8 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -6,7 +6,7 @@ ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID +TARGET_ACCOUNT_ID=intents.near FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -19,7 +19,7 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ height: .block.header.height, hash: .block.header.hash, direct_tx_count: ([.shards[].chunk.transactions[]? - | select((.transaction.receiver_id // .receiver_id) == $target)] | length), + | select(.transaction.receiver_id == $target)] | length), incoming_receipt_count: ([.shards[].chunk.receipts[]? | select(.receiver_id == $target)] | length), outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? @@ -28,7 +28,10 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ or (.execution_outcome.outcome.executor_id // "") == $target )] | length), state_change_count: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target)] | length) + | select((.change.account_id // "") == $target)] | length), + state_change_types: ([.shards[].state_changes[]? + | select((.change.account_id // "") == $target) + | .type] | unique | sort) } | . + { touched: ( (.direct_tx_count > 0) @@ -39,7 +42,9 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ }' ``` -Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. +Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. Здесь закреплён `intents.near`, чтобы первый запуск с высокой вероятностью сразу вернул реальный touched-блок, а уже потом вы сможете подставить свой контракт. + +Блоки NEAR шардингованы, поэтому фильтр проходит по `.shards[]`, прежде чем смотреть транзакции, receipts, outcomes или изменения состояния. `chunk.receipts` означает работу, которая приземлилась в этом блоке; `receipt_execution_outcomes` означает работу, которая исполнилась в этом блоке, даже если была запланирована раньше. ## Готовое расследование @@ -48,10 +53,10 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC. Стратегия - Сначала ответьте на вопрос о контрактном touch, а затем оставьте только один tx hash или receipt id для следующего шага. + Сначала ответьте на вопрос о contract touch, а затем оставьте только один tx hash или receipt id для следующего шага. 01last-block-final даёт одну стабильную высоту блока без угадывания. - 02block — это главный read: он уже содержит транзакции, receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» + 02block — это главный read: он уже содержит транзакции, входящие receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» 03Только если ответ «да», расширяйтесь дальше: сохраните один точный tx hash или receipt id из того же сохранённого блока, а затем передайте этот идентификатор в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). **Цель** @@ -63,9 +68,10 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - финализированную высоту и хеш - ответ “затронут / не затронут” - счётчики прямых транзакций, входящих receipts, outcome-hit и state changes +- компактный список `state_change_types` - один sample tx hash или receipt id, когда он есть -### Shell-сценарий от финализированного блока к ответу по контракту +### Shell-сценарий от финализированного блока к ответу по contract touch Используйте этот сценарий, когда целевой аккаунт уже известен и нужен один свежий финализированный ответ, а не длинный polling-цикл. @@ -74,11 +80,11 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - Получаете redirect target для последнего финализированного блока. - Один раз загружаете полный документ блока. - Собираете один компактный ответ по одному `TARGET_ACCOUNT_ID`. -- Получаете ответ “да / нет” плюс минимально полезные счётчики и sample-идентификаторы. +- Получаете ответ “да / нет” плюс минимально полезные счётчики, типы изменений состояния и sample-идентификаторы. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=YOUR_CONTRACT_ID +TARGET_ACCOUNT_ID=intents.near FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -96,8 +102,8 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' [ .shards[] | .chunk.transactions[]? - | select((.transaction.receiver_id // .receiver_id) == $target) - | (.transaction.hash // .hash) + | select(.transaction.receiver_id == $target) + | .transaction.hash ] ) as $txs | ( @@ -129,6 +135,11 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' | .type ] ) as $state_changes + | ( + $state_changes + | unique + | sort + ) as $state_change_types | { height: .block.header.height, hash: .block.header.hash, @@ -142,6 +153,7 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' incoming_receipt_count: ($receipts | length), outcome_hit_count: ($outcomes | length), state_change_count: ($state_changes | length), + state_change_types: $state_change_types, sample_direct_tx: ($txs[0] // null), sample_incoming_receipt: ($receipts[0] // null), sample_outcome_tx_hash: ($outcomes[0] // null) @@ -151,6 +163,8 @@ jq --arg target "$TARGET_ACCOUNT_ID" ' Если позже понадобятся более богатые списки, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. +Типичные `state_change_types` — это `account_update`, `access_key_update`, `data_update` и соответствующие варианты `*_deletion`. Этого часто достаточно, чтобы ещё внутри NEAR Data понять, смотрите ли вы на запись в storage, churn ключей или более широкое изменение аккаунта. + #### Необязательное продолжение: Какой tx hash или receipt id разбирать дальше? Используйте ту же сохранённую сводку и поднимите один точный идентификатор для следующей поверхности. @@ -185,11 +199,82 @@ printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один точный tx hash или receipt id уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. +### Насколько optimistic head опережает final прямо сейчас? + +Используйте это, когда нужно выбрать между low-latency read и settled read ещё до запуска polling. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +OPTIMISTIC_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +jq -n \ + --arg final_location "$FINAL_LOCATION" \ + --arg optimistic_location "$OPTIMISTIC_LOCATION" '{ + final_location: $final_location, + optimistic_location: $optimistic_location, + final_height: ($final_location | split("/") | last | tonumber), + optimistic_height: ($optimistic_location | split("/") | last | tonumber) + } | . + { + optimistic_minus_final: (.optimistic_height - .final_height) + }' +``` + +Используйте `last_block/optimistic`, когда приложению важнее скорость, чем settled finality, например для реактивных status-view или ранних алертов. Используйте `last_block/final`, когда ответ пойдёт в accounting, reconciliation или любой workflow, который не должен откатываться назад. + +### Как идти вперёд блок за блоком? + +Используйте этот шаблон, когда задача звучит как «начать с высоты N, получить блок, обработать его, увеличить высоту и повторить». Для детерминированной стартовой точки один раз прочитайте [`first-block`](https://docs.fastnear.com/ru/neardata/first-block), а затем идите вперёд. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +FINAL_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' +)" + +FINAL_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | awk -F/ '{print $4}')" +NEXT_HEIGHT=$((FINAL_HEIGHT + 1)) + +while true; do + HTTP_CODE="$( + curl -s -o /tmp/neardata-next-block.json -w '%{http_code}' \ + "$NEARDATA_BASE_URL/v0/block/$NEXT_HEIGHT" + )" + + if [ "$HTTP_CODE" = "200" ]; then + jq '{height: .block.header.height, hash: .block.header.hash}' \ + /tmp/neardata-next-block.json + NEXT_HEIGHT=$((NEXT_HEIGHT + 1)) + elif [ "$HTTP_CODE" = "404" ]; then + sleep 2 + else + printf 'Unexpected status: %s\n' "$HTTP_CODE" >&2 + break + fi +done +``` + +Это каноническая форма polling для финализированных данных: получить блок по высоте, обработать один блок, перейти к следующему и трактовать `404` как «ещё не финализирован, подождите и повторите». Если нужен тот же цикл на optimistic-скорости, переключитесь на `/v0/block_opt/` и примите optimistic semantics вместо final. + ## Частые ошибки - Воспринимать NEAR Data как push-стрим, а не как polling- или point-read API. - Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. - Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. +- Использовать optimistic-данные для settled accounting или reconciliation. - Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. - Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data один точный tx hash или receipt id. From bd76124c53684af45fe7107769bab2e70864a1c4 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 18 Apr 2026 20:35:32 -0700 Subject: [PATCH 22/35] docs: add chunk tracing workflow --- docs/neardata/block-chunk.mdx | 5 +- docs/neardata/block-shard.mdx | 5 +- docs/rpc/examples.md | 1831 ++++- docs/rpc/protocol/chunk-by-block-shard.mdx | 3 + docs/rpc/protocol/chunk-by-hash.mdx | 3 + .../current/neardata/block-chunk.mdx | 5 +- .../current/neardata/block-shard.mdx | 5 +- .../current/rpc/examples.md | 1829 ++++- .../rpc/protocol/chunk-by-block-shard.mdx | 3 + .../current/rpc/protocol/chunk-by-hash.mdx | 3 + static/ru/llms-full.txt | 6762 +++++++++++------ static/ru/rpc/examples.md | 1764 ++++- static/ru/rpc/examples/index.md | 1764 ++++- 13 files changed, 10221 insertions(+), 3761 deletions(-) diff --git a/docs/neardata/block-chunk.mdx b/docs/neardata/block-chunk.mdx index 5aea143..7177de2 100644 --- a/docs/neardata/block-chunk.mdx +++ b/docs/neardata/block-chunk.mdx @@ -1,6 +1,6 @@ --- title: Block Chunk -description: Fetch one chunk from a finalized block. +description: Fetch the lightweight per-shard chunk view for a finalized block. slug: /neardata/block-chunk hide_table_of_contents: true --- @@ -9,5 +9,8 @@ import FastnearDirectOperation from '@site/src/components/FastnearDirectOperatio # Block Chunk +This is the lightweight per-shard view of a finalized block: the chunk header, the transactions included for that shard, and the incoming receipts it executed. + +Use this when you want shard-local execution without the fuller shard envelope. It is a good fit for chunk-aware tracing, indexing, or analytics that only need the chunk payload. If you also need state changes and the receipts produced by that shard's execution, use [Block Shard](/neardata/block-shard). For a worked receipt-to-chunk tracing flow, see [RPC Examples](/rpc/examples). diff --git a/docs/neardata/block-shard.mdx b/docs/neardata/block-shard.mdx index 282c34a..8eb476a 100644 --- a/docs/neardata/block-shard.mdx +++ b/docs/neardata/block-shard.mdx @@ -1,6 +1,6 @@ --- title: Block Shard -description: Fetch one shard from a finalized block. +description: Fetch the fuller per-shard envelope for a finalized block. slug: /neardata/block-shard hide_table_of_contents: true --- @@ -9,5 +9,8 @@ import FastnearDirectOperation from '@site/src/components/FastnearDirectOperatio # Block Shard +Use this when chunk data alone is not enough and you need the fuller shard envelope for a finalized block. + +`Block Shard` goes one step wider than [Block Chunk](/neardata/block-chunk): you still get the shard's chunk payload, but you also see shard state changes and the receipts that execution produced. That makes it the better surface for debugging, indexers, and analytics when execution outcomes matter, not just included transactions and incoming receipts. For a worked tracing flow that starts from a transaction and ends on a destination chunk, see [RPC Examples](/rpc/examples). diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index 9110e8d..0117379 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /rpc/examples title: RPC Examples -description: Plain-language workflows for using FastNear RPC docs for transaction submission, access-key checks, FT preflight, raw-state reads, and Rainbow Bridge discovery. +description: Plain-language workflows for using FastNear RPC docs for exact state checks, block inspection, contract views, and transaction submission. displayed_sidebar: rpcSidebar page_actions: - markdown @@ -10,65 +10,98 @@ page_actions: # RPC Examples -Use this page when you want one exact RPC answer fast. Start with one read, then move to transactions or raw state only when the simpler check stops being enough. +Use this page when you already know the answer lives in RPC and you want the shortest path to it. The goal is not to memorize every method. It is to start with the right RPC read or write, stop as soon as the response answers the question, and only switch to a higher-level API when that would save time. -## Quick start +## Transaction Submission and Tracking -If you just landed here, start with one exact account read. +Start here when the real question is not just “how do I send this?” but “which RPC endpoint should I use, and how do I track the transaction all the way to done?” -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=near +### Submit a transaction, then track it from hash to final execution -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - finality: "final", - account_id: $account_id - } - }')" \ - | jq '.result | { - amount, - locked, - code_hash, - storage_usage - }' -``` +Use this when the user story is simple: “I have a signed transaction. Which endpoint do I call first, and what should I poll after I get the hash?” Not every tx question wants the same RPC method. The practical pattern is to submit fast, then track deliberately. -This is the smallest reliable RPC example on the page: one request, one exact answer, no receipt tree. +This walkthrough is intentionally pinned and historical. It uses one real mainnet transaction that wrote a NEAR Social follow edge: -## Transaction Submission and Tracking +- transaction hash: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- signer: `mike.near` +- receiver: `social.near` +- included block height: `79574923` +- receipt execution block for the SocialDB write: `79574924` + +Because this transaction is already old and finalized, you cannot literally replay its true pending window. That is fine. The point here is to teach the right submission and tracking pattern, then inspect one pinned transaction with the same tools. + +
+
+ Strategy +

Submit fast, poll the simpler status path first, and only drop into the receipt tree when the headline status stops being enough.

+
+
+

01RPC broadcast_tx_async is the low-latency submission surface when your client will track separately.

+

02RPC tx is the default polling surface for included, optimistic, and final guarantees.

+

03RPC EXPERIMENTAL_tx_status is the deeper follow-up when you need the receipt tree, not the default loop.

+
+
-### Two-part pattern: submit a transaction, or track a known tx hash to final execution +**What you're deciding** -Default pattern: +- which submission endpoint to reach for first +- what to poll after you have a tx hash +- how `wait_until` thresholds relate to included, optimistic, and final guarantees +- when to stop using `tx` and switch to `EXPERIMENTAL_tx_status` -- `broadcast_tx_async` to submit -- `tx` with `wait_until: "FINAL"` to track -- `EXPERIMENTAL_tx_status` only if the next question is about receipts +```mermaid +flowchart LR + S["Sign transaction"] --> A["broadcast_tx_async
returns tx hash"] + A --> T["tx polling
INCLUDED_FINAL -> FINAL"] + T --> F["Transaction fully done"] + T -. "only when needed" .-> E["EXPERIMENTAL_tx_status
receipt tree + outcomes"] + F -. "optional readable story" .-> X["POST /v0/transactions"] +``` -This walkthrough is intentionally split in two: +| Method | Use it when | What comes back | Position here | +| --- | --- | --- | --- | +| [`broadcast_tx_async`](/rpc/transaction/broadcast-tx-async) | your client wants to own tracking after submission | just the tx hash | **default submit path** | +| [`send_tx`](/rpc/transaction/send-tx) | you want the node to wait to a chosen threshold for you | tx result up to `wait_until` | blocking alternative | +| [`broadcast_tx_commit`](/rpc/transaction/broadcast-tx-commit) | older code or quick one-call confirmation is the point | execution result with commit-style waiting | legacy convenience | +| [`tx`](/rpc/transaction/tx-status) | you already have the tx hash and want to know how far it got | status plus outcomes at the threshold you asked for | **default tracking path** | +| [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | you need receipt-tree detail or a richer async story | full receipt tree and detailed outcomes | deep follow-up only | -- submit a fresh signed transaction and keep the returned hash -- track one known historical tx hash with reproducible output +**Status and wait map** -The tracking half uses one pinned historical transaction, so the status lookups use the archival host: +`wait_until` values are waiting thresholds, not a permanent lifecycle enum you should treat as the user's one true transaction status. The word `pending` is still useful in human conversation, but here it means “the transaction has been submitted and is not yet included.” -- transaction hash: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- signer: `mike.near` -- receiver: `social.near` -- `https://archival-rpc.mainnet.fastnear.com` +| Phase or threshold | What it means in practice | Best RPC surface | +| --- | --- | --- | +| pre-inclusion / pending | the client has submitted the tx, but it is not yet anchored in a block | your own submission state plus retry/backoff logic | +| `INCLUDED` | the tx is in a block, but that block may not be final yet | `tx` | +| `INCLUDED_FINAL` | the inclusion block is final | `tx` | +| `EXECUTED_OPTIMISTIC` | execution has happened with optimistic finality | `tx` or `send_tx` | +| `FINAL` | all relevant execution has completed and finalized | `tx` by default, `EXPERIMENTAL_tx_status` when you need more detail | -1. Submit a fresh signed transaction and keep the returned hash. +The key practical distinction is simple: + +- use `broadcast_tx_async` when the tx hash is enough to keep going +- use `tx` as the normal tracking loop +- use `EXPERIMENTAL_tx_status` when the next question is about the receipt tree rather than the headline status + +**What you're doing** + +- Show what a live submission would look like with `broadcast_tx_async`. +- Poll the pinned tx with `tx` at two thresholds: `INCLUDED_FINAL` and `FINAL`. +- Only after that inspect the same tx with `EXPERIMENTAL_tx_status`. +- Optionally pivot to Transactions API if the human-readable story is what matters next. ```bash RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near +RECEIVER_ID=social.near +``` + +1. If this were a live client flow, submit with `broadcast_tx_async` and keep the returned hash. +```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data '{ @@ -80,16 +113,40 @@ curl -s "$RPC_URL" \ | jq . ``` -That first step is only about the submission shape. The returned hash is what you would track next for your own live transaction. +In a real app, that response is the moment you stop waiting on submission and start tracking by tx hash. -2. Track one known tx hash until you have the simplest final answer. +2. Poll with `tx` at the first threshold that answers the user question. ```bash -RPC_URL=https://archival-rpc.mainnet.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "INCLUDED_FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' ``` +What to notice: + +- on a live tx, this threshold is useful when you care that the tx is safely included before you claim success to the user +- on this historical tx, it returns immediately because the transaction is long past inclusion +- `transaction_outcome.outcome.status` still tells you that the original action handed off into receipt execution + +3. Poll again with `FINAL` when you want the completed transaction story rather than just safe inclusion. + ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ @@ -107,12 +164,18 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - transaction_status: .result.status, + status: .result.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -3. Only switch to `EXPERIMENTAL_tx_status` when that known tx now needs receipt-tree detail. +What to notice: + +- for a historical tx, this call also returns immediately +- in a real tracking loop, this is the threshold that answers “is the transaction actually done?” +- for many apps, this is where you stop and move on + +4. Only switch to `EXPERIMENTAL_tx_status` when you need the richer receipt tree. ```bash curl -s "$RPC_URL" \ @@ -131,54 +194,87 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, + status: .result.status, transaction_handoff: .result.transaction_outcome.outcome.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -If you want the node to wait for you instead of tracking separately, use [`send_tx`](/rpc/transaction/send-tx). The default pattern on this page is still: submit with `broadcast_tx_async`, then track a hash with `tx`. +This is where you go when “did it finish?” turns into “show me the receipt tree and the full async execution story.” + +5. Optional: pivot to Transactions API only when you want the readable story surface. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transaction_handoff: .transactions[0].transaction_outcome.outcome.status + }' +``` + +That last step is intentionally optional. The RPC truth is already enough for submission and tracking. This is only the human-readable story surface when the next user question becomes “what actually happened?” instead of “how far did the tx get?” + +**Recommended pattern** + +- Use `broadcast_tx_async` plus `tx` polling when you want the best client control and the fastest feedback. +- Use `send_tx` when you really do want one blocking call to wait up to a chosen threshold. +- Use `EXPERIMENTAL_tx_status` when the normal polling loop stops being enough and the receipt tree becomes the real question. ## Account and Key Mechanics -Start here when the question is about exact permissions, exact key state, or whether one key can call one contract right now. +Start here when the question is about exact permissions, exact key state, or one contract-level write flow. -### Does this access key let me call this contract right now? +### Audit and remove old Near Social function-call keys -Use this when you already have an account, one public key, and one target contract, and you want a plain yes-or-no answer before you try to sign anything. +Use this when you know an account has accumulated older `social.near` function-call keys and you want to inspect them, choose one intentionally, and remove it with raw RPC submission.
Strategy -

Filter the account’s keys first, inspect the exact key second, and classify the permission last.

+

Use exact key reads to narrow the target first, then sign exactly one delete.

-

01RPC view_access_key_list narrows the account down to keys that could matter for the target contract.

-

02RPC view_access_key gives the exact permission object for the one public key you might actually use.

-

03jq turns that permission object into full_access, function_call_match, receiver_mismatch, or method_not_allowed.

+

01RPC view_access_key_list finds only the function-call keys scoped to social.near.

+

02RPC view_access_key double-checks the one key you plan to remove, and POST /v0/account is only for optional account-level context.

+

03RPC send_tx submits the DeleteKey, then RPC view_access_key_list closes the loop.

**What you're doing** -- List the account’s access keys and narrow them to the contract you care about. -- Inspect the exact key you might sign with. -- Decide whether it can call this receiver and method without leaving RPC. +- Use RPC itself to list every access key on the account. +- Narrow that list to function-call keys scoped to `social.near`. +- Inspect one candidate key exactly before you delete it. +- Build and sign a `DeleteKey` transaction with a full-access key, then submit it through RPC and verify the key is gone. -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -TARGET_CONTRACT_ID=crossword.puzzle.near -TARGET_METHOD_NAME=new_puzzle -TARGET_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_WANT_TO_CHECK' +Two caveats matter up front: -# Sample live values observed on April 19, 2026: -# ACCOUNT_ID=mike.near -# TARGET_CONTRACT_ID=crossword.puzzle.near -# TARGET_METHOD_NAME=new_puzzle -# TARGET_PUBLIC_KEY='ed25519:otwaB1X88ocpmUdC1B5XaifucfDLmLKaonb26KqTj96' +- The deleting key must be a full-access key. A function-call key cannot sign a `DeleteKey` action. +- This flow is about exact key state and cleanup. The optional Transactions API step below gives account-level context, not authoritative per-key “last used” forensics. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export SOCIAL_RECEIVER_ID=social.near +export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' +export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' +export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' ``` -1. List the account’s keys and narrow them to the target contract. +1. List all access keys on the account, then narrow to `social.near` function-call keys. ```bash curl -s "$RPC_URL" \ @@ -193,28 +289,252 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/access-key-list.json >/dev/null - -jq --arg target_contract_id "$TARGET_CONTRACT_ID" '{ - candidate_keys: [ - .result.keys[] - | select( - .access_key.permission == "FullAccess" - or ( - (.access_key.permission | type) == "object" - and .access_key.permission.FunctionCall.receiver_id == $target_contract_id - ) - ) - | { - public_key, - nonce: .access_key.nonce, - permission: .access_key.permission + | tee /tmp/fastnear-access-keys.json >/dev/null + +jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + nonce: .access_key.nonce, + receiver_id: .access_key.permission.FunctionCall.receiver_id, + method_names: .access_key.permission.FunctionCall.method_names, + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } +' /tmp/fastnear-access-keys.json +``` + +Pick one `public_key` from that filtered list and set `DELETE_PUBLIC_KEY` to it. + +2. Inspect the specific candidate key one more time before deleting it. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$DELETE_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" } - ] -}' /tmp/access-key-list.json + }')" \ + | jq '{nonce: .result.nonce, permission: .result.permission}' +``` + +3. Optional: pull recent function-call activity for the account to decide whether you want to investigate more before cleanup. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + is_function_call: true, + limit: 10 + }')" \ + | jq '{ + account_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_success + } + ] + }' +``` + +That query helps answer “has this account still been doing function-call work recently?”, but it does not prove that a specific access key was the one used. + +4. Sign a `DeleteKey` transaction for `DELETE_PUBLIC_KEY` with a full-access key. + +Run this in a directory where `near-api-js@5` is installed. The command reads the environment variables above, fetches the latest nonce for `FULL_ACCESS_PUBLIC_KEY`, fetches a fresh final block hash, signs a `DeleteKey` action, and stores the resulting `signed_tx_base64` in `SIGNED_TX_BASE64`. + +```bash +SIGNED_TX_BASE64="$( + node --input-type=module <<'EOF' +import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; + +const { + ACCOUNT_ID, + NETWORK_ID = 'mainnet', + RPC_URL = 'https://rpc.mainnet.fastnear.com', + DELETE_PUBLIC_KEY, + FULL_ACCESS_PUBLIC_KEY, + FULL_ACCESS_PRIVATE_KEY, +} = process.env; + +for (const name of [ + 'ACCOUNT_ID', + 'DELETE_PUBLIC_KEY', + 'FULL_ACCESS_PUBLIC_KEY', + 'FULL_ACCESS_PRIVATE_KEY', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); +const derivedPublicKey = keyPair.getPublicKey().toString(); + +if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { + throw new Error( + `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: ACCOUNT_ID, + public_key: FULL_ACCESS_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const transaction = transactions.createTransaction( + ACCOUNT_ID, + utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), + ACCOUNT_ID, + BigInt(accessKey.nonce) + 1n, + [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +)" +``` + +5. Submit the signed transaction through raw RPC and wait for `FINAL`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Re-run the access-key list and verify that the deleted key is gone. + +```bash +if curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' + .result.keys[] + | select(.public_key == $public_key) + ' >/dev/null; then + echo "Key is still present: $DELETE_PUBLIC_KEY" +else + echo "Key deleted: $DELETE_PUBLIC_KEY" +fi +``` + +**Why this next step?** + +Re-running `view_access_key_list` closes the loop on the same RPC method you used for discovery. If the delete succeeded there, you do not need an indexed API to prove the cleanup. + +### Which transaction added this `social.near` function-call key, and who authorized it? + +Use this when you can already see a live access key on the account, but you want to trace it back to the `AddKey` transaction that created it and identify which public key actually authorized that change. + +
+
+ Strategy +

Start from the live key, then walk backward only as far as you need.

+
+
+

01RPC view_access_key gives the current stored nonce, which is the best historical clue you have.

+

02POST /v0/account turns that nonce into a tight candidate window instead of a whole-account search.

+

03POST /v0/transactions tells you whether the key was added directly or through delegated authorization, and POST /v0/receipt is only for the exact AddKey execution block.

+
+
+ +**What you're doing** + +- Read the exact key first with RPC and keep its current nonce as the clue. +- Convert that nonce into a tight block-height window for the likely `AddKey` receipt. +- Search account history only inside that window instead of scanning the whole account. +- Hydrate the candidate transaction and distinguish three different keys: + - the key that got added + - the top-level signer public key + - the delegated authorizing public key, if the change was wrapped in a `Delegate` action + +Three nonce details matter up front: + +- New access keys start with a nonce derived from block height at roughly `block_height * 1_000_000`, so dividing the current nonce by `1_000_000` gives a useful search window. +- The `AddKey` action payload often shows `access_key.nonce: 0`. That is not the stored nonce you later see from `view_access_key`. +- If the key has been used heavily since creation, widen the search window a bit more. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' + +# Sample live key observed on April 18, 2026: +# export ACCOUNT_ID=mike.near +# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' ``` -2. Inspect the exact key you want to evaluate. +1. Read the exact key first, then turn its current nonce into a search window. ```bash curl -s "$RPC_URL" \ @@ -232,72 +552,175 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/exact-access-key.json >/dev/null + | tee /tmp/key-origin-view.json >/dev/null -jq '{nonce: .result.nonce, permission: .result.permission}' /tmp/exact-access-key.json +CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" +ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" +SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" +SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" + +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_public_key "$TARGET_PUBLIC_KEY" \ + --argjson current_nonce "$CURRENT_NONCE" \ + --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ + --argjson search_from "$SEARCH_FROM" \ + --argjson search_to "$SEARCH_TO" \ + --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ + account_id: $account_id, + target_public_key: $target_public_key, + current_nonce: $current_nonce, + estimated_receipt_block: $estimated_receipt_block, + search_from_tx_block_height: $search_from, + search_to_tx_block_height: $search_to, + permission: ($permission | fromjson) + }' ``` -3. Turn that exact permission object into a yes-or-no answer for this contract and method. +If you use the sample key above, the estimated receipt block should land at `112057392`. + +2. Search account history only inside that block neighborhood. ```bash -jq -n \ - --slurpfile key /tmp/exact-access-key.json \ - --arg target_contract_id "$TARGET_CONTRACT_ID" \ - --arg target_method_name "$TARGET_METHOD_NAME" ' - ($key[0].result.permission) as $permission - | if $permission == "FullAccess" then - { - can_call_now: true, - reason: "full_access" - } - elif $permission.FunctionCall.receiver_id != $target_contract_id then - { - can_call_now: false, - reason: "receiver_mismatch", - receiver_id: $permission.FunctionCall.receiver_id - } - elif ( - ($permission.FunctionCall.method_names | length) == 0 - or ($permission.FunctionCall.method_names | index($target_method_name)) - ) then - { - can_call_now: true, - reason: ( - if ($permission.FunctionCall.method_names | length) == 0 - then "function_call_any_method" - else "function_call_method_match" - end - ), - allowance: ($permission.FunctionCall.allowance // "unlimited") - } - else - { - can_call_now: false, - reason: "method_not_allowed", - allowed_methods: $permission.FunctionCall.method_names +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_tx_block_height "$SEARCH_FROM" \ + --argjson to_tx_block_height "$SEARCH_TO" '{ + account_id: $account_id, + is_real_signer: true, + from_tx_block_height: $from_tx_block_height, + to_tx_block_height: $to_tx_block_height, + desc: false, + limit: 50 + }')" \ + | tee /tmp/key-origin-candidates.json >/dev/null + +jq '{ + txs_count, + candidate_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_signer, + is_real_signer, + is_predecessor, + is_receiver } - end' + ] +}' /tmp/key-origin-candidates.json +``` + +With the sample `mike.near` key above, this window returns one candidate transaction: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` at outer tx block `112057390`. + +3. Hydrate those candidates and keep only the transaction that actually added your target key. + +```bash +TX_HASHES_JSON="$( + jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json +)" + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ + | tee /tmp/key-origin-transactions.json >/dev/null + +jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "direct", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $tx.transaction.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }), + ($tx.transaction.actions[]? + | .Delegate? + | .delegate_action as $delegate + | $delegate.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "delegated", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $delegate.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }) + ) + | { + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + tx_block_hash: $tx.execution_outcome.block_hash, + receiver_id: $tx.transaction.receiver_id + } + . +' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json +``` + +If `authorization_mode` is `direct`, the top-level signer public key and the authorizing public key are the same. If `authorization_mode` is `delegated`, the key that actually authorized the `AddKey` lives inside `Delegate.delegate_action.public_key`. + +With the sample `mike.near` key above, the match is delegated: + +- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` +- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` +- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` +- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` + +4. Optional: if you need the exact `AddKey` receipt block too, pivot one more time by receipt ID. + +```bash +ADD_KEY_RECEIPT_ID="$( + jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | .receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) + | .receipt.receipt_id + ' /tmp/key-origin-transactions.json | head -n 1 +)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receipt_block_height: .receipt.block_height, + tx_block_height: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }' ``` -For the sample `mike.near` key above on April 19, 2026, the answer is `can_call_now: true`: the key is a function-call key for `crossword.puzzle.near`, and `method_names: ["new_puzzle"]` explicitly allows the method we asked about. +For the sample key above, the exact `AddKey` receipt is `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` in receipt block `112057392`, while the outer transaction landed earlier in block `112057390`. **Why this next step?** -`view_access_key_list` is the fastest contract-level filter. `view_access_key` is the exact authority check for the one public key you may actually use. If the answer is `false`, you need a different key or a different permission setup, not a deeper history trace. +Start with exact current key state because it gives you the nonce clue. A tight `/v0/account` window turns that clue into a small candidate set. `/v0/transactions` tells you whether the key was added directly or through delegated authorization. `/v0/receipt` is the optional last step when you need the exact `AddKey` receipt block, not just the outer transaction. -### Does this FT receiver need storage registration before I transfer? +### Register FT storage if needed, then transfer tokens -Use this when the user story is “I am about to send fungible tokens, and I want a plain yes-or-no answer about whether the receiver needs `storage_deposit` first.” +Use this when the user story is “send fungible tokens safely, but first prove whether the receiver is already registered for storage on that FT contract.”
Strategy -

Read the receiver storage state first, then stop as soon as you know whether `ft_transfer` can proceed.

+

Read storage first, then spend the minimum write calls needed to make the transfer stick.

01RPC call_function storage_balance_of tells you whether the receiver is already registered.

02RPC call_function storage_balance_bounds only matters if you need the exact minimum deposit before writing.

-

03jq turns those two reads into one answer: “transfer can proceed” or “send `storage_deposit` first”.

+

03RPC send_tx submits storage_deposit and ft_transfer, then RPC call_function ft_balance_of proves the result.

@@ -310,19 +733,24 @@ Use this when the user story is “I am about to send fungible tokens, and I wan - [FT storage and transfer](https://docs.near.org/integrations/fungible-tokens) - [Pre-deployed FT contract](https://docs.near.org/tutorials/fts/predeployed-contract) -This walkthrough uses the safe public contract `ft.predeployed.examples.testnet`. The point here is the read-only decision: do you need a `storage_deposit` first, or can your transfer path proceed already? +This walkthrough uses the safe public contract `ft.predeployed.examples.testnet`. Before you start, make sure the sender already holds some `gtNEAR` there. If not, mint a small balance first with the pre-deployed contract guide above and then come back to this flow. **What you're doing** - Use exact RPC view calls to check whether the receiver already has FT storage on the contract. -- Fetch the exact minimum storage requirement from the same contract. -- Stop once you know whether `ft_transfer` can proceed or whether `storage_deposit` has to come first. +- If needed, fetch the minimum storage requirement. +- Sign and submit `storage_deposit`, then `ft_transfer`. +- Verify the receiver balance with the same contract’s own view method. ```bash export NETWORK_ID=testnet export RPC_URL=https://rpc.testnet.fastnear.com export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet +export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' +export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' +export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' ``` 1. Check whether the receiver is already registered on the FT contract. @@ -358,7 +786,7 @@ jq '{ }' /tmp/ft-storage-balance.json ``` -2. Fetch the minimum storage deposit from the same contract. +2. If the receiver is not registered yet, fetch the minimum storage deposit. ```bash MIN_STORAGE_YOCTO="$( @@ -383,105 +811,335 @@ MIN_STORAGE_YOCTO="$( printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" ``` -3. Turn those two reads into one transfer-readiness answer. - -```bash -jq -n \ - --slurpfile balance /tmp/ft-storage-balance.json \ - --slurpfile bounds /tmp/ft-storage-bounds.json \ - --arg receiver_account_id "$RECEIVER_ACCOUNT_ID" ' - ( - $balance[0].result.result - | if length == 0 then null else (implode | fromjson) end - ) as $storage - | ( - $bounds[0].result.result - | implode - | fromjson - ) as $bounds - | { - receiver_account_id: $receiver_account_id, - receiver_registered: ($storage != null), - current_storage: $storage, - minimum_storage_deposit_yocto: $bounds.min, - next_step: ( - if $storage != null - then "receiver already registered; ft_transfer can proceed" - else "send storage_deposit before ft_transfer" - end - ) - }' -``` - -**Why this next step?** - -This is the clean RPC question in this workflow: “is the receiver already registered, and if not, what minimum deposit will the contract require?” The signed write path depends on your wallet, CLI, or backend integration, so it does not belong in the smallest core RPC example. - -## Contract Reads and Raw State - -Start here when the question is “does this contract method tell me enough?” versus “should I read the storage directly?” - -### Read a counter straight from contract state, then confirm it with the view method - -Use this when you already know the exact storage key family and want the smallest possible contrast between raw state and the contract’s public read API. +3. Define one reusable signer for contract function calls. -This uses the live public testnet contract `counter.near-examples.testnet`: - -- `view_state` reads the raw `STATE` entry directly -- `call_function get_num` asks the contract for the same current number +Run this in a directory where `near-api-js@5` is installed. The function below reads the exported shell variables above and turns each function call into a signed payload for raw RPC submission. ```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export CONTRACT_ID=counter.near-examples.testnet -export STATE_PREFIX_BASE64=U1RBVEU= +sign_function_call() { + METHOD_NAME="$1" \ + ARGS_JSON="$2" \ + DEPOSIT_YOCTO="$3" \ + GAS_TGAS="$4" \ + node --input-type=module <<'EOF' +import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; + +const { + NETWORK_ID = 'testnet', + RPC_URL = 'https://rpc.testnet.fastnear.com', + TOKEN_CONTRACT_ID, + SENDER_ACCOUNT_ID, + SENDER_PUBLIC_KEY, + SENDER_PRIVATE_KEY, + METHOD_NAME, + ARGS_JSON, + DEPOSIT_YOCTO = '0', + GAS_TGAS = '100', +} = process.env; + +for (const name of [ + 'TOKEN_CONTRACT_ID', + 'SENDER_ACCOUNT_ID', + 'SENDER_PUBLIC_KEY', + 'SENDER_PRIVATE_KEY', + 'METHOD_NAME', + 'ARGS_JSON', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); +const signer = await InMemorySigner.fromKeyPair( + NETWORK_ID, + SENDER_ACCOUNT_ID, + keyPair +); + +const derivedPublicKey = keyPair.getPublicKey().toString(); +if (derivedPublicKey !== SENDER_PUBLIC_KEY) { + throw new Error( + `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: SENDER_ACCOUNT_ID, + public_key: SENDER_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const action = transactions.functionCall( + METHOD_NAME, + Buffer.from(ARGS_JSON), + BigInt(GAS_TGAS) * 10n ** 12n, + BigInt(DEPOSIT_YOCTO) +); + +const transaction = transactions.createTransaction( + SENDER_ACCOUNT_ID, + utils.PublicKey.fromString(SENDER_PUBLIC_KEY), + TOKEN_CONTRACT_ID, + BigInt(accessKey.nonce) + 1n, + [action], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + SENDER_ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +} ``` -1. Read the raw `STATE` entry. +4. If needed, register the receiver for storage first. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$CONTRACT_ID" \ - --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ +if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then + SIGNED_TX_BASE64="$( + sign_function_call \ + storage_deposit \ + "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id, + registration_only: true + }')" \ + "$MIN_STORAGE_YOCTO" \ + 100 + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ jsonrpc: "2.0", id: "fastnear", - method: "query", + method: "send_tx", params: { - request_type: "view_state", - account_id: $account_id, - prefix_base64: $prefix_base64, - finality: "final" + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" } }')" \ - | tee /tmp/counter-view-state.json >/dev/null + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash + }' +fi +``` -jq '{ - key: (.result.values[0].key | @base64d), - value_base64: .result.values[0].value +5. Transfer the FT after storage is ready. + +```bash +SIGNED_TX_BASE64="$( + sign_function_call \ + ft_transfer \ + "$(jq -nc \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ + receiver_id: $receiver_id, + amount: $amount, + memo: "FastNear RPC example" + }')" \ + 1 \ + 100 +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Verify the receiver’s FT balance with the contract’s own view method. + +```bash +RECEIVER_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_balance_of", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + receiver_balance: (.result.result | implode | fromjson) + }' +``` + +**Why this next step?** + +This is a good RPC example because every step stays close to the contract itself: first check storage state, then send the minimum required change calls, then verify the post-transfer balance directly on the contract. + +## Contract Reads and Raw State + +Start here when the question is “does this contract method tell me enough?” versus “should I read the storage directly?” + +### How do I read a contract's raw storage directly? + +Use this when the public view method is missing, or when you need to verify the storage layout itself instead of trusting the method alone. + +This walkthrough uses the live public testnet contract `counter.near-examples.testnet`. The number can change over time. That is fine. The point is that you read the raw storage first, then confirm that the contract's public view method agrees: + +- `view_state` reads the raw `STATE` entry directly from contract storage +- `call_function get_num` asks the contract for the same current number through its public view API + +
+
+ Strategy +

Read the storage the hard way first, then let the contract confirm the same answer through its view method.

+
+
+

01RPC view_state reads the raw STATE entry without running contract code.

+

02Decode the base64 value into bytes, then interpret those bytes with the contract’s known Borsh layout.

+

03RPC call_function get_num is the friendly cross-check that the raw-state read and the view method still agree.

+
+
+ +The mental model matters more than the counter itself: + +- `view_state` is a direct storage read from the trie +- `call_function` executes a read-only method on the contract +- both can answer the same question, but they do different work to get there + +```mermaid +flowchart LR + S["RPC view_state
prefix STATE"] --> R["Raw STATE bytes"] + R --> D["Decode base64 + Borsh"] + D --> N["Signed counter value"] + C["RPC call_function get_num"] --> J["JSON method result"] + N --> X["Compare"] + J --> X + X --> A["Same current counter value"] +``` + +**What you're doing** + +- Read the raw `STATE` key from contract storage. +- Decode the returned bytes into the current signed counter value. +- Call `get_num` through the view method and confirm that the method answer matches the raw-state decode. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export CONTRACT_ID=counter.near-examples.testnet +export STATE_PREFIX_BASE64=U1RBVEU= +``` + +1. Read the raw contract state first. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$CONTRACT_ID" \ + --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_state", + account_id: $account_id, + prefix_base64: $prefix_base64, + finality: "final" + } + }')" \ + | tee /tmp/counter-view-state.json >/dev/null + +jq '{ + block_height: .result.block_height, + key_base64: .result.values[0].key, + value_base64: .result.values[0].value }' /tmp/counter-view-state.json + +jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json ``` -That should show `key: "STATE"`. This is the case where `view_state` makes sense: you already know the exact key family. +That last command should print `STATE`. This is the key family you already knew ahead of time, so `view_state` can go straight to the raw storage entry without asking the contract to execute any method. -2. Decode the raw bytes. +2. Decode the returned value bytes into the signed counter. ```bash RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" -python3 - "$RAW_VALUE_BASE64" <<'PY' +python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . import base64 +import json import sys raw = base64.b64decode(sys.argv[1]) -print(int.from_bytes(raw, "little", signed=True)) + +print(json.dumps({ + "value_base64": sys.argv[1], + "bytes": list(raw), + "hex": raw.hex(), + "signed_i8": int.from_bytes(raw, "little", signed=True), + "unsigned_u8": int.from_bytes(raw, "little", signed=False), +})) PY ``` -For this contract, `STATE` is a one-byte signed counter, so decoding is trivial. On other contracts the layout may be much less friendly: near-sdk collections and Borsh-serialized structs often derive storage keys from prefixes and internal key schemes, so `view_state` only stays practical when you already know the exact layout you want. The rule stays the same: bytes first, schema second. +For this specific contract, one byte is enough because the Rust counter stores `val: i8` inside the contract state. That is why a raw value like `CQ==` decodes to one byte `0x09`, which reads as the signed integer `9`. + +One small signed-value note is worth keeping in your head: if the counter were negative, the same one-byte payload would still decode correctly as a signed two's-complement `i8`. For example, `/w==` is the single byte `0xff`, which means `-1` as `signed_i8`, not `255`. -3. Ask the contract the friendly way and compare. +The reusable recipe is small: + +- `view_state` gives you base64-encoded raw bytes +- you decode those bytes with the contract’s known storage layout +- for larger contracts, that layout may be more complex, but the idea is the same: bytes first, schema second + +3. Now ask the contract the friendly way and compare. ```bash curl -s "$RPC_URL" \ @@ -506,7 +1164,7 @@ jq '{ }' /tmp/counter-call-function.json ``` -4. Compare both answers. +4. Compare both answers directly. ```bash RAW_STATE_NUMBER="$( @@ -532,202 +1190,719 @@ jq -n \ }' ``` +If `agrees_now` is `true`, you have proved the point of the example: + +- `view_state` answered the question by reading storage directly +- `call_function get_num` answered the same question by running the contract’s public read method + **Why this next step?** -Use `view_state` when you already know the exact storage key family and want raw bytes. Use `call_function` when you want the contract’s public read API. If the next question becomes historical instead of “what is it right now?”, widen into [KV FastData API](/fastdata/kv). +Use `view_state` when the real question is about exact storage, missing view methods, or verifying a known key family. Use `call_function` when you want the contract’s public read API. If the next question becomes historical instead of “what is it right now?”, that is the moment to widen into [KV FastData API](/fastdata/kv). + +## Chunk and Shard Tracing + +Start here when the question is no longer just “did this transaction succeed?” but “which shard-local chunk actually executed each leg of the work?” -### Which Rainbow Bridge ERC-20 tokens exist on NEAR, and how much of one token is out there? +### Trace a generated transfer receipt from one shard chunk to another -Use this when you want to discover Rainbow Bridge ERC-20 contracts and inspect one token's live supply on NEAR. Rainbow Bridge deploys one NEAR contract per bridged ERC-20 token, and `factory.bridge.near` lists them. +Use this when the contract call itself was only the start of the story. In this pinned mainnet case, the signed transaction starts on shard `11`, then a generated `Transfer` receipt finishes on shard `6`. That cross-shard handoff is exactly why chunk-level inspection matters. + +This walkthrough is pinned to: + +- transaction `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` from `7419369993.tg` to `game.hot.tg` calling `l2_claim` +- origin chunk `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` on shard `11` in block `194623170` +- first receipt chunk `FJWpAYzVXbZwqJUbGXELTnnBBkdvc6W8vWkwuUA3Zwz9` on shard `11` in block `194623171` +- generated `Transfer` receipt `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` +- destination chunk `EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU` on shard `6` in block `194623172`
Strategy -

One factory read lists the token contracts. Two small view calls on one token tell you what it is and how much is on NEAR right now.

+

Recover the receipt chain first, inspect the generated receipt directly, then map each leg back to the shard chunk that actually carried it.

-

01RPC call_function get_tokens_accounts on factory.bridge.near returns the deployed bridged token contracts.

-

02RPC call_function ft_metadata on one bridged token contract returns its name, symbol, and decimals.

-

03RPC call_function ft_total_supply on the same contract returns the current raw supply on NEAR.

+

01RPC EXPERIMENTAL_tx_status quickly shows the receipt graph and which later blocks the work moved into.

+

02RPC EXPERIMENTAL_receipt lets you inspect the generated receipt payload directly instead of inferring it from logs alone.

+

03RPC chunk by block-and-shard or by chunk hash proves which shard-local execution unit actually carried each step.

+The two experimental methods here are a good fit for advanced tracing: `EXPERIMENTAL_tx_status` finds the receipt graph quickly, and `EXPERIMENTAL_receipt` shows the generated receipt body before you map it back to chunks. + +```mermaid +flowchart LR + A["Tx 8xrc...
block 194623170
chunk Bfyd...
shard 11"] --> B["Receipt AFC2...
block 194623171
chunk FJWp...
shard 11
ft_mint logs"] + B --> C["Generated receipt TtRn...
Transfer 1800930478788300000000 yoctoNEAR"] + C --> D["Chunk EPau...
block 194623172
shard 6
receipt executes"] +``` + **What you're doing** -- Ask the bridge factory for every bridged token contract it has created. -- Pick one bridged token contract and read its metadata. -- Read the same contract's total supply and convert it to human units using `decimals`. +- Recover the receipt chain from the transaction first. +- Inspect the generated `Transfer` receipt body directly. +- Use chunk coordinates when you know the block and shard. +- Use chunk hash when another tool already handed you the exact destination chunk. ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com -export FACTORY_ID=factory.bridge.near -export TOKENS_FILE=/tmp/rainbow-bridge-tokens.json +export TX_HASH=8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP +export SIGNER_ACCOUNT_ID=7419369993.tg +export ORIGIN_BLOCK_HEIGHT=194623170 +export ORIGIN_SHARD_ID=11 +export RECEIPT_BLOCK_HEIGHT=194623171 +export RECEIPT_SHARD_ID=11 +export GENERATED_RECEIPT_ID=TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4 +export DESTINATION_CHUNK_HASH=EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU ``` -1. List the bridged token contracts. +1. Start with `EXPERIMENTAL_tx_status` so you can see the receipt graph before you think about chunks. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$FACTORY_ID" '{ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: [$tx_hash, $signer_account_id] + }')" \ + | tee /tmp/chunk-trace-status.json >/dev/null + +jq '{ + final_execution_status: .result.final_execution_status, + transaction_handoff: .result.transaction_outcome.outcome.status, + receipts: ( + .result.receipts_outcome + | map({ + receipt_id: .id, + executor_id: .outcome.executor_id, + block_hash, + status: .outcome.status + }) + ) +}' /tmp/chunk-trace-status.json +``` + +What to notice: + +- the signed transaction hands off into receipt `AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y` +- later in the same receipt graph, `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` executes for `7419369993.tg` +- the high-level tx status is already enough to tell you that the real work continued after the original signed transaction + +2. Inspect the generated receipt directly so you can prove that the follow-up object is a real `Transfer` receipt. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$GENERATED_RECEIPT_ID" '{ jsonrpc: "2.0", id: "fastnear", - method: "query", + method: "EXPERIMENTAL_receipt", params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_tokens_accounts", - args_base64: "e30=", - finality: "final" + receipt_id: $receipt_id } }')" \ - | tee "$TOKENS_FILE" >/dev/null + | tee /tmp/chunk-trace-receipt.json >/dev/null -jq -r '.result.result | implode | fromjson | .[]' "$TOKENS_FILE" +jq '{ + predecessor_id: .result.predecessor_id, + receiver_id: .result.receiver_id, + signer_id: .result.receipt.Action.signer_id, + signer_public_key: .result.receipt.Action.signer_public_key, + actions: .result.receipt.Action.actions +}' /tmp/chunk-trace-receipt.json ``` -Each line is one bridged FT contract on NEAR in the form `.factory.bridge.near`. For example, bridged ERC-20 USDT on Ethereum address `0xdAC17F958D2ee523a2206206994597C13D831ec7` appears as `dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near`. +This is the point where the shard story becomes concrete: the contract flow generated a `Transfer` action receipt from `system` to `7419369993.tg` with deposit `1800930478788300000000`. -2. Read metadata for one token contract. +3. Use chunk by block and shard to locate the original signed transaction on shard `11`. ```bash -export TOKEN_ID=dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --argjson block_id "$ORIGIN_BLOCK_HEIGHT" \ + --argjson shard_id "$ORIGIN_SHARD_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "chunk", + params: { + block_id: $block_id, + shard_id: $shard_id + } + }')" \ + | jq --arg tx_hash "$TX_HASH" '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created + }, + matching_transaction: ( + .result.transactions[] + | select(.hash == $tx_hash) + | { + hash, + signer_id, + receiver_id + } + ) + }' +``` + +This is the cleanest use of `chunk` by block and shard: you already know the coordinates, and you want the exact shard-local execution unit that carried the original signed transaction. + +4. Stay on the same route for the next block and watch the first receipt execute on the same shard. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --argjson block_id "$RECEIPT_BLOCK_HEIGHT" \ + --argjson shard_id "$RECEIPT_SHARD_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "chunk", + params: { + block_id: $block_id, + shard_id: $shard_id + } + }')" \ + | jq '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created, + tx_root: .result.header.tx_root, + gas_used: .result.header.gas_used + }, + tx_count: (.result.transactions | length), + receipt_count: (.result.receipts | length), + matching_receipt: ( + .result.receipts[] + | select(.receipt_id == "AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y") + | { + receipt_id, + predecessor_id, + receiver_id + } + ) + }' +``` + +This is the chunk moment that makes the whole concept natural: + +- the chunk has `tx_root = 11111111111111111111111111111111` +- `tx_count` is `0` +- but the shard still burned gas and executed receipt `AFC2...` + +In other words, this shard did real work in that block even though no new signed transaction appeared directly inside the chunk. +5. Switch to chunk by hash once another tool has already given you the exact destination chunk. + +```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + --data "$(jq -nc --arg chunk_id "$DESTINATION_CHUNK_HASH" '{ jsonrpc: "2.0", id: "fastnear", - method: "query", + method: "chunk", params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_metadata", - args_base64: "e30=", - finality: "final" + chunk_id: $chunk_id } }')" \ - | tee /tmp/rainbow-bridge-token-metadata.json >/dev/null + | jq --arg receipt_id "$GENERATED_RECEIPT_ID" '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created, + tx_root: .result.header.tx_root, + gas_used: .result.header.gas_used + }, + tx_count: (.result.transactions | length), + receipt_count: (.result.receipts | length), + matching_receipt: ( + .result.receipts[] + | select(.receipt_id == $receipt_id) + | { + receipt_id, + predecessor_id, + receiver_id + } + ) + }' +``` + +This confirms the cross-shard hop: + +- the generated `Transfer` receipt executes in chunk `EPau...` +- that chunk lives on shard `6`, not shard `11` +- the signed transaction started on one shard, but the later receipt finished on another + +**Why this next step?** + +Use [`Chunk by Block and Shard`](/rpc/protocol/chunk-by-block-shard) when you know the block and shard coordinates and want to ask “what did this shard execute in this block?” Use [`Chunk by Hash`](/rpc/protocol/chunk-by-hash) when another tool has already handed you the exact chunk hash. Use [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) and [`EXPERIMENTAL_receipt`](/rpc/transaction/experimental-receipt) when the real question is receipt-driven tracing. If you also need state changes and produced receipts, widen to [Block Shard](/neardata/block-shard). + +## NEAR Social and BOS Exact Reads -jq '.result.result | implode | fromjson | {name, symbol, decimals}' /tmp/rainbow-bridge-token-metadata.json +These stay on exact SocialDB reads and on-chain readiness checks until the question turns historical. + +### Can this account still publish to NEAR Social right now? + +Use this when the user story is “I’m about to publish a profile change, widget update, or graph write under `mike.near`, and I want a plain go/no-go answer before I open wallet signing.” + +
+
+ Strategy +

Ask social.near for the two things that matter before you sign anything.

+
+
+

01RPC view_account makes sure the signer account exists and can actually submit a transaction.

+

02RPC call_function get_account_storage tells you whether the target account has room left on social.near.

+

03RPC call_function is_write_permission_granted only comes into play when a different signer is trying to write on that account’s behalf.

+
+
+ +This is the same question real NEAR Social clients have to answer before they try a write: + +- does the target account already have storage on `social.near`? +- if it does, is there still room left in that storage? +- if a different signer is trying to write under that account, has write permission already been granted? + +**Official references** + +- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) + +**What you're doing** + +- Check that the signer account itself exists and can pay gas. +- Ask `social.near` how much storage the target account has left. +- If the signer differs from the target account, ask `social.near` whether that delegated write is already allowed. +- Turn those exact RPC answers into one simple “ready now” or “fix this first” summary. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mike.near +export SIGNER_ACCOUNT_ID=mike.near ``` -3. Read the current total supply on NEAR and convert it to human units. +1. Check the signer account itself first. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { - request_type: "call_function", + request_type: "view_account", account_id: $account_id, - method_name: "ft_total_supply", - args_base64: "e30=", finality: "final" } }')" \ - | tee /tmp/rainbow-bridge-token-supply.json >/dev/null + | tee /tmp/social-publish-signer.json >/dev/null + +jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + signer_account_id: $signer_account_id, + amount: .result.amount, + locked: .result.locked, + storage_usage: .result.storage_usage +}' /tmp/social-publish-signer.json +``` -RAW_SUPPLY="$( - jq -r '.result.result | implode | fromjson' /tmp/rainbow-bridge-token-supply.json -)" +If this query fails, you do not have a signer account to work with. If it succeeds, you know the signer exists and can at least pay gas. + +2. Ask `social.near` how much storage is already available for the account you want to write under. -DECIMALS="$( - jq -r '.result.result | implode | fromjson | .decimals' /tmp/rainbow-bridge-token-metadata.json +```bash +SOCIAL_STORAGE_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' )" -HUMAN_SUPPLY="$( - python3 - "$RAW_SUPPLY" "$DECIMALS" <<'PY' -from decimal import Decimal -import sys +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_account_storage", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-account-storage.json >/dev/null -raw = Decimal(sys.argv[1]) -decimals = int(sys.argv[2]) -human = raw / (Decimal(10) ** decimals) -print(human) -PY +jq --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + storage: (.result.result | implode | fromjson), + storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) +}' /tmp/social-account-storage.json +``` + +If `available_bytes` is greater than zero, storage is not the blocker. If this method returns `null` or `available_bytes` is zero, the account needs a `storage_deposit` top-up before a new write can land. + +3. If the signer is different from the target account, check delegated write permission too. + +```bash +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + jq -n --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + signer_matches_target: true, + permission_granted: true, + reason: "owner write" + }' +else + WRITE_PERMISSION_ARGS_BASE64="$( + jq -nc \ + --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ + --arg key "$ACCOUNT_ID" '{ + predecessor_id: $predecessor_id, + key: $key + }' | base64 | tr -d '\n' + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + signer_matches_target: false, + permission_granted: (.result.result | implode | fromjson) + }' +fi +``` + +4. Turn the storage and permission checks into one readable answer. + +```bash +AVAILABLE_BYTES="$( + jq -r ' + .result.result + | if length == 0 then "0" + else (implode | fromjson | .available_bytes // 0 | tostring) + end + ' /tmp/social-account-storage.json )" +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + PERMISSION_GRANTED=true +else + PERMISSION_GRANTED="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq -r '.result.result | implode | fromjson' + )" +fi + jq -n \ - --arg token_id "$TOKEN_ID" \ - --arg raw_supply "$RAW_SUPPLY" \ - --argjson decimals "$DECIMALS" \ - --arg human_supply "$HUMAN_SUPPLY" '{ - token_id: $token_id, - raw_supply: $raw_supply, - decimals: $decimals, - human_supply: $human_supply + --arg account_id "$ACCOUNT_ID" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ + --argjson available_bytes "$AVAILABLE_BYTES" \ + --argjson permission_granted "$PERMISSION_GRANTED" '{ + account_id: $account_id, + signer_account_id: $signer_account_id, + storage_ready: ($available_bytes > 0), + permission_ready: $permission_granted, + ready_to_publish_now: (($available_bytes > 0) and $permission_granted) }' ``` -The `ft_total_supply` result is in the token's smallest units. Use the `decimals` from `ft_metadata` to convert it into a human-readable supply. +If that final object says `ready_to_publish_now: true`, RPC has already answered the question. If it says `false`, you know whether the blocker is storage, delegated permission, or both. + +**Why this next step?** + +This keeps the whole question on exact on-chain reads. `social.near` itself answers whether the target account has room left and whether a delegated signer is already allowed to write. That is a better NEAR Social readiness check than guessing from wallet state alone. + +### What does `mob.near/widget/Profile` actually contain right now? + +Use this when the question is simple: “show me the live source for `mob.near/widget/Profile`, tell me when that widget key was last written, and keep me on exact RPC reads.” + +
+
+ Strategy +

Stay on exact SocialDB reads, and only widen into history if the question turns forensic.

+
+
+

01RPC call_function keys shows the widget catalog and the last-write blocks under mob.near/widget/*.

+

02RPC call_function get reads the exact source for widget/Profile.

+

03If the next question becomes “which transaction wrote this?”, hand off to the widget proof recipe in /tx/examples.

+
+
+ +**Official references** + +- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) + +**What you're doing** + +- Ask `social.near` for the widget catalog under `mob.near`. +- Keep the block heights so you know when each widget key last changed. +- Confirm that `Profile` is really there, then read its exact source through the same contract. +- If the next question becomes “which transaction wrote this widget?”, switch to the NEAR Social proof recipes in [Transactions Examples](/tx/examples). -#### Optional extension: print the first few bridged tokens with metadata and supply +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mob.near +export WIDGET_NAME=Profile +``` -Use this when you want a quick sample inventory without leaving RPC. +1. List the widget catalog and keep the last-write block heights. ```bash -export TOKEN_SAMPLE_COUNT=5 +WIDGET_KEYS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} + }' | base64 | tr -d '\n' +)" -python3 <<'PY' -import json -import os -from decimal import Decimal - -TOKENS_FILE = os.environ["TOKENS_FILE"] -LIMIT = int(os.environ.get("TOKEN_SAMPLE_COUNT", "5")) -RPC_URL = os.environ["RPC_URL"] - -def decode_result(result): - return json.loads("".join(chr(b) for b in result)) - -with open(TOKENS_FILE) as fh: - token_ids = decode_result(json.load(fh)["result"]["result"])[:LIMIT] - -def rpc_call(account_id, method_name): - payload = { - "jsonrpc": "2.0", - "id": "fastnear", - "method": "query", - "params": { - "request_type": "call_function", - "account_id": account_id, - "method_name": method_name, - "args_base64": "e30=", - "finality": "final", - }, - } - import subprocess - raw = subprocess.check_output([ - "curl", "-s", RPC_URL, - "-H", "content-type: application/json", - "--data", json.dumps(payload), - ], text=True) - return decode_result(json.loads(raw)["result"]["result"]) - -print(f"{'token_id':<56} {'symbol':<12} {'decimals':>8} {'raw_supply':>24} {'human_supply':>24} name") -for token_id in token_ids: - metadata = rpc_call(token_id, "ft_metadata") - raw_supply = rpc_call(token_id, "ft_total_supply") - human_supply = Decimal(raw_supply) / (Decimal(10) ** metadata["decimals"]) - print( - f"{token_id:<56} " - f"{metadata['symbol']:<12} " - f"{metadata['decimals']:>8} " - f"{raw_supply:>24} " - f"{str(human_supply):>24} " - f"{metadata['name']}" - ) -PY +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "keys", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-keys.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].widget + | to_entries + | sort_by(.value * -1) + | map({ + widget_name: .key, + last_write_block: .value + }) + | .[0:20] +' /tmp/social-widget-keys.json ``` +2. Confirm that `Profile` is really in the catalog, then print the exact source stored in SocialDB. + +```bash +WIDGET_GET_ARGS_BASE64="$( + jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-source.json >/dev/null + +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:25] + | join("\n") + ' /tmp/social-widget-source.json +``` + +3. Pull the last-write block for the same widget so you keep one useful historical anchor. + +```bash +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + ' /tmp/social-widget-keys.json \ + | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +``` + +At the time of writing, the live last-write block for `mob.near/widget/Profile` was `86494825`. Keep that block if you later want to prove which transaction wrote this version. + **Why this next step?** -Stay on RPC while the question is “what bridged token contracts exist and how much of one token is out there?” The factory is the source of truth for the bridged token set, and each token contract answers its own metadata and supply through standard NEP-141 view methods. If the next question becomes “who holds this token?”, move to [V1 FT Top Holders](/api/v1/ft-top) instead of trying to walk holders through RPC. +Sometimes the right RPC answer is just: here is the widget, here is the live source, and here is the block height to keep if provenance matters later. + +## Common jobs + +### Check exact account or access-key state + +**Start here** + +- [View Account](/rpc/account/view-account) for exact account fields. +- [View Access Key](/rpc/account/view-access-key) or [View Access Key List](/rpc/account/view-access-key-list) for key inspection. + +**Next page if needed** + +- [FastNear API full account view](/api/v1/account-full) if you want a readable holdings summary after checking the exact RPC state. +- [Transactions API account history](/tx/account) if the next question is "what has this account been doing?" + +**Stop when** + +- The RPC fields already answer the state or permission question. + +**Switch when** + +- The user wants balances, NFTs, staking, or another readable account summary. +- The user really wants recent activity history rather than current state. + +### Trace shard-local execution through chunks + +**Start here** + +- Start with the chunk-tracing example above when the real question is “which chunk or shard actually executed this receipt?” +- [Chunk by Block and Shard](/rpc/protocol/chunk-by-block-shard) when you already know the block and shard coordinates. +- [Chunk by Hash](/rpc/protocol/chunk-by-hash) when another tool already handed you the exact chunk hash. + +**Next page if needed** + +- [Experimental Receipt](/rpc/transaction/experimental-receipt) when you need the generated receipt body itself. +- [Block Shard](/neardata/block-shard) when chunk data alone is not enough and you need state changes or produced receipts too. +- [Transactions Examples](/tx/examples) when the question turns into a broader async or callback investigation. + +**Stop when** + +- You can name which chunk and shard carried the work that mattered. + +**Switch when** + +- The user really wants a readable transaction story instead of shard-local execution details. Move to [Transactions API](/tx). + +### Check one exact block or protocol snapshot + +**Start here** + +- [Block by ID](/rpc/block/block-by-id) or [Block by Height](/rpc/block/block-by-height) when you already know which block you care about. +- [Latest Block](/rpc/protocol/latest-block) when the question is “what is the current head right now?” +- [Status](/rpc/protocol/status), [Health](/rpc/protocol/health), or [Network Info](/rpc/protocol/network-info) when the real question is about node or network condition, not transaction history. + +**Next page if needed** + +- [Block Effects](/rpc/block/block-effects) if the block payload tells you what block you are looking at but not what changed in it. +- [Transactions API block history](/tx/block) or [Transactions API block range](/tx/blocks) if the question becomes “what actually happened around this block?” rather than “what does this block payload say?” + +**Stop when** + +- One exact block or protocol response already answers the question directly. + +**Switch when** + +- The user wants to watch fresh blocks arrive rather than inspect one exact snapshot. Move to [NEAR Data API](/neardata). +- The user needs a readable story across many transactions, not just one block payload. Move to [Transactions API](/tx). + +### What does this contract return right now? + +**Start here** + +- Start with the counter example above when the real decision is “should I use `call_function` or `view_state`?” or “can I read this storage directly instead of calling a method?” +- [Call Function](/rpc/contract/call-function) when you already know the view method you want and just need the exact return value. +- [View State](/rpc/contract/view-state) when the real question is about raw contract storage or key prefixes, not a method result. +- [View Code](/rpc/contract/view-code) when the real question is “is there code here at all?” or “which code hash is deployed?” + +**Next page if needed** + +- [FastNear API](/api) if the raw contract answer is technically correct but the user actually wanted a readable holdings or account summary. +- [KV FastData API](/fastdata/kv) if the next question becomes “what did this storage key look like over time?” instead of “what is it right now?” + +**Stop when** + +- The view call, storage read, or code hash already answers the contract question exactly. + +**Switch when** + +- The user wants indexed history or a simpler summary instead of raw contract output. +- The user stops asking “what does it return right now?” and starts asking “what changed over time?” + +### Send and confirm a transaction + +**Start here** + +- Start with the worked example above when the real question is which submission endpoint to use and how to track the transaction through completion. +- [Send Transaction](/rpc/transaction/send-tx) when you want RPC submission with explicit waiting semantics. +- [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) or [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit) when those exact submission modes are the point. +- [Transaction Status](/rpc/transaction/tx-status) to confirm the final result. + +**Next page if needed** + +- [Transactions by Hash](/tx/transactions) for a readable history record after submission. +- [Receipt Lookup](/tx/receipt) when you need to investigate downstream execution or callback flow. +- [Transactions Examples](/tx/examples) when the next question is “one batched action failed, did the earlier actions roll back too?” + +**Stop when** + +- You have the submission result and final status you needed. + +**Switch when** + +- The next question is about receipts, affected accounts, or execution history in a human-friendly order. +- You need a fuller investigation workflow instead of one status check. ## Common mistakes diff --git a/docs/rpc/protocol/chunk-by-block-shard.mdx b/docs/rpc/protocol/chunk-by-block-shard.mdx index 69e4fe1..34f1767 100644 --- a/docs/rpc/protocol/chunk-by-block-shard.mdx +++ b/docs/rpc/protocol/chunk-by-block-shard.mdx @@ -12,5 +12,8 @@ import FastnearDirectOperation from '@site/src/components/FastnearDirectOperatio `chunk` request type +Use this when you know the block and shard coordinates and want to inspect exactly what that shard executed in that block. + +This is the most natural RPC route for cross-shard debugging because it answers the practical question “what did shard X execute in block Y?” The response gives the chunk header plus the transactions and receipts in that shard-local execution unit. If you already have the exact chunk hash, use [Chunk by Hash](/rpc/protocol/chunk-by-hash). If you want the fuller per-shard envelope with state changes and produced receipts, use [Block Shard](/neardata/block-shard). For a worked tracing example, see [RPC Examples](/rpc/examples). diff --git a/docs/rpc/protocol/chunk-by-hash.mdx b/docs/rpc/protocol/chunk-by-hash.mdx index 7a55e5f..0edee84 100644 --- a/docs/rpc/protocol/chunk-by-hash.mdx +++ b/docs/rpc/protocol/chunk-by-hash.mdx @@ -12,5 +12,8 @@ import FastnearDirectOperation from '@site/src/components/FastnearDirectOperatio `chunk` request type +Use this when another tool has already given you an exact chunk hash and you want the canonical protocol chunk: the header, the transactions included there, and the incoming receipts that shard executed in that block. + +On NEAR, a signed transaction can hand off into receipts that keep running on later chunks or different shards. This route is the right fit once the chunk hash is already known from a block summary, a tracing workflow, or a proof/debugging flow. If you know the block and shard coordinates instead, use [Chunk by Block and Shard](/rpc/protocol/chunk-by-block-shard). For a worked receipt-to-chunk tracing example, see [RPC Examples](/rpc/examples). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/block-chunk.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/block-chunk.mdx index 5987549..9adf91f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/block-chunk.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/block-chunk.mdx @@ -1,6 +1,6 @@ --- title: "Чанк блока" -description: "Получите один чанк из финализированного блока." +description: "Получите облегчённое представление чанка одного шарда для финализированного блока." slug: /neardata/block-chunk hide_table_of_contents: true --- @@ -9,5 +9,8 @@ import FastnearDirectOperation from '@site/src/components/FastnearDirectOperatio # Чанк блока +Это облегчённое представление одного шарда для финализированного блока: заголовок чанка, включённые транзакции для этого шарда и входящие квитанции, которые он исполнил. + +Используйте этот маршрут, когда нужен взгляд на исполнение одного шарда без более широкого представления всего шарда. Он хорошо подходит для трассировки, индексации и аналитики, когда нужно только само содержимое чанка. Если ещё нужны изменения состояния и квитанции, которые это исполнение произвело, переходите к [Шарду блока](/neardata/block-shard). За готовым сценарием, где квитанция доводится до конкретного чанка, переходите в [Примеры RPC](/rpc/examples). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/block-shard.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/block-shard.mdx index a71dac0..72c42d3 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/block-shard.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/block-shard.mdx @@ -1,6 +1,6 @@ --- title: "Шард блока" -description: "Получите один шард из финализированного блока." +description: "Получите более полное представление одного шарда для финализированного блока." slug: /neardata/block-shard hide_table_of_contents: true --- @@ -9,5 +9,8 @@ import FastnearDirectOperation from '@site/src/components/FastnearDirectOperatio # Шард блока +Используйте этот маршрут, когда данных одного чанка уже недостаточно и нужно более полное представление одного шарда для финализированного блока. + +Этот маршрут идёт на шаг шире, чем [Чанк блока](/neardata/block-chunk): помимо самого содержимого чанка, вы ещё видите изменения состояния на шарде и квитанции, которые это исполнение произвело. Поэтому этот маршрут лучше подходит для отладки, индексаторов и аналитики, когда важны итоги исполнения, а не только включённые транзакции и входящие квитанции. За готовым сценарием, который начинается с транзакции и заканчивается конечным чанком, переходите в [Примеры RPC](/rpc/examples). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index ee965de..b7ab0d6 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /rpc/examples title: "Примеры RPC" -description: "Пошаговые сценарии использования FastNear RPC для отправки транзакций, проверки прав ключа доступа, предварительной проверки FT, чтения сырого состояния и поиска Rainbow Bridge." +description: "Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций." displayed_sidebar: rpcSidebar page_actions: - markdown @@ -10,65 +10,98 @@ page_actions: # Примеры RPC -Используйте эту страницу, когда нужен быстрый точный ответ через RPC. Начните с одного чтения, а к транзакциям и сырому состоянию переходите только если простого запроса уже недостаточно. +Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. -## Быстрый старт +## Отправка и отслеживание транзакции -Если вы только открыли эту страницу, начните с одного точного чтения аккаунта. +Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=near +### Отправить транзакцию и затем проследить её от хеша до финального исполнения -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - finality: "final", - account_id: $account_id - } - }')" \ - | jq '.result | { - amount, - locked, - code_hash, - storage_usage - }' -``` +Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. -Это самый маленький надёжный RPC-пример на странице: один запрос, один точный ответ, без дерева receipts. +Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: -## Отправка и отслеживание транзакции +- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- signer: `mike.near` +- receiver: `social.near` +- высота блока включения: `79574923` +- высота блока исполнения receipt для записи в SocialDB: `79574924` + +Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. + +
+
+ Стратегия +

Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно.

+
+
+

01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше.

+

02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения.

+

03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts.

+
+
-### Двухчастный паттерн: отправить транзакцию или отследить уже известный tx hash до финального исполнения +**Что вы здесь решаете** -Базовый паттерн: +- какой эндпоинт отправки брать первым +- что опрашивать после того, как у вас появился tx hash +- как `wait_until` связан с included-, optimistic- и final-гарантиями +- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` -- `broadcast_tx_async` для отправки -- `tx` с `wait_until: "FINAL"` для отслеживания -- `EXPERIMENTAL_tx_status` только если следующий вопрос уже про receipts +```mermaid +flowchart LR + S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] + A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] + T --> F["Транзакция полностью завершена"] + T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] + F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] +``` -Этот walkthrough намеренно разбит на две части: +| Метод | Когда использовать | Что вернётся | Роль здесь | +| --- | --- | --- | --- | +| [`broadcast_tx_async`](/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | +| [`send_tx`](/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | +| [`broadcast_tx_commit`](/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | +| [`tx`](/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | +| [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | -- отправить новую подписанную транзакцию и сохранить возвращённый хеш -- отследить один известный исторический tx hash с воспроизводимым выводом +**Карта статусов и ожидания** -Для части про отслеживание используется одна зафиксированная историческая транзакция, поэтому status-lookup идёт через архивный хост: +Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. -- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- signer: `mike.near` -- receiver: `social.near` -- `https://archival-rpc.mainnet.fastnear.com` +| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | +| --- | --- | --- | +| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | +| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | +| `INCLUDED_FINAL` | блок включения уже финален | `tx` | +| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | +| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | -1. Отправьте новую подписанную транзакцию и сохраните возвращённый хеш. +Практическое различие очень простое: + +- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash +- используйте `tx` как обычный цикл опроса +- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу + +**Что вы делаете** + +- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. +- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. +- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. +- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. ```bash RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near +RECEIVER_ID=social.near +``` + +1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. +```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data '{ @@ -80,16 +113,40 @@ curl -s "$RPC_URL" \ | jq . ``` -Этот первый шаг нужен только для формы отправки. Именно возвращённый хеш вы потом будете отслеживать для своей живой транзакции. +В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. -2. Отслеживайте один известный tx hash, пока не получите самый простой финальный ответ. +2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. ```bash -RPC_URL=https://archival-rpc.mainnet.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "INCLUDED_FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' ``` +Что здесь важно заметить: + +- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality +- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения +- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt + +3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. + ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ @@ -107,12 +164,18 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - transaction_status: .result.status, + status: .result.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -3. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда для этого известного tx уже нужен уровень receipts. +Что здесь важно заметить: + +- для исторической tx этот вызов тоже возвращается сразу +- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» +- для многих приложений именно здесь и стоит остановиться + +4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. ```bash curl -s "$RPC_URL" \ @@ -131,54 +194,87 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, + status: .result.status, transaction_handoff: .result.transaction_outcome.outcome.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Если вы хотите, чтобы узел ждал за вас, используйте [`send_tx`](/rpc/transaction/send-tx). Но базовый паттерн на этой странице остаётся таким: отправка через `broadcast_tx_async`, затем отслеживание хеша через `tx`. +Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». + +5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transaction_handoff: .transactions[0].transaction_outcome.outcome.status + }' +``` + +Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» + +**Рекомендуемый паттерн** + +- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. +- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. +- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. ## Механика аккаунтов и ключей Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. -### Может ли этот access key прямо сейчас вызвать этот контракт? +### Проверить и удалить старые function-call-ключи Near Social -Используйте этот сценарий, когда у вас уже есть аккаунт, один public key и целевой контракт, а вам нужен простой ответ да или нет до того, как вы начнёте что-то подписывать. +Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC.
Стратегия -

Сначала отфильтруйте ключи аккаунта, затем прочитайте точный ключ и только потом классифицируйте его права.

+

Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление.

-

01RPC view_access_key_list сужает список до ключей, которые вообще могут относиться к целевому контракту.

-

02RPC view_access_key даёт точный permission-object для того public key, которым вы реально можете подписывать.

-

03jq превращает этот permission-object в full_access, function_call_match, receiver_mismatch или method_not_allowed.

+

01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near.

+

02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта.

+

03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат.

**Что вы делаете** -- Получаете access key аккаунта и сужаете список до нужного контракта. -- Точно проверяете тот ключ, которым собираетесь подписывать. -- Решаете, может ли он вызвать этот receiver и method, не выходя за пределы RPC. +- Через сам RPC получаете полный список access key аккаунта. +- Сужаете этот список до function-call-ключей, привязанных к `social.near`. +- Точно проверяете один выбранный ключ перед удалением. +- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -TARGET_CONTRACT_ID=crossword.puzzle.near -TARGET_METHOD_NAME=new_puzzle -TARGET_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_WANT_TO_CHECK' +Сразу важны два ограничения: -# Пример живых значений, проверенных 19 апреля 2026 года: -# ACCOUNT_ID=mike.near -# TARGET_CONTRACT_ID=crossword.puzzle.near -# TARGET_METHOD_NAME=new_puzzle -# TARGET_PUBLIC_KEY='ed25519:otwaB1X88ocpmUdC1B5XaifucfDLmLKaonb26KqTj96' +- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. +- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export SOCIAL_RECEIVER_ID=social.near +export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' +export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' +export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' ``` -1. Получите ключи аккаунта и сузьте их до целевого контракта. +1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. ```bash curl -s "$RPC_URL" \ @@ -193,28 +289,252 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/access-key-list.json >/dev/null - -jq --arg target_contract_id "$TARGET_CONTRACT_ID" '{ - candidate_keys: [ - .result.keys[] - | select( - .access_key.permission == "FullAccess" - or ( - (.access_key.permission | type) == "object" - and .access_key.permission.FunctionCall.receiver_id == $target_contract_id - ) - ) - | { - public_key, - nonce: .access_key.nonce, - permission: .access_key.permission + | tee /tmp/fastnear-access-keys.json >/dev/null + +jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + nonce: .access_key.nonce, + receiver_id: .access_key.permission.FunctionCall.receiver_id, + method_names: .access_key.permission.FunctionCall.method_names, + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } +' /tmp/fastnear-access-keys.json +``` + +Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. + +2. Ещё раз проверьте конкретный ключ перед удалением. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$DELETE_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" } - ] -}' /tmp/access-key-list.json + }')" \ + | jq '{nonce: .result.nonce, permission: .result.permission}' +``` + +3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + is_function_call: true, + limit: 10 + }')" \ + | jq '{ + account_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_success + } + ] + }' +``` + +Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. + +4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. + +Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. + +```bash +SIGNED_TX_BASE64="$( + node --input-type=module <<'EOF' +import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; + +const { + ACCOUNT_ID, + NETWORK_ID = 'mainnet', + RPC_URL = 'https://rpc.mainnet.fastnear.com', + DELETE_PUBLIC_KEY, + FULL_ACCESS_PUBLIC_KEY, + FULL_ACCESS_PRIVATE_KEY, +} = process.env; + +for (const name of [ + 'ACCOUNT_ID', + 'DELETE_PUBLIC_KEY', + 'FULL_ACCESS_PUBLIC_KEY', + 'FULL_ACCESS_PRIVATE_KEY', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); +const derivedPublicKey = keyPair.getPublicKey().toString(); + +if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { + throw new Error( + `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: ACCOUNT_ID, + public_key: FULL_ACCESS_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const transaction = transactions.createTransaction( + ACCOUNT_ID, + utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), + ACCOUNT_ID, + BigInt(accessKey.nonce) + 1n, + [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +)" +``` + +5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. + +```bash +if curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' + .result.keys[] + | select(.public_key == $public_key) + ' >/dev/null; then + echo "Key is still present: $DELETE_PUBLIC_KEY" +else + echo "Key deleted: $DELETE_PUBLIC_KEY" +fi +``` + +**Зачем нужен следующий шаг?** + +Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. + +### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? + +Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. + +
+
+ Стратегия +

Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно.

+
+
+

01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории.

+

02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта.

+

03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey.

+
+
+ +**Что вы делаете** + +- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. +- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. +- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. +- Подтягиваете кандидата по транзакциям и различаете три разных ключа: + - ключ, который был добавлен + - public key верхнеуровневого signer + - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` + +Сразу важны три детали про nonce: + +- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. +- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. +- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' + +# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: +# export ACCOUNT_ID=mike.near +# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' ``` -2. Прочитайте точное состояние того ключа, который хотите оценить. +1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. ```bash curl -s "$RPC_URL" \ @@ -232,72 +552,175 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/exact-access-key.json >/dev/null + | tee /tmp/key-origin-view.json >/dev/null -jq '{nonce: .result.nonce, permission: .result.permission}' /tmp/exact-access-key.json +CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" +ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" +SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" +SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" + +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_public_key "$TARGET_PUBLIC_KEY" \ + --argjson current_nonce "$CURRENT_NONCE" \ + --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ + --argjson search_from "$SEARCH_FROM" \ + --argjson search_to "$SEARCH_TO" \ + --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ + account_id: $account_id, + target_public_key: $target_public_key, + current_nonce: $current_nonce, + estimated_receipt_block: $estimated_receipt_block, + search_from_tx_block_height: $search_from, + search_to_tx_block_height: $search_to, + permission: ($permission | fromjson) + }' ``` -3. Превратите этот permission-object в ответ да или нет для этого контракта и метода. +Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. + +2. Ищите историю аккаунта только внутри этого диапазона блоков. ```bash -jq -n \ - --slurpfile key /tmp/exact-access-key.json \ - --arg target_contract_id "$TARGET_CONTRACT_ID" \ - --arg target_method_name "$TARGET_METHOD_NAME" ' - ($key[0].result.permission) as $permission - | if $permission == "FullAccess" then - { - can_call_now: true, - reason: "full_access" - } - elif $permission.FunctionCall.receiver_id != $target_contract_id then - { - can_call_now: false, - reason: "receiver_mismatch", - receiver_id: $permission.FunctionCall.receiver_id - } - elif ( - ($permission.FunctionCall.method_names | length) == 0 - or ($permission.FunctionCall.method_names | index($target_method_name)) - ) then - { - can_call_now: true, - reason: ( - if ($permission.FunctionCall.method_names | length) == 0 - then "function_call_any_method" - else "function_call_method_match" - end - ), - allowance: ($permission.FunctionCall.allowance // "unlimited") - } - else - { - can_call_now: false, - reason: "method_not_allowed", - allowed_methods: $permission.FunctionCall.method_names +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_tx_block_height "$SEARCH_FROM" \ + --argjson to_tx_block_height "$SEARCH_TO" '{ + account_id: $account_id, + is_real_signer: true, + from_tx_block_height: $from_tx_block_height, + to_tx_block_height: $to_tx_block_height, + desc: false, + limit: 50 + }')" \ + | tee /tmp/key-origin-candidates.json >/dev/null + +jq '{ + txs_count, + candidate_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_signer, + is_real_signer, + is_predecessor, + is_receiver } - end' + ] +}' /tmp/key-origin-candidates.json +``` + +Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. + +3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. + +```bash +TX_HASHES_JSON="$( + jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json +)" + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ + | tee /tmp/key-origin-transactions.json >/dev/null + +jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "direct", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $tx.transaction.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }), + ($tx.transaction.actions[]? + | .Delegate? + | .delegate_action as $delegate + | $delegate.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "delegated", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $delegate.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }) + ) + | { + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + tx_block_hash: $tx.execution_outcome.block_hash, + receiver_id: $tx.transaction.receiver_id + } + . +' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json +``` + +Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. + +Для примерного ключа `mike.near` выше совпадение оказывается делегированным: + +- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` +- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` +- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` +- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` + +4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. + +```bash +ADD_KEY_RECEIPT_ID="$( + jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | .receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) + | .receipt.receipt_id + ' /tmp/key-origin-transactions.json | head -n 1 +)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receipt_block_height: .receipt.block_height, + tx_block_height: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }' ``` -Для примерного ключа `mike.near` выше на 19 апреля 2026 года ответ получается `can_call_now: true`: это function-call-key для `crossword.puzzle.near`, а `method_names: ["new_puzzle"]` явно разрешает тот метод, который мы проверяем. +Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. **Зачем нужен следующий шаг?** -`view_access_key_list` — самый быстрый фильтр на уровне контракта. `view_access_key` — точная проверка полномочий для того public key, которым вы действительно хотите пользоваться. Если ответ `false`, вам нужен другой ключ или другая схема permissions, а не более глубокая историческая трассировка. +Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. -### Нужно ли этому получателю сначала зарегистрировать FT storage? +### Проверить регистрацию FT storage и затем перевести токены -Используйте этот сценарий, когда история звучит так: «я собираюсь отправить FT-токен и хочу получить простой ответ “нужен ли сначала `storage_deposit`?”». +Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте».
Стратегия -

Сначала прочитайте storage-состояние получателя и остановитесь, как только станет понятно, может ли `ft_transfer` уже пройти.

+

Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу.

01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас.

02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит.

-

03jq превращает эти два чтения в один ответ: «перевод уже может идти» или «сначала нужен `storage_deposit`».

+

03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог.

@@ -310,19 +733,24 @@ jq -n \ - [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) - [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) -В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Здесь важен именно read-only-ответ: нужен ли сначала `storage_deposit`, или путь перевода уже может продолжаться. +В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. **Что вы делаете** - Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. -- Получаете точный минимальный размер storage deposit на этом же контракте. -- Останавливаетесь, как только понимаете: `ft_transfer` уже может идти или сначала нужен `storage_deposit`. +- При необходимости получаете минимальный размер storage deposit. +- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. +- Подтверждаете баланс получателя тем же view-методом самого контракта. ```bash export NETWORK_ID=testnet export RPC_URL=https://rpc.testnet.fastnear.com export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet +export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' +export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' +export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' ``` 1. Проверьте, зарегистрирован ли получатель на FT-контракте. @@ -358,7 +786,7 @@ jq '{ }' /tmp/ft-storage-balance.json ``` -2. Получите минимальный storage deposit на этом же контракте. +2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. ```bash MIN_STORAGE_YOCTO="$( @@ -383,105 +811,335 @@ MIN_STORAGE_YOCTO="$( printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" ``` -3. Превратите эти два чтения в один ответ о готовности перевода. - -```bash -jq -n \ - --slurpfile balance /tmp/ft-storage-balance.json \ - --slurpfile bounds /tmp/ft-storage-bounds.json \ - --arg receiver_account_id "$RECEIVER_ACCOUNT_ID" ' - ( - $balance[0].result.result - | if length == 0 then null else (implode | fromjson) end - ) as $storage - | ( - $bounds[0].result.result - | implode - | fromjson - ) as $bounds - | { - receiver_account_id: $receiver_account_id, - receiver_registered: ($storage != null), - current_storage: $storage, - minimum_storage_deposit_yocto: $bounds.min, - next_step: ( - if $storage != null - then "получатель уже зарегистрирован; ft_transfer может продолжаться" - else "сначала отправьте storage_deposit, потом делайте ft_transfer" - end - ) - }' -``` - -**Зачем нужен следующий шаг?** - -Это чистый RPC-вопрос в этом workflow: «зарегистрирован ли уже получатель и какой минимальный депозит потребует контракт, если нет?» Подписанный write-path зависит уже от вашего wallet, CLI или backend-интеграции, поэтому в самый маленький core RPC-пример он не входит. - -## Чтения контракта и сырое состояние - -Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» - -### Прочитать счётчик прямо из состояния контракта, а потом подтвердить его через view-метод - -Используйте этот сценарий, когда вы уже знаете точное семейство storage-ключей и хотите увидеть самый короткий контраст между raw state и публичным методом чтения контракта. +3. Определите одну переиспользуемую функцию подписи для function-call к контракту. -Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`: - -- `view_state` читает сырой ключ `STATE` напрямую -- `call_function get_num` спрашивает у контракта то же текущее число +Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. ```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export CONTRACT_ID=counter.near-examples.testnet -export STATE_PREFIX_BASE64=U1RBVEU= +sign_function_call() { + METHOD_NAME="$1" \ + ARGS_JSON="$2" \ + DEPOSIT_YOCTO="$3" \ + GAS_TGAS="$4" \ + node --input-type=module <<'EOF' +import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; + +const { + NETWORK_ID = 'testnet', + RPC_URL = 'https://rpc.testnet.fastnear.com', + TOKEN_CONTRACT_ID, + SENDER_ACCOUNT_ID, + SENDER_PUBLIC_KEY, + SENDER_PRIVATE_KEY, + METHOD_NAME, + ARGS_JSON, + DEPOSIT_YOCTO = '0', + GAS_TGAS = '100', +} = process.env; + +for (const name of [ + 'TOKEN_CONTRACT_ID', + 'SENDER_ACCOUNT_ID', + 'SENDER_PUBLIC_KEY', + 'SENDER_PRIVATE_KEY', + 'METHOD_NAME', + 'ARGS_JSON', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); +const signer = await InMemorySigner.fromKeyPair( + NETWORK_ID, + SENDER_ACCOUNT_ID, + keyPair +); + +const derivedPublicKey = keyPair.getPublicKey().toString(); +if (derivedPublicKey !== SENDER_PUBLIC_KEY) { + throw new Error( + `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: SENDER_ACCOUNT_ID, + public_key: SENDER_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const action = transactions.functionCall( + METHOD_NAME, + Buffer.from(ARGS_JSON), + BigInt(GAS_TGAS) * 10n ** 12n, + BigInt(DEPOSIT_YOCTO) +); + +const transaction = transactions.createTransaction( + SENDER_ACCOUNT_ID, + utils.PublicKey.fromString(SENDER_PUBLIC_KEY), + TOKEN_CONTRACT_ID, + BigInt(accessKey.nonce) + 1n, + [action], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + SENDER_ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +} ``` -1. Сначала прочитайте сырой ключ `STATE`. +4. При необходимости сначала зарегистрируйте storage для получателя. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$CONTRACT_ID" \ - --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ +if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then + SIGNED_TX_BASE64="$( + sign_function_call \ + storage_deposit \ + "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id, + registration_only: true + }')" \ + "$MIN_STORAGE_YOCTO" \ + 100 + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ jsonrpc: "2.0", id: "fastnear", - method: "query", + method: "send_tx", params: { - request_type: "view_state", - account_id: $account_id, - prefix_base64: $prefix_base64, - finality: "final" + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" } }')" \ - | tee /tmp/counter-view-state.json >/dev/null + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash + }' +fi +``` -jq '{ - key: (.result.values[0].key | @base64d), - value_base64: .result.values[0].value +5. После готовности storage переведите FT. + +```bash +SIGNED_TX_BASE64="$( + sign_function_call \ + ft_transfer \ + "$(jq -nc \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ + receiver_id: $receiver_id, + amount: $amount, + memo: "FastNear RPC example" + }')" \ + 1 \ + 100 +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Подтвердите FT-баланс получателя тем же view-методом контракта. + +```bash +RECEIVER_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_balance_of", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + receiver_balance: (.result.result | implode | fromjson) + }' +``` + +**Зачем нужен следующий шаг?** + +Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. + +## Чтения контракта и сырое состояние + +Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» + +### Как прочитать сырое состояние контракта напрямую? + +Используйте этот сценарий, когда у контракта нет нужного view-метода или когда вам нужно проверить саму схему хранения, а не просто довериться ответу метода. + +В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: сначала вы читаете storage напрямую, а потом подтверждаете, что публичный view-метод контракта даёт тот же ответ: + +- `view_state` читает сырой ключ `STATE` прямо из storage контракта +- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API + +
+
+ Стратегия +

Сначала прочитайте storage напрямую, а уже потом дайте контракту подтвердить тот же ответ через view-метод.

+
+
+

01RPC view_state читает сырой ключ STATE, не запуская код контракта.

+

02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта.

+

03RPC call_function get_num — это удобная перепроверка того, что прямое чтение storage и view-метод по-прежнему дают один и тот же ответ.

+
+
+ +Здесь важнее ментальная модель, чем сам счётчик: + +- `view_state` — это прямое чтение storage из trie +- `call_function` исполняет read-only-метод контракта +- оба способа могут ответить на один и тот же вопрос, но делают разную работу + +```mermaid +flowchart LR + S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] + R --> D["Декодировать base64 + Borsh"] + D --> N["Знаковое значение счётчика"] + C["RPC call_function get_num"] --> J["JSON-результат метода"] + N --> X["Сравнить"] + J --> X + X --> A["Одно и то же текущее значение"] +``` + +**Что вы делаете** + +- Читаете сырой ключ `STATE` из storage контракта. +- Декодируете возвращённые байты в текущее знаковое значение счётчика. +- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export CONTRACT_ID=counter.near-examples.testnet +export STATE_PREFIX_BASE64=U1RBVEU= +``` + +1. Сначала прочитайте сырое состояние контракта. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$CONTRACT_ID" \ + --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_state", + account_id: $account_id, + prefix_base64: $prefix_base64, + finality: "final" + } + }')" \ + | tee /tmp/counter-view-state.json >/dev/null + +jq '{ + block_height: .result.block_height, + key_base64: .result.values[0].key, + value_base64: .result.values[0].value }' /tmp/counter-view-state.json + +jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json ``` -Здесь должен появиться `key: "STATE"`. Это и есть тот случай, когда `view_state` уместен: семейство ключей вам уже известно заранее. +Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. -2. Декодируйте сырые байты. +2. Декодируйте байты значения в знаковое число счётчика. ```bash RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" -python3 - "$RAW_VALUE_BASE64" <<'PY' +python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . import base64 +import json import sys raw = base64.b64decode(sys.argv[1]) -print(int.from_bytes(raw, "little", signed=True)) + +print(json.dumps({ + "value_base64": sys.argv[1], + "bytes": list(raw), + "hex": raw.hex(), + "signed_i8": int.from_bytes(raw, "little", signed=True), + "unsigned_u8": int.from_bytes(raw, "little", signed=False), +})) PY ``` -Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть гораздо менее дружелюбным: near-sdk-коллекции и Borsh-сериализованные структуры часто выводят storage-ключи из префиксов и внутренних схем ключей, поэтому `view_state` остаётся практичным только когда вы уже знаете точный layout, который хотите читать. Правило остаётся тем же: сначала байты, потом схема. +Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. + +Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. -3. Теперь спросите контракт привычным способом и сравните. +Переиспользуемый рецепт здесь короткий: + +- `view_state` возвращает сырые байты в base64 +- вы декодируете эти байты по известной схеме хранения контракта +- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема + +3. Теперь спросите контракт более привычным способом и сравните. ```bash curl -s "$RPC_URL" \ @@ -506,7 +1164,7 @@ jq '{ }' /tmp/counter-call-function.json ``` -4. Сравните оба ответа. +4. Сравните оба ответа напрямую. ```bash RAW_STATE_NUMBER="$( @@ -532,202 +1190,719 @@ jq -n \ }' ``` +Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: + +- `view_state` ответил на вопрос, прочитав storage напрямую +- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта + **Зачем нужен следующий шаг?** -Используйте `view_state`, когда вы уже знаете точное семейство storage-ключей и хотите raw bytes. Используйте `call_function`, когда вам нужен публичный метод чтения самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда уже стоит расширяться в [KV FastData API](/fastdata/kv). +Используйте `view_state`, когда настоящий вопрос относится к точному storage, отсутствующему view-методу или проверке известного семейства ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](/fastdata/kv). + +## Трассировка чанков и шардов + +Начинайте отсюда, когда вопрос уже не просто «транзакция прошла или нет?», а «какой именно чанк на шарде исполнил каждый шаг работы?» -### Какие ERC-20 токены из Rainbow Bridge существуют на NEAR и сколько одного такого токена сейчас в обращении? +### Проследить, как сгенерированная `Transfer`-receipt переходит из одного чанка на шарде в другой -Используйте этот сценарий, когда хотите найти Rainbow Bridge ERC-20 контракты и посмотреть живой объём одного токена на NEAR. Rainbow Bridge развёртывает по одному NEAR-контракту на каждый bridged ERC-20 токен, а `factory.bridge.near` их перечисляет. +Используйте этот сценарий, когда вызов контракта был только началом истории. В этом зафиксированном mainnet-примере подписанная транзакция стартует на шарде `11`, а сгенерированная `Transfer`-receipt заканчивает путь уже на шарде `6`. Именно ради таких cross-shard handoff и имеет смысл смотреть на чанки. + +Этот walkthrough привязан к: + +- транзакции `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` от `7419369993.tg` к `game.hot.tg` с вызовом `l2_claim` +- исходному чанку `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` на шарде `11` в блоке `194623170` +- чанку первой receipt `FJWpAYzVXbZwqJUbGXELTnnBBkdvc6W8vWkwuUA3Zwz9` на шарде `11` в блоке `194623171` +- сгенерированной `Transfer`-receipt `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` +- конечному чанку `EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU` на шарде `6` в блоке `194623172`
Стратегия -

Одно чтение factory перечисляет token-контракты. Ещё два небольших view-вызова по одному токену показывают, что это за токен и сколько его сейчас на NEAR.

+

Сначала восстановите receipt-цепочку, потом напрямую посмотрите на сгенерированную receipt, а затем привяжите каждый шаг к тому чанку на шарде, который действительно нёс эту работу.

-

01RPC call_function get_tokens_accounts по factory.bridge.near возвращает развёрнутые bridged token-контракты.

-

02Следующий RPC call_function по одному bridged token-контракту возвращает метаданные токена: имя, тикер и число десятичных знаков.

-

03Ещё один RPC call_function по тому же контракту возвращает текущее сырое значение объёма в обращении на NEAR.

+

01RPC EXPERIMENTAL_tx_status быстро показывает граф receipts и в какие следующие блоки перешла работа.

+

02RPC EXPERIMENTAL_receipt позволяет посмотреть на тело сгенерированной receipt напрямую, а не выводить его только из логов.

+

03RPC chunk по блоку и шарду или по хешу чанка доказывает, какая именно единица исполнения на шарде нёсла каждый шаг.

+Оба experimental-метода здесь очень уместны: `EXPERIMENTAL_tx_status` быстро находит граф receipts, а `EXPERIMENTAL_receipt` показывает тело сгенерированной receipt ещё до того, как вы привяжете её обратно к чанкам. + +```mermaid +flowchart LR + A["Tx 8xrc...
блок 194623170
чанк Bfyd...
шард 11"] --> B["Receipt AFC2...
блок 194623171
чанк FJWp...
шард 11
логи ft_mint"] + B --> C["Сгенерированная receipt TtRn...
Transfer 1800930478788300000000 yoctoNEAR"] + C --> D["Чанк EPau...
блок 194623172
шард 6
receipt исполняется"] +``` + **Что вы делаете** -- Спрашиваете у bridge factory обо всех bridged token-контрактах, которые она создала. -- Выбираете один bridged token-контракт и читаете его метаданные. -- Читаете total supply того же контракта и переводите его в человеческие единицы через `decimals`. +- Сначала восстанавливаете receipt-цепочку из транзакции. +- Напрямую смотрите на тело сгенерированной `Transfer`-receipt. +- Используете координаты блока и шарда там, где они уже известны. +- Используете хеш чанка там, где другой инструмент уже выдал точный конечный чанк. ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com -export FACTORY_ID=factory.bridge.near -export TOKENS_FILE=/tmp/rainbow-bridge-tokens.json +export TX_HASH=8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP +export SIGNER_ACCOUNT_ID=7419369993.tg +export ORIGIN_BLOCK_HEIGHT=194623170 +export ORIGIN_SHARD_ID=11 +export RECEIPT_BLOCK_HEIGHT=194623171 +export RECEIPT_SHARD_ID=11 +export GENERATED_RECEIPT_ID=TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4 +export DESTINATION_CHUNK_HASH=EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU ``` -1. Получите список bridged token-контрактов. +1. Начните с `EXPERIMENTAL_tx_status`, чтобы сначала увидеть граф receipts, а уже потом думать о чанках. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$FACTORY_ID" '{ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: [$tx_hash, $signer_account_id] + }')" \ + | tee /tmp/chunk-trace-status.json >/dev/null + +jq '{ + final_execution_status: .result.final_execution_status, + transaction_handoff: .result.transaction_outcome.outcome.status, + receipts: ( + .result.receipts_outcome + | map({ + receipt_id: .id, + executor_id: .outcome.executor_id, + block_hash, + status: .outcome.status + }) + ) +}' /tmp/chunk-trace-status.json +``` + +На что смотреть: + +- подписанная транзакция передаёт работу в receipt `AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y` +- позже в том же графе receipts исполняется `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` для `7419369993.tg` +- уже одного tx status достаточно, чтобы увидеть: настоящая работа продолжилась после исходной подписанной транзакции + +2. Посмотрите на сгенерированную receipt напрямую, чтобы доказать, что это действительно `Transfer`-receipt. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$GENERATED_RECEIPT_ID" '{ jsonrpc: "2.0", id: "fastnear", - method: "query", + method: "EXPERIMENTAL_receipt", params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_tokens_accounts", - args_base64: "e30=", - finality: "final" + receipt_id: $receipt_id } }')" \ - | tee "$TOKENS_FILE" >/dev/null + | tee /tmp/chunk-trace-receipt.json >/dev/null -jq -r '.result.result | implode | fromjson | .[]' "$TOKENS_FILE" +jq '{ + predecessor_id: .result.predecessor_id, + receiver_id: .result.receiver_id, + signer_id: .result.receipt.Action.signer_id, + signer_public_key: .result.receipt.Action.signer_public_key, + actions: .result.receipt.Action.actions +}' /tmp/chunk-trace-receipt.json ``` -Каждая строка — это один bridged FT-контракт на NEAR в форме `.factory.bridge.near`. Например, bridged ERC-20 USDT с Ethereum-адреса `0xdAC17F958D2ee523a2206206994597C13D831ec7` появляется как `dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near`. +Именно здесь история по шардам становится конкретной: эта цепочка исполнения контракта сгенерировала `Transfer` action receipt от `system` к `7419369993.tg` с депозитом `1800930478788300000000`. -2. Прочитайте метаданные одного токен-контракта. +3. Используйте `chunk` по блоку и шарду, чтобы найти исходную подписанную транзакцию на шарде `11`. ```bash -export TOKEN_ID=dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --argjson block_id "$ORIGIN_BLOCK_HEIGHT" \ + --argjson shard_id "$ORIGIN_SHARD_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "chunk", + params: { + block_id: $block_id, + shard_id: $shard_id + } + }')" \ + | jq --arg tx_hash "$TX_HASH" '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created + }, + matching_transaction: ( + .result.transactions[] + | select(.hash == $tx_hash) + | { + hash, + signer_id, + receiver_id + } + ) + }' +``` + +Это самый чистый use case для `chunk` по блоку и шарду: координаты уже известны, а вам нужна точная единица исполнения на шарде, которая несла исходную подписанную транзакцию. + +4. Оставайтесь на том же маршруте и посмотрите, как первая receipt исполняется в следующем блоке на том же шарде. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --argjson block_id "$RECEIPT_BLOCK_HEIGHT" \ + --argjson shard_id "$RECEIPT_SHARD_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "chunk", + params: { + block_id: $block_id, + shard_id: $shard_id + } + }')" \ + | jq '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created, + tx_root: .result.header.tx_root, + gas_used: .result.header.gas_used + }, + tx_count: (.result.transactions | length), + receipt_count: (.result.receipts | length), + matching_receipt: ( + .result.receipts[] + | select(.receipt_id == "AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y") + | { + receipt_id, + predecessor_id, + receiver_id + } + ) + }' +``` + +Вот здесь chunks наконец становятся естественными: + +- у чанка `tx_root = 11111111111111111111111111111111` +- `tx_count` равен `0` +- но шард всё равно жжёт gas и исполняет receipt `AFC2...` + +То есть этот шард реально сделал работу в этом блоке, хотя новая подписанная транзакция прямо в самом чанке не появилась. +5. Переключайтесь на `chunk` по хешу, когда другой инструмент уже выдал точный конечный чанк. + +```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + --data "$(jq -nc --arg chunk_id "$DESTINATION_CHUNK_HASH" '{ jsonrpc: "2.0", id: "fastnear", - method: "query", + method: "chunk", params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_metadata", - args_base64: "e30=", - finality: "final" + chunk_id: $chunk_id } }')" \ - | tee /tmp/rainbow-bridge-token-metadata.json >/dev/null + | jq --arg receipt_id "$GENERATED_RECEIPT_ID" '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created, + tx_root: .result.header.tx_root, + gas_used: .result.header.gas_used + }, + tx_count: (.result.transactions | length), + receipt_count: (.result.receipts | length), + matching_receipt: ( + .result.receipts[] + | select(.receipt_id == $receipt_id) + | { + receipt_id, + predecessor_id, + receiver_id + } + ) + }' +``` + +Это и подтверждает cross-shard hop: + +- сгенерированная `Transfer`-receipt исполняется в чанке `EPau...` +- этот чанк живёт на шарде `6`, а не на шарде `11` +- подписанная транзакция стартовала на одном шарде, а следующая receipt завершилась уже на другом + +**Зачем нужен следующий шаг?** + +Используйте [Chunk by Block and Shard](/rpc/protocol/chunk-by-block-shard), когда вы знаете координаты блока и шарда и хотите буквально спросить: «что этот шард исполнил в этом блоке?» Используйте [Chunk by Hash](/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. Используйте [EXPERIMENTAL_tx_status](/rpc/transaction/experimental-tx-status) и [EXPERIMENTAL_receipt](/rpc/transaction/experimental-receipt), когда настоящий вопрос относится к трассировке на уровне receipts. Если ещё нужны state changes и produced receipts, расширяйтесь в [Block Shard](/neardata/block-shard). + +## Точные чтения NEAR Social и BOS -jq '.result.result | implode | fromjson | {name, symbol, decimals}' /tmp/rainbow-bridge-token-metadata.json +Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. + +### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? + +Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». + +
+
+ Стратегия +

Спросите у social.near ровно о двух вещах, которые важны до подписи.

+
+
+

01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию.

+

02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near.

+

03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer.

+
+
+ +Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: + +- есть ли у целевого аккаунта storage на `social.near`? +- если есть, осталось ли там ещё место? +- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? + +**Официальные ссылки** + +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +**Что вы делаете** + +- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. +- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. +- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. +- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mike.near +export SIGNER_ACCOUNT_ID=mike.near ``` -3. Прочитайте текущий total supply на NEAR и переведите его в человеческие единицы. +1. Сначала проверьте сам аккаунт signer. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { - request_type: "call_function", + request_type: "view_account", account_id: $account_id, - method_name: "ft_total_supply", - args_base64: "e30=", finality: "final" } }')" \ - | tee /tmp/rainbow-bridge-token-supply.json >/dev/null + | tee /tmp/social-publish-signer.json >/dev/null + +jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + signer_account_id: $signer_account_id, + amount: .result.amount, + locked: .result.locked, + storage_usage: .result.storage_usage +}' /tmp/social-publish-signer.json +``` -RAW_SUPPLY="$( - jq -r '.result.result | implode | fromjson' /tmp/rainbow-bridge-token-supply.json -)" +Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. + +2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. -DECIMALS="$( - jq -r '.result.result | implode | fromjson | .decimals' /tmp/rainbow-bridge-token-metadata.json +```bash +SOCIAL_STORAGE_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' )" -HUMAN_SUPPLY="$( - python3 - "$RAW_SUPPLY" "$DECIMALS" <<'PY' -from decimal import Decimal -import sys +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_account_storage", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-account-storage.json >/dev/null -raw = Decimal(sys.argv[1]) -decimals = int(sys.argv[2]) -human = raw / (Decimal(10) ** decimals) -print(human) -PY +jq --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + storage: (.result.result | implode | fromjson), + storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) +}' /tmp/social-account-storage.json +``` + +Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. + +3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. + +```bash +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + jq -n --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + signer_matches_target: true, + permission_granted: true, + reason: "owner write" + }' +else + WRITE_PERMISSION_ARGS_BASE64="$( + jq -nc \ + --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ + --arg key "$ACCOUNT_ID" '{ + predecessor_id: $predecessor_id, + key: $key + }' | base64 | tr -d '\n' + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + signer_matches_target: false, + permission_granted: (.result.result | implode | fromjson) + }' +fi +``` + +4. Сведите проверку storage и разрешения в один читаемый итог. + +```bash +AVAILABLE_BYTES="$( + jq -r ' + .result.result + | if length == 0 then "0" + else (implode | fromjson | .available_bytes // 0 | tostring) + end + ' /tmp/social-account-storage.json )" +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + PERMISSION_GRANTED=true +else + PERMISSION_GRANTED="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq -r '.result.result | implode | fromjson' + )" +fi + jq -n \ - --arg token_id "$TOKEN_ID" \ - --arg raw_supply "$RAW_SUPPLY" \ - --argjson decimals "$DECIMALS" \ - --arg human_supply "$HUMAN_SUPPLY" '{ - token_id: $token_id, - raw_supply: $raw_supply, - decimals: $decimals, - human_supply: $human_supply + --arg account_id "$ACCOUNT_ID" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ + --argjson available_bytes "$AVAILABLE_BYTES" \ + --argjson permission_granted "$PERMISSION_GRANTED" '{ + account_id: $account_id, + signer_account_id: $signer_account_id, + storage_ready: ($available_bytes > 0), + permission_ready: $permission_granted, + ready_to_publish_now: (($available_bytes > 0) and $permission_granted) }' ``` -Результат `ft_total_supply` приходит в минимальных единицах токена. Используйте `decimals` из ответа предыдущего шага, чтобы перевести его в человекочитаемый объём в обращении. +Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. + +**Зачем нужен следующий шаг?** + +Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. + +### Что прямо сейчас содержит `mob.near/widget/Profile`? + +Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». + +
+
+ Стратегия +

Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой.

+
+
+

01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*.

+

02RPC call_function get читает точный исходник widget/Profile.

+

03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples.

+
+
+ +**Официальные ссылки** + +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +**Что вы делаете** + +- Спрашиваете у `social.near` каталог виджетов под `mob.near`. +- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. +- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. +- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](/tx/examples). -#### Необязательное расширение: показать первые несколько bridged token-ов с метаданными и объёмом в обращении +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mob.near +export WIDGET_NAME=Profile +``` -Используйте это расширение, когда нужен быстрый sample-инвентарь и вы всё ещё хотите оставаться в RPC. +1. Получите каталог виджетов и сохраните высоты блоков последней записи. ```bash -export TOKEN_SAMPLE_COUNT=5 +WIDGET_KEYS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} + }' | base64 | tr -d '\n' +)" -python3 <<'PY' -import json -import os -from decimal import Decimal - -TOKENS_FILE = os.environ["TOKENS_FILE"] -LIMIT = int(os.environ.get("TOKEN_SAMPLE_COUNT", "5")) -RPC_URL = os.environ["RPC_URL"] - -def decode_result(result): - return json.loads("".join(chr(b) for b in result)) - -with open(TOKENS_FILE) as fh: - token_ids = decode_result(json.load(fh)["result"]["result"])[:LIMIT] - -def rpc_call(account_id, method_name): - payload = { - "jsonrpc": "2.0", - "id": "fastnear", - "method": "query", - "params": { - "request_type": "call_function", - "account_id": account_id, - "method_name": method_name, - "args_base64": "e30=", - "finality": "final", - }, - } - import subprocess - raw = subprocess.check_output([ - "curl", "-s", RPC_URL, - "-H", "content-type: application/json", - "--data", json.dumps(payload), - ], text=True) - return decode_result(json.loads(raw)["result"]["result"]) - -print(f"{'token_id':<56} {'symbol':<12} {'decimals':>8} {'raw_supply':>24} {'human_supply':>24} name") -for token_id in token_ids: - metadata = rpc_call(token_id, "ft_metadata") - raw_supply = rpc_call(token_id, "ft_total_supply") - human_supply = Decimal(raw_supply) / (Decimal(10) ** metadata["decimals"]) - print( - f"{token_id:<56} " - f"{metadata['symbol']:<12} " - f"{metadata['decimals']:>8} " - f"{raw_supply:>24} " - f"{str(human_supply):>24} " - f"{metadata['name']}" - ) -PY +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "keys", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-keys.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].widget + | to_entries + | sort_by(.value * -1) + | map({ + widget_name: .key, + last_write_block: .value + }) + | .[0:20] +' /tmp/social-widget-keys.json ``` +2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. + +```bash +WIDGET_GET_ARGS_BASE64="$( + jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-source.json >/dev/null + +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:25] + | join("\n") + ' /tmp/social-widget-source.json +``` + +3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. + +```bash +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + ' /tmp/social-widget-keys.json \ + | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +``` + +На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. + **Зачем нужен следующий шаг?** -Оставайтесь в RPC, пока вопрос звучит как «какие bridged token-контракты существуют и сколько одного такого токена сейчас в обращении?» Factory — это источник истины для множества bridged token-ов, а каждый token-контракт сам отвечает за свои метаданные и объём в обращении через стандартные NEP-141 view-методы. Если следующий вопрос становится «кто держит этот токен?», переключайтесь на [V1 FT Top Holders](/api/v1/ft-top), а не пытайтесь обходить holders через RPC. +Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. + +## Частые задачи + +### Проверить точное состояние аккаунта или ключа доступа + +**Начните здесь** + +- [View Account](/rpc/account/view-account) для точных полей аккаунта. +- [View Access Key](/rpc/account/view-access-key) или [View Access Key List](/rpc/account/view-access-key-list) для проверки ключей. + +**Следующая страница при необходимости** + +- [FastNear API full account view](/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. +- [Transactions API account history](/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» + +**Остановитесь, когда** + +- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. + +**Переходите дальше, когда** + +- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. +- Пользователя интересует не текущее состояние, а недавняя история активности. + +### Трассировать исполнение на уровне шарда через чанки + +**Начните здесь** + +- Начните с примера выше, если настоящий вопрос звучит как «какой чанк или шард вообще исполнил эту receipt?» +- [Chunk by Block and Shard](/rpc/protocol/chunk-by-block-shard), когда координаты блока и шарда уже известны. +- [Chunk by Hash](/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. + +**Следующая страница при необходимости** + +- [Experimental Receipt](/rpc/transaction/experimental-receipt), если нужно само тело сгенерированной receipt. +- [Block Shard](/neardata/block-shard), если chunk payload уже недостаточен и ещё нужны state changes или produced receipts. +- [Transactions Examples](/tx/examples), если вопрос превращается в более широкое async- или callback-расследование. + +**Остановитесь, когда** + +- Уже можно назвать, какой именно чанк и какой шард несли ту работу, которая была важна. + +**Переходите дальше, когда** + +- Пользователю нужны уже не детали исполнения на уровне шарда, а читаемая история транзакции. Тогда переходите к [Transactions API](/tx). + +### Проверить один точный блок или снимок состояния протокола + +**Начните здесь** + +- [Block by ID](/rpc/block/block-by-id) или [Block by Height](/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. +- [Latest Block](/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» +- [Status](/rpc/protocol/status), [Health](/rpc/protocol/health) или [Network Info](/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. + +**Следующая страница при необходимости** + +- [Block Effects](/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. +- [Transactions API block history](/tx/block) или [Transactions API block range](/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» + +**Остановитесь, когда** + +- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. + +**Переходите дальше, когда** + +- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](/neardata). +- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](/tx). + +### Что этот контракт возвращает прямо сейчас? + +**Начните здесь** + +- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» +- [Call Function](/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. +- [View State](/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. +- [View Code](/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» + +**Следующая страница при необходимости** + +- [FastNear API](/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. +- [KV FastData API](/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» + +**Остановитесь, когда** + +- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. + +**Переходите дальше, когда** + +- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. +- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» + +### Отправить транзакцию и подтвердить результат + +**Начните здесь** + +- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. +- [Send Transaction](/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. +- [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. +- [Transaction Status](/rpc/transaction/tx-status), чтобы подтвердить финальный результат. + +**Следующая страница при необходимости** + +- [Transactions by Hash](/tx/transactions), если после отправки нужна более читаемая история по транзакции. +- [Receipt Lookup](/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. +- [Transactions Examples](/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» + +**Остановитесь, когда** + +- У вас уже есть результат отправки и нужный финальный статус. + +**Переходите дальше, когда** + +- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. +- Нужен уже не единичный статус, а более широкий сценарий расследования. ## Частые ошибки diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/protocol/chunk-by-block-shard.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/protocol/chunk-by-block-shard.mdx index 4e1caa0..6534ab5 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/protocol/chunk-by-block-shard.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/protocol/chunk-by-block-shard.mdx @@ -12,5 +12,8 @@ import FastnearDirectOperation from '@site/src/components/FastnearDirectOperatio `chunk` — тип запроса. +Используйте этот маршрут, когда вы знаете координаты блока и шарда и хотите посмотреть, что именно этот шард исполнил в данном блоке. + +Это самый естественный RPC-маршрут для cross-shard debugging, потому что он отвечает на практический вопрос: «что исполнил шард X в блоке Y?» В ответе вы получаете заголовок чанка, а также транзакции и receipts в этой единице исполнения конкретного шарда. Если у вас уже есть точный хеш чанка, используйте [Чанк по хешу](/rpc/protocol/chunk-by-hash). Если нужна более полная shard-оболочка со state changes и produced receipts, переходите к [Block Shard](/neardata/block-shard). За готовым tracing-примером переходите в [RPC Examples](/rpc/examples). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/protocol/chunk-by-hash.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/protocol/chunk-by-hash.mdx index 7f99188..5460067 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/protocol/chunk-by-hash.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/protocol/chunk-by-hash.mdx @@ -12,5 +12,8 @@ import FastnearDirectOperation from '@site/src/components/FastnearDirectOperatio `chunk` — тип запроса. +Используйте этот маршрут, когда другой инструмент уже выдал точный хеш чанка, а вам нужен канонический protocol chunk: заголовок, включённые транзакции и входящие receipts, которые этот шард исполнил в данном блоке. + +В NEAR подписанная транзакция может передать работу в receipts, а receipts потом продолжают исполняться в следующих чанках или на других шардах. Этот маршрут особенно уместен, когда хеш чанка уже известен из сводки по блоку, tracing-сценария или proof/debugging-потока. Если вы знаете координаты блока и шарда, используйте [Чанк по блоку и шарду](/rpc/protocol/chunk-by-block-shard). За готовым receipt-to-chunk walkthrough переходите в [RPC Examples](/rpc/examples). diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 08470e6..a8c712b 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -994,43 +994,16 @@ https://test.api.fastnear.com **Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) -## Быстрый старт - -Начните с одного поиска по публичному ключу и одного широкого чтения аккаунта. - -```bash -API_BASE_URL=https://api.fastnear.com -PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' - -ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" - -ACCOUNT_ID="$( - curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ - | jq -r '.account_ids[0]' -)" - -echo "$ACCOUNT_ID" - -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' -``` - -Это самый короткий путь к вопросам «какой это аккаунт?» и «что сейчас видно по этому кошельку?» - ## Готовые сценарии +Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. + ### Определить аккаунт по публичному ключу, а затем получить сводку по нему Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» Стратегия - Сначала определите личность, а затем либо сразу проверьте один аккаунт, либо пройдитесь по всему списку, если ключ сопоставляется с несколькими аккаунтами. + Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. @@ -1039,8 +1012,8 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Что вы делаете** - Ищете по публичному ключу один или несколько `account_id`. -- Сначала считаете, сколько `account_id` вернулось, прежде чем выбирать один. -- Сразу переиспользуете один аккаунт или проходите по всему списку, если ключ сопоставляется с несколькими аккаунтами. +- Извлекаете первый найденный `account_id` через `jq`. +- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. ```bash API_BASE_URL=https://api.fastnear.com @@ -1056,53 +1029,32 @@ ACCOUNT_ID="$( | jq -r '.account_ids[0]' )" -ACCOUNT_COUNT="$( - jq -r '.account_ids | length' /tmp/fastnear-public-key.json -)" - -jq '{ - account_ids, - account_count: (.account_ids | length) -}' /tmp/fastnear-public-key.json +jq '{account_ids}' /tmp/fastnear-public-key.json -if [ "$ACCOUNT_COUNT" -eq 1 ]; then - curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' -else - jq -r '.account_ids[]' /tmp/fastnear-public-key.json \ - | while read -r candidate_account_id; do - curl -s "$API_BASE_URL/v1/account/$candidate_account_id/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' - done -fi +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' ``` **Зачем нужен следующий шаг?** -Поиск по публичному ключу говорит, с каким аккаунтом или аккаунтами вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, именно здесь стоит либо пройтись по каждому найденному `account_id`, либо перейти к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) для более широкого исторического ответа. +Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. -### Есть ли у этого аккаунта прямой стейкинг прямо сейчас? +### Показывает ли этот кошелёк прямой стейкинг, ликвидные стейкинг-токены или и то и другое? -Используйте этот сценарий, когда история проста: «скажи, есть ли у аккаунта видимые прямые staking pool прямо сейчас, и покажи, какие именно это пулы». +Используйте этот сценарий, когда история звучит так: «покажи, видно ли по этому кошельку прямые позиции в staking pool, ликвидные стейкинг-токены или и то и другое». Стратегия - Один раз прочитайте staking-эндпоинт и превратите видимый список пулов в ответ “да / нет”. + Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. - 01GET /v1/account/.../staking возвращает видимые прямые staking-позиции аккаунта. - 02jq превращает ответ в has_direct_staking_now, pool_count и pool_ids. - 03Если массив pools пуст, ответ этой поверхности просто звучит как «прямой стейкинг сейчас не виден». + 01GET /v1/account/.../staking находит прямую экспозицию через пулы. + 02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них. + 03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed. **Сеть** @@ -1111,208 +1063,344 @@ fi **Официальные ссылки** - [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) +- [Liquid staking](https://docs.near.org/primitives/liquid-staking) + +Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. **Что вы делаете** - Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Печатаете короткий итог “да / нет” и список видимых `pool_id`. -- На этом останавливаетесь, если только следующий вопрос уже не касается `unstake` или `withdraw` в конкретном пуле. +- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. +- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. +- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' ``` 1. Получите представление по прямому стейкингу. ```bash curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json >/dev/null + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' +``` -jq '{ - account_id, - has_direct_staking_now: ((.pools // []) | length > 0), - pool_count: ((.pools // []) | length), - pool_ids: ((.pools // []) | map(.pool_id)) -}' /tmp/account-staking.json +2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. + +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null ``` -На момент написания для `mike.near` здесь возвращались видимые прямые staking-пулы. Если для вашего аккаунта `pool_ids` пуст, этот эндпоинт отвечает: «прямой стейкинг сейчас не виден». +3. Классифицируйте аккаунт на основе этих двух индексированных представлений. + +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' +``` **Зачем нужен следующий шаг?** -Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: liquid staking-позиции обычно сначала видны как FT-holdings в конкретных LST-контрактах, поэтому правильный follow-up здесь — FT-пример ниже. И ещё одна граница этой поверхности: этот эндпоинт сейчас не показывает pending-unstake или withdraw-ready amount, так что по нему не стоит отвечать на вопросы о задержках по эпохам. +Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. -#### Необязательное продолжение: Что сделал этот контрактный вызов для делегирования? +### Заархивировать версию BOS-виджета как provenance NFT -Используйте это продолжение, когда staking-эндпоинт уже показал пул вроде `polkachu.poolv1.near`, и теперь вы хотите увидеть форму одной реальной делегационной транзакции. +Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». -Этот зафиксированный mainnet tx хорош тем, что очень ясно показывает весь паттерн: + Стратегия + Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. + + 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. + 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. + 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner. + +**Сети** + +- mainnet для чтения виджета из `social.near` +- testnet для безопасного mint provenance NFT в `nft.examples.testnet` + +**Официальные ссылки** + +- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) +- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +**Что вы делаете** -- хеш транзакции: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` -- top-level receiver: `polkachu.poolv1.near` -- top-level метод: `deposit_and_stake` -- attached deposit: `34650000000000000000000000` (≈34.65 NEAR) +- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. +- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. +- Хешируете исходник виджета и превращаете его в provenance-метаданные. +- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. +- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. -Важная форма chain-истории здесь такая: +Зафиксированный исходный виджет: -- делегатор отправляет `FunctionCall deposit_and_stake` в pool-контракт -- pool-контракт учитывает депозит и staking shares -- затем pool выпускает self-receipt с настоящим `Stake` action +- аккаунт автора: `mob.near` +- путь виджета: `mob.near/widget/Profile` +- SocialDB-блок уровня виджета: `86494825` ```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz +API_BASE_URL=https://test.api.fastnear.com +MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com +TESTNET_RPC_URL=https://rpc.testnet.fastnear.com +AUTHOR_ACCOUNT_ID=mob.near +WIDGET_NAME=Profile +DESTINATION_COLLECTION_ID=nft.examples.testnet +RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" +``` -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/staking-delegation-tx.json >/dev/null +1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. -jq '{ - top_level_call: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - attached_deposit: .transactions[0].transaction.actions[0].FunctionCall.deposit - }, - pool_side_effects: [ - .transactions[0].receipts[] - | select(.receipt.receiver_id == "polkachu.poolv1.near") +```bash +curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ + | tee /tmp/provenance-account-nfts.json >/dev/null + +jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ + existing_archive_tokens: [ + .tokens[]? + | select(.contract_id == $destination_collection_id) | { - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - actions: ( - .receipt.receipt.Action.actions - | map(if type == "string" then . else keys[0] end) - ), - first_logs: (.execution_outcome.outcome.logs[:3]) + contract_id, + token_id, + last_update_block_height } ] -}' /tmp/staking-delegation-tx.json +}' /tmp/provenance-account-nfts.json ``` -Простой вывод здесь такой: делегатор не подписывал сырой `Stake` action напрямую. Он вызвал staking-pool-контракт через `deposit_and_stake` и приложил депозит, а затем уже pool-контракт сам выполнил `Stake` action на своём аккаунте. +2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. + +```bash +WIDGET_ARGS_BASE64="$( + jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($author_account_id + "/widget/" + $widget_name)], + options: {with_block_height: true} + }' | base64 | tr -d '\n' +)" -### Какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает? +curl -s "$MAINNET_RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/bos-widget.json >/dev/null + +jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + widget_path: ($author_account_id + "/widget/" + $widget_name), + socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], + source_preview: ( + .[$author_account_id].widget[$widget_name][""] + | split("\n")[0:8] + ) +}' /tmp/bos-widget.json +``` -Используйте этот сценарий, когда у wallet-экрана, support-инструмента или агента уже есть `account_id` и нужен быстрый индексированный обзор holdings: FT-балансы плюс NFT-коллекции, из которых этот аккаунт сейчас что-то показывает. +3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. - Стратегия - Сначала прочитайте FT-балансы, затем NFT-коллекции и только потом соберите их в один компактный индексированный инвентарь. +```bash +jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][""] +' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx + +WIDGET_BLOCK_HEIGHT="$( + jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][":block"] + ' /tmp/bos-widget.json +)" - 01GET /v1/account/.../ft даёт индексированные FT-балансы кошелька. - 02GET /v1/account/.../nft даёт NFT-коллекции, из которых этот кошелёк сейчас показывает holdings. - 03jq превращает эти два индексированных чтения в один wallet-friendly инвентарь. +SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" +SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" +TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" + +PROVENANCE_METADATA_JSON="$( + jq -nc \ + --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" \ + --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ + --arg block_height "$WIDGET_BLOCK_HEIGHT" \ + --arg source_sha256 "$SOURCE_SHA256" '{ + title: ("BOS widget archive: " + $widget_path), + description: ("Archived from social.near on mainnet at block " + $block_height), + copies: 1, + extra: ({ + author_account_id: $author_account_id, + widget_name: $widget_name, + widget_path: $widget_path, + source_contract_id: "social.near", + source_network: "mainnet", + socialdb_block_height: ($block_height | tonumber), + source_sha256: $source_sha256 + } | @json) + }' +)" -**Что вы делаете** +printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' +``` -- Читаете FT-балансы аккаунта. -- Читаете NFT-holdings аккаунта на уровне коллекций. -- Печатаете один короткий индексированный инвентарь, который можно переиспользовать в wallet- или support-сценарии. +4. Выпустите provenance NFT в testnet. -Этот пример не отвечает на вопросы про нативный баланс, стейкинг, пулы, точные NFT token ID или метаданные. +```bash +near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ + --arg token_id "$TOKEN_ID" \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --argjson metadata "$PROVENANCE_METADATA_JSON" '{ + token_id: $token_id, + receiver_id: $receiver_id, + metadata: $metadata + }')" \ + --accountId "$SIGNER_ACCOUNT_ID" \ + --deposit 0.1 \ + --networkId testnet +``` -FT-эндпоинт здесь решает задачу балансов. Он не включает display-метаданные вроде `symbol` или `decimals`; когда нужно форматировать баланс для UI, вызовите у токен-контракта read-метод `ft_metadata` через RPC. +5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. -NFT-эндпоинт здесь работает на уровне коллекций. Воспринимайте его как ответ на вопрос «из каких NFT-контрактов этот аккаунт сейчас что-то держит?», а не как полный per-token crawl. +Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID +NFT_TOKEN_ARGS_BASE64="$( + jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ + | base64 | tr -d '\n' +)" -# Пример живого значения, проверенного 19 апреля 2026 года: -# ACCOUNT_ID=mike.near -``` +for attempt in 1 2 3 4 5; do + curl -s "$TESTNET_RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$DESTINATION_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_token", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/bos-widget-provenance-token.json >/dev/null -1. Прочитайте FT-балансы аккаунта. + if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then + break + fi -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null + sleep 1 +done jq '{ - account_id, - ft_contracts: ( - .tokens - | map(select((.balance // "0") != "0") | { - contract_id, - balance, - last_update_block_height - }) - | .[:10] - ) -}' /tmp/account-ft.json + token_id, + owner_id, + title: .metadata.title, + provenance: (.metadata.extra | fromjson) +}' /tmp/bos-widget-provenance-token.json ``` -2. Прочитайте NFT-коллекции для того же аккаунта. +**Зачем нужен следующий шаг?** -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ - | tee /tmp/account-nft.json >/dev/null +FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). -jq '{ - account_id, - nft_collections: ( - (.tokens // []) - | map({ - contract_id, - last_update_block_height - }) - | unique_by(.contract_id) - | .[:10] - ) -}' /tmp/account-nft.json -``` +## Частые задачи -3. Соберите из этих двух чтений один компактный инвентарь. +### Что этот аккаунт вообще держит прямо сейчас? -```bash -jq -n \ - --slurpfile ft /tmp/account-ft.json \ - --slurpfile nft /tmp/account-nft.json ' - ($ft[0].tokens // []) as $ft_tokens - | ($nft[0].tokens // []) as $nft_tokens - | { - account_id: ($ft[0].account_id // $nft[0].account_id), - ft_contract_count: ( - $ft_tokens - | map(select((.balance // "0") != "0")) - | length - ), - nft_collection_count: ( - $nft_tokens - | map(.contract_id) - | unique - | length - ), - ft_contracts: ( - $ft_tokens - | map(select((.balance // "0") != "0") | { - contract_id, - balance, - last_update_block_height - }) - | .[:10] - ), - nft_collections: ( - $nft_tokens - | map({ - contract_id, - last_update_block_height - }) - | unique_by(.contract_id) - | .[:10] - ) - }' -``` +**Начните здесь** -Для `mike.near` на 19 апреля 2026 года эти чтения вернули десятки FT-контрактов и NFT-коллекций. Этого достаточно для частого wallet-вопроса: «какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает?» +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» -**Зачем нужен следующий шаг?** +**Следующая страница при необходимости** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» + +**Остановитесь, когда** + +- Сводка уже отвечает на вопрос по активам в одной выдаче. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Определить аккаунты по публичному ключу + +**Начните здесь** + +- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. +- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. + +**Остановитесь, когда** -Переходите к [`GET /v1/account/{account_id}/full`](https://docs.fastnear.com/ru/api/v1/account-full), когда следующий вопрос уже требует ещё и стейкинг, пулы или нативное состояние аккаунта. Переходите к contract-specific чтениям только тогда, когда вопрос меняется на «какие именно идентификаторы NFT-токенов и метаданные мне принадлежат?» +- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). +- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? + +**Начните здесь** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. +- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft), когда вопрос конкретно про владение NFT. +- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» + +**Остановитесь, когда** + +- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. + +**Переходите дальше, когда** + +- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). ## Частые ошибки @@ -1435,7 +1523,7 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" \ # KV FastData API -KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации FastData. +KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации хранилища. ## Базовые URL @@ -1447,12 +1535,41 @@ https://kv.main.fastnear.com https://kv.test.fastnear.com ``` +## Быстрый старт + +Если вы уже знаете один точный ключ, начните с последней индексированной строки и остановитесь, как только она ответит на вопрос. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' +``` + +Это самый узкий полезный KV-запрос: один точный ключ и одна последняя индексированная строка. Если следующий вопрос уже звучит как «как этот ключ менялся со временем?», переходите к [истории по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или к более подробным [примерам KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples). + ## Используйте этот API, когда - нужно последнее индексированное состояние по одному ключу или известному семейству ключей - нужна история изменений по аккаунту, ключу или `predecessor_id` - нужны пакетные поиски по уже известным точным ключам -- идёт отладка индексированных строк FastData, эмитированных контрактом или предшественником +- идёт отладка хранилища контракта в индексированном виде ## Не стартуйте здесь, когда @@ -1478,18 +1595,18 @@ https://kv.test.fastnear.com ## Нужен сценарий? -Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории точного ключа, анализа по `predecessor_id` и привязки к транзакции. +Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. ## Рабочий цикл по умолчанию 1. Выберите самую узкую область, подходящую под вопрос пользователя. 2. Оставайтесь внутри KV FastData, пока вопрос остаётся про индексированные данные «ключ–значение». 3. Используйте эндпоинты «последнего значения» для текущих индексированных представлений, а исторические эндпоинты — только когда пользователю нужны ответы с изменением во времени. -4. Остановитесь, как только индексированных строк уже достаточно для ответа на FastData-вопрос. +4. Остановитесь, как только индексированных строк уже достаточно для ответа на вопрос о хранилище. ## Аутентификация и доступность -- Публичные индексированные чтения FastData часто работают и без ключа. +- Публичные индексированные чтения хранилища часто работают и без ключа. - Если вы стандартизируете один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. - Добавьте `?network=testnet`, чтобы переключить страницу на testnet-бэкенд там, где это поддерживается. - В ответах со списками поле `page_token` отсутствует, когда новых результатов больше нет. @@ -1500,7 +1617,7 @@ https://kv.test.fastnear.com - пользователю нужна каноническая семантика состояния контракта - индексированное представление хранилища — неподходящая абстракция для вопроса -Тогда расширяйтесь на [Просмотр состояния контракта](https://docs.fastnear.com/ru/rpc/contract/view-state) в [Справочнике RPC](https://docs.fastnear.com/ru/rpc) или на собственный read-метод контракта. Не предполагайте, что один ключ FastData напрямую соответствует одному raw-ключу из `view_state`. +Тогда расширяйтесь на [Просмотр состояния контракта](https://docs.fastnear.com/ru/rpc/contract/view-state) в [Справочнике RPC](https://docs.fastnear.com/ru/rpc). ## Устранение неполадок @@ -1521,99 +1638,75 @@ https://kv.test.fastnear.com **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) -## Быстрый старт - -Если точные FastData-ключи уже известны, читайте их напрямую. - -```bash -KV_BASE_URL=https://kv.test.fastnear.com -CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=kv.gork-agent.testnet - -curl -s "$KV_BASE_URL/v0/multi" \ - -H 'content-type: application/json' \ - --data '{ - "keys": [ - "kv.gork-agent.testnet/kv.gork-agent.testnet/key", - "kv.gork-agent.testnet/kv.gork-agent.testnet/value" - ] - }' \ - | jq '{ - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] - }' -``` - -Это самое короткое полезное чтение FastData на странице: один запрос и сразу две точные строки. - ## Готовое расследование -### Прочитать одну индексированную настройку и посмотреть её историю +### Посмотреть индексированные записи одного `predecessor_id`, а затем сузиться до ключа, который изменился -Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки и менялось ли оно раньше?» +Используйте это расследование, когда сначала известен предшественник, а настоящий вопрос звучит так: «что вообще записал этот `predecessor_id`, какая строка здесь действительно интересна и что потом происходило с этим ключом?» Стратегия - Сначала читайте точные строки настройки, расширяйтесь до метаданных предшественника только если нужна provenance-цепочка, и переходите к Transactions API только для финального доказательства. + Начните с области по `predecessor_id`, переходите к точному ключу только после того, как он заслужил внимание, а RPC оставляйте на самый конец. - 01multi или get-latest-key читают точные индексированные строки настройки. - 02get-history-key показывает, менялось ли это индексированное значение позже. - 03Только если важна provenance-цепочка, latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки. + 01all-by-predecessor даёт последние индексированные строки для одного `predecessor_id` по затронутым контрактам. + 02get-history-key или history-by-predecessor объясняют, как менялась интересующая строка во времени. + 03RPC view_state — это уже необязательное точное чтение, когда нужен именно канонический current state, а не индексированная история. **Цель** -- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта и подтвердить историю точного ключа для одной строки. +- Объяснить, что записал этот `predecessor_id`, какой точный ключ стал настоящим фокусом, как он менялся и нужен ли вам вообще финальный `view_state`. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Чтение точной настройки | KV FastData [`multi`](https://docs.fastnear.com/ru/fastdata/kv/multi) | Читаем известные строки `key` и `value` одним запросом | Это самое узкое полезное чтение, когда точные индексированные строки настройки уже известны | -| Чтение точной строки | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Повторно читаем одну точную строку по path-маршруту | Полезно, когда вопрос только про одну строку, а не про всю пару настройки | -| История точного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) | Проверяем историю точной строки `value` | Показывает, менялось ли именно это индексированное значение в нескольких записях | -| Необязательный мост к provenance | KV FastData [`latest-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) | Восстанавливаем `tx_hash` и `receipt_id` для индексированных строк только если provenance действительно важна | Это необязательный мост от индексированных строк обратно к одной записи | -| Необязательная гидратация транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем исходные args только когда нужно это доказательство | Финальное необязательное доказательство того, что обе строки создал один вызов | +| Последние индексированные строки по области | KV FastData [`all-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) | Сначала получаем текущие индексированные записи одного `predecessor_id` по затронутым контрактам | Отвечает на вопрос по области раньше, чем вы начинаете притворяться, будто точный ключ уже известен | +| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Забираем историю точного ключа или ещё на один шаг оставляем историю шире, на уровне предшественника | Показывает, стабильно ли интересующее значение, насколько оно недавнее и не входит ли в более широкий паттерн записей | +| Точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | **Что должен включать полезный ответ** -- какие именно `current_account_id`, `predecessor_id` и индексированные строки настройки исследовались -- как выглядят последние индексированные строки и история точного ключа для одной из них -- какой `tx_hash` или `receipt_id` создал эти строки, только если важна provenance-цепочка -- остаётся ли вопрос про индексированные строки FastData или уже перешёл к каноническому состоянию контракта +- с какой области по `predecessor_id` вы начали +- какой точный ключ стал настоящим фокусом +- как этот ключ менялся в истории +- нужен ли вообще финальный `view_state` -### Проверенный read-only testnet shell-сценарий +### Shell-сценарий по области предшественника -Используйте этот сценарий, когда нужен полностью read-only пример на стабильных sample-данных в `kv.gork-agent.testnet`. - -Этот минимальный контракт ведёт себя как крошечное хранилище настроек: одна запись эмитирует две индексированные строки, `key` и `value`. Сейчас sample-настройка выглядит как `test=hello`, и этого достаточно, чтобы показать shape FastData без притворства, будто перед нами более богатый прикладной объект. -Этот sample-контракт индексирует собственные записи, поэтому в этом walkthrough `CURRENT_ACCOUNT_ID` и `PREDECESSOR_ID` намеренно совпадают. +Используйте этот сценарий, когда сначала известен один `predecessor_id` и нужно аккуратно перейти от вопроса «что он вообще записал?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» **Что вы делаете** -- Читаете точные индексированные строки настройки вместе. -- Повторно читаете те же строки по отдельности, чтобы был понятен shape exact-key маршрута. -- Забираете историю точного ключа для строки `value` этой настройки. -- Останавливаетесь на этом, если provenance дальше не нужна. +- Читаете последние индексированные строки для одного `predecessor_id` по затронутым контрактам. +- Поднимаете интересующие `current_account_id` и точный `key` через `jq`. +- Переиспользуете эти значения в документированном маршруте истории по точному ключу. +- Только после этого решаете, нужен ли вам `view_state` для канонического current state. ```bash -KV_BASE_URL=https://kv.test.fastnear.com -TX_BASE_URL=https://tx.test.fastnear.com -CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=kv.gork-agent.testnet +KV_BASE_URL=https://kv.main.fastnear.com +PREDECESSOR_ID=james.near -curl -s "$KV_BASE_URL/v0/multi" \ +curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ -H 'content-type: application/json' \ - --data '{ - "keys": [ - "kv.gork-agent.testnet/kv.gork-agent.testnet/key", - "kv.gork-agent.testnet/kv.gork-agent.testnet/value" - ] - }' \ + --data '{"include_metadata":true,"limit":10}' \ + | tee /tmp/kv-predecessor.json >/dev/null + +jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ] +}' /tmp/kv-predecessor.json + +CURRENT_ACCOUNT_ID="$(jq -r '.entries[0].current_account_id' /tmp/kv-predecessor.json)" +EXACT_KEY="$(jq -r '.entries[0].key' /tmp/kv-predecessor.json)" +ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" + +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ | jq '{ entries: [ .entries[] @@ -1626,113 +1719,101 @@ curl -s "$KV_BASE_URL/v0/multi" \ } ] }' +``` -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ - | jq '{ - latest_key_row: ( - .entries[0] - | { - block_height, - key, - value - } - ) - }' +**Зачем нужен следующий шаг?** -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ - | jq '{ - latest_value_row: ( - .entries[0] - | { - block_height, - key, - value - } - ) - }' +Первый запрос отвечает на вопрос по области: «что этот `predecessor_id` сейчас пишет?». Сужение из этой ленты до одного точного ключа отвечает на более точный вопрос: «как именно эта строка дошла до такого состояния?». Если картина всё ещё шире одного ключа, ещё немного побудьте на [History by Predecessor](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), а уже потом переходите к точной истории ключа или RPC. -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ - | jq '{ - page_token, - entries: [ - .entries[] - | { - block_height, - key, - value - } - ] - }' -``` +## Частые задачи -На этом основной read-path заканчивается: точные строки, их exact latest-чтение и история точного ключа для той же индексированной настройки. +### Начать с записей одного `predecessor_id` -### Необязательное расширение до provenance +**Начните здесь** -Переходите сюда только тогда, когда следующий вопрос уже звучит как «какая запись создала эти строки?» +- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor), когда вы знаете, кто писал строки, но ещё не знаете, какой точный ключ важнее всего. -```bash +**Следующая страница при необходимости** -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ - -H 'content-type: application/json' \ - --data '{"include_metadata": true, "limit": 10}' \ - | tee /tmp/kv-predecessor-latest.json >/dev/null +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если одна строка становится настоящим фокусом. +- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), если более широкая картина записей всё ещё важнее точного ключа. -jq '{ - entries: [ - .entries[] - | { - block_height, - key, - value, - tx_hash, - receipt_id - } - ] -}' /tmp/kv-predecessor-latest.json +**Остановитесь, когда** -INDEXED_TX_HASH="$( - jq -r ' - first(.entries[] | select(.key == "value") | .tx_hash) - ' /tmp/kv-predecessor-latest.json -)" +- Уже можно объяснить, что писал этот `predecessor_id`, и заслуживает ли одна строка более глубокой истории. -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$INDEXED_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - args: ( - .transactions[0].transaction.actions[0].FunctionCall.args - | @base64d - | fromjson - ), - receipt_ids: .transactions[0].execution_outcome.outcome.receipt_ids - }' -``` +**Переходите дальше, когда** -**Зачем нужен следующий шаг?** +- Пользователю нужно каноническое текущее состояние в цепочке, а не индексированная история записей. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). + +### Превратить один точный ключ в историю изменений + +**Начните здесь** + +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для поиска истории по пути. +- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. + +**Следующая страница при необходимости** + +- Возвращайтесь к [Последнему по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. + +**Остановитесь, когда** + +- Уже можно объяснить, как ключ менялся со временем. + +**Переходите дальше, когда** + +- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. + +### Проследить записи от одного `predecessor_id` + +**Начните здесь** + +- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. +- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. + +**Следующая страница при необходимости** + +- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. + +**Остановитесь, когда** + +- Уже можно ответить, что именно этот предшественник изменил и где. -Этот sample-контракт эмитирует две индексированные строки из одной записи: `key=test` и `value=hello`. Рассматривайте их как одну индексированную настройку. Exact-key маршруты напрямую доказывают эти строки. Lookup по предшественнику с метаданными — это необязательный мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, которые создали эти строки. Гидратация транзакции доказывает, что эти индексированные строки произошли из одного вызова `__fastdata_kv` с декодированными args `{ "key": "test", "value": "hello" }`. +**Переходите дальше, когда** -Именно здесь проходит важная граница этой поверхности: KV FastData отвечает на вопросы про индексированные строки FastData. Если вопрос меняется на каноническое состояние контракта, переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную layout-структуру хранилища. +- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. + +### Пакетно проверить несколько известных ключей + +**Начните здесь** + +- [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. + +**Следующая страница при необходимости** + +- Переведите один интересный ключ в [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. + +**Остановитесь, когда** + +- Пакетный ответ уже показывает, какие ключи действительно важны. + +**Переходите дальше, когда** + +- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. ## Частые ошибки -- Начинать с широких выборок по предшественнику, когда точные строки FastData уже известны. -- Считать [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) тем же самым, что и [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). Первый маршрут глобальный по строке ключа, второй остаётся внутри одного контракта и predecessor. -- Использовать KV FastData, когда настоящий вопрос про балансы, holdings или account summaries. -- Путать индексированные строки FastData с каноническим on-chain-состоянием контракта. -- Предполагать, что для каждого FastData-расследования сначала обязательно нужна новая запись. +- Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. +- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. +- Путать индексированную историю с точным текущим состоянием в цепочке. +- Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. ## Полезные связанные страницы - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv) -- [Transactions API](https://docs.fastnear.com/ru/tx) - [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [FastNear API](https://docs.fastnear.com/ru/api) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) @@ -2009,7 +2090,7 @@ Playwright, relevance scoring и более тяжёлые редакционн # NEAR Data API -NEAR Data API — это поверхность для чтения данных почти в реальном времени, а также по семействам блоков. Используйте её, когда нужны свежие срезы блоков, вспомогательные маршруты с перенаправлением или недавние финализированные и оптимистичные чтения, но без позиционирования продукта как потокового сервиса. +NEAR Data API — это поверхность для недавних блоков и шардов. Используйте её, когда нужны свежие срезы блоков, мониторинг активности контракта, вспомогательные маршруты с перенаправлением или сравнение оптимистичных и финализированных чтений без превращения продукта в потоковый API. ## Базовые URL @@ -2024,8 +2105,9 @@ https://testnet.neardata.xyz ## Лучше всего подходит для - опроса недавних финализированных и оптимистичных блоков; -- вспомогательных маршрутов по блокам и сценариев с перенаправлением; -- лёгких проверок свежести данных и мониторинга. +- обнаружения того, появился ли живой контракт в недавнем блоке и изменил ли он состояние; +- сравнения оптимистичного сигнала с финализированным подтверждением; +- проверки одного недавнего шарда, когда уже известно, какой блок важен. ## Когда его не стоит использовать @@ -2040,13 +2122,14 @@ https://testnet.neardata.xyz ## С чего обычно начинают -- [Оптимистичный блок](https://docs.fastnear.com/ru/neardata/block-optimistic) — для самого свежего опроса блоков. -- [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block) и [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers) — для запросов по финализированным блокам. -- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) и [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) — когда нужны вспомогательные маршруты с перенаправлением. +- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) и [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) — когда нужно быстро узнать самый новый недавний блок. +- [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block) — когда нужен один недавний гидратированный блок с уже приложенными данными по шардам. +- [Шард блока](https://docs.fastnear.com/ru/neardata/block-shard) — когда недавний блок уже показал нужный шард и его надо разобрать глубже. +- [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers) — когда важнее движение головы цепочки и финальности, а не широкий блоковый документ. ## Нужен сценарий? -Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для простых пошаговых сценариев: опроса оптимистичных блоков, подтверждения финализированных блоков, работы с перенаправлениями и перехода к каноническому RPC. +Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для компактных сценариев: обнаружения активности контракта, сравнения оптимистичных и финализированных наблюдений, а также проверки изменений на уровне шарда. ## Устранение неполадок @@ -2071,91 +2154,61 @@ https://testnet.neardata.xyz **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) -## Быстрый старт - -Начните с одного недавнего финализированного блока и сначала запросите самую маленькую возможную touch-сводку. - -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=intents.near - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | jq --arg target "$TARGET_ACCOUNT_ID" '{ - height: .block.header.height, - hash: .block.header.hash, - direct_tx_count: ([.shards[].chunk.transactions[]? - | select(.transaction.receiver_id == $target)] | length), - incoming_receipt_count: ([.shards[].chunk.receipts[]? - | select(.receiver_id == $target)] | length), - outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? - | select( - (.receipt.receiver_id // "") == $target - or (.execution_outcome.outcome.executor_id // "") == $target - )] | length), - state_change_count: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target)] | length), - state_change_types: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target) - | .type] | unique | sort) - } | . + { - touched: ( - (.direct_tx_count > 0) - or (.incoming_receipt_count > 0) - or (.outcome_hit_count > 0) - or (.state_change_count > 0) - ) - }' -``` +NEAR Data особенно хороша там, где вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. -Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. Здесь закреплён `intents.near`, чтобы первый запуск с высокой вероятностью сразу вернул реальный touched-блок, а уже потом вы сможете подставить свой контракт. - -Блоки NEAR шардингованы, поэтому фильтр проходит по `.shards[]`, прежде чем смотреть транзакции, receipts, outcomes или изменения состояния. `chunk.receipts` означает работу, которая приземлилась в этом блоке; `receipt_execution_outcomes` означает работу, которая исполнилась в этом блоке, даже если была запланирована раньше. - -## Готовое расследование +## Готовые расследования ### Был ли мой контракт затронут в последнем финализированном блоке? -Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC. +Используйте это, когда приложению, боту или инструменту поддержки нужен один быстрый ответ о живом контракте. Мы будем проверять `intents.near`, но та же сводка работает для любого аккаунта контракта. Стратегия - Сначала ответьте на вопрос о contract touch, а затем оставьте только один tx hash или receipt id для следующего шага. - - 01last-block-final даёт одну стабильную высоту блока без угадывания. - 02block — это главный read: он уже содержит транзакции, входящие receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» - 03Только если ответ «да», расширяйтесь дальше: сохраните один точный tx hash или receipt id из того же сохранённого блока, а затем передайте этот идентификатор в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). + Сначала дайте NEAR Data ответить на задачу мониторинга, а уже потом сохраняйте tx hash или receipt ID для следующей поверхности, если это вообще понадобится. -**Цель** - -- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только компактные счётчики плюс один точный идентификатор для следующего шага. - -**Что должен включать полезный ответ** - -- финализированную высоту и хеш -- ответ “затронут / не затронут” -- счётчики прямых транзакций, входящих receipts, outcome-hit и state changes -- компактный список `state_change_types` -- один sample tx hash или receipt id, когда он есть - -### Shell-сценарий от финализированного блока к ответу по contract touch - -Используйте этот сценарий, когда целевой аккаунт уже известен и нужен один свежий финализированный ответ, а не длинный polling-цикл. - -**Что вы делаете** + 01last-block-final находит самую новую финализированную высоту. + 02block даёт один недавний гидратированный документ блока с уже присоединёнными данными по shard. + 03Суммируйте прямые транзакции, входящие receipts, результаты выполнения и state_changes для нужного контракта. Считайте state_changes самым сильным сигналом того, что контракт действительно изменился. -- Получаете redirect target для последнего финализированного блока. -- Один раз загружаете полный документ блока. -- Собираете один компактный ответ по одному `TARGET_ACCOUNT_ID`. -- Получаете ответ “да / нет” плюс минимально полезные счётчики, типы изменений состояния и sample-идентификаторы. +Такой сценарий вполне честно может вернуть `touched: false`, если блок тихий. Это тоже полезный ответ: в самом новом финализированном блоке сейчас нет ничего, что требовало бы более глубокого разбора. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=intents.near +TARGET_CONTRACT=intents.near + +contract_touch_summary() { + jq -r --arg contract "$1" ' + [ .shards[] | { + shard_id, + direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length), + sample_tx_hash: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash] | .[0]), + sample_receipt_id: ( + [ .chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id ] + + [ .receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract) | .execution_outcome.id ] + + [ .state_changes[]? | select(.change.account_id? == $contract) | (.cause.receipt_hash? // empty) ] + | .[0] + ) + } + | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) + ] as $rows + | { + height: .block.header.height, + hash: .block.header.hash, + contract: $contract, + touched: (($rows | length) > 0), + shards: ($rows | map(.shard_id)), + evidence: { + direct_txs: (($rows | map(.direct_txs) | add) // 0), + incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), + execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), + state_changes: (($rows | map(.state_changes) | add) // 0) + }, + sample_tx_hash: ([ $rows[] | .sample_tx_hash | select(.) ] | .[0]), + sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) + }' +} FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -2163,199 +2216,156 @@ FINAL_LOCATION="$( | tr -d '\r' )" -printf 'Final redirect target: %s\n' "$FINAL_LOCATION" +printf 'Latest finalized block: %s\n' "$FINAL_LOCATION" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-block.json >/dev/null - -jq --arg target "$TARGET_ACCOUNT_ID" ' - ( - [ - .shards[] - | .chunk.transactions[]? - | select(.transaction.receiver_id == $target) - | .transaction.hash - ] - ) as $txs - | ( - [ - .shards[] - | .chunk.receipts[]? - | select(.receiver_id == $target) - | .receipt_id - ] - ) as $receipts - | ( - [ - .shards[] - | .receipt_execution_outcomes[]? - | select( - (.receipt.receiver_id // "") == $target - or (.execution_outcome.outcome.executor_id // "") == $target - ) - | .tx_hash - | select(. != null) - ] - | unique - ) as $outcomes - | ( - [ - .shards[] - | .state_changes[]? - | select((.change.account_id // "") == $target) - | .type - ] - ) as $state_changes - | ( - $state_changes - | unique - | sort - ) as $state_change_types - | { - height: .block.header.height, - hash: .block.header.hash, - touched: ( - ($txs | length) > 0 - or ($receipts | length) > 0 - or ($outcomes | length) > 0 - or ($state_changes | length) > 0 - ), - direct_tx_count: ($txs | length), - incoming_receipt_count: ($receipts | length), - outcome_hit_count: ($outcomes | length), - state_change_count: ($state_changes | length), - state_change_types: $state_change_types, - sample_direct_tx: ($txs[0] // null), - sample_incoming_receipt: ($receipts[0] // null), - sample_outcome_tx_hash: ($outcomes[0] // null) - } -' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json + | tee /tmp/neardata-final-block.json \ + | contract_touch_summary "$TARGET_CONTRACT" ``` -Если позже понадобятся более богатые списки, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. - -Типичные `state_change_types` — это `account_update`, `access_key_update`, `data_update` и соответствующие варианты `*_deletion`. Этого часто достаточно, чтобы ещё внутри NEAR Data понять, смотрите ли вы на запись в storage, churn ключей или более широкое изменение аккаунта. - -#### Необязательное продолжение: Какой tx hash или receipt id разбирать дальше? - -Используйте ту же сохранённую сводку и поднимите один точный идентификатор для следующей поверхности. - -```bash -FOLLOW_UP_KIND="$( - jq -r ' - if .sample_direct_tx != null then "tx_hash" - elif .sample_incoming_receipt != null then "receipt_id" - elif .sample_outcome_tx_hash != null then "tx_hash" - else "none" - end - ' /tmp/neardata-touch-summary.json -)" - -FOLLOW_UP_VALUE="$( - jq -r ' - .sample_direct_tx - // .sample_incoming_receipt - // .sample_outcome_tx_hash - // empty - ' /tmp/neardata-touch-summary.json -)" +Читать ответ стоит так: -printf 'Next identifier kind: %s\n' "$FOLLOW_UP_KIND" -printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" -``` +- `touched: false` означает, что самый новый финализированный блок не упомянул и не изменил контракт ни одним из отслеживаемых способов. +- `sample_tx_hash` означает, что у вас уже есть хороший якорь для следующего шага на `/tx`. +- `sample_receipt_id` без tx hash обычно означает, что контракт появился в цепочке через receipts, и NEAR Data уже сэкономила вам более дешёвый этап мониторинга. -Если идентификатор — это `tx_hash`, передайте его в [Transactions API](https://docs.fastnear.com/ru/tx) или RPC `tx` status. Если это `receipt_id`, передайте его в [Transactions API: Receipt by ID](https://docs.fastnear.com/ru/tx/receipt). И только после этого решайте, нужен ли вам вообще shard-level follow-up. +### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? -**Зачем нужен следующий шаг?** +Используйте это, когда нужен ранний сигнал по живому контракту, но стабильный ответ всё равно должен пройти через финализированное подтверждение. -Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один точный tx hash или receipt id уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. + Стратегия + Используйте один и тот же словарь contract-touch на обеих поверхностях, чтобы сравнение было честным. -### Насколько optimistic head опережает final прямо сейчас? + 01last-block-optimistic находит самую новую optimistic-высоту. + 02block-optimistic показывает ранний сигнал для того же контракта. + 03block на той же высоте либо подтверждает то же наблюдение, либо показывает, что finality ещё не догнала. -Используйте это, когда нужно выбрать между low-latency read и settled read ещё до запуска polling. +Если finality уже догнала, optimistic- и finalized-сводки могут совпасть сразу. Это тоже полезно: ранний сигнал уже попал в стабильную историю. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + +contract_touch_summary() { + jq -r --arg contract "$1" ' + [ .shards[] | { + shard_id, + direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) + } + | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) + ] as $rows + | { + height: .block.header.height, + hash: .block.header.hash, + contract: $contract, + touched: (($rows | length) > 0), + shards: ($rows | map(.shard_id)), + evidence: { + direct_txs: (($rows | map(.direct_txs) | add) // 0), + incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), + execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), + state_changes: (($rows | map(.state_changes) | add) // 0) + } + }' +} -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -OPTIMISTIC_LOCATION="$( +OPT_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' \ | tr -d '\r' )" -jq -n \ - --arg final_location "$FINAL_LOCATION" \ - --arg optimistic_location "$OPTIMISTIC_LOCATION" '{ - final_location: $final_location, - optimistic_location: $optimistic_location, - final_height: ($final_location | split("/") | last | tonumber), - optimistic_height: ($optimistic_location | split("/") | last | tonumber) - } | . + { - optimistic_minus_final: (.optimistic_height - .final_height) - }' +OPT_HEIGHT="${OPT_LOCATION##*/}" + +printf 'Latest optimistic block: %s\n' "$OPT_LOCATION" + +curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" \ + | tee /tmp/neardata-optimistic-block.json \ + | contract_touch_summary "$TARGET_CONTRACT" + +curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT" \ + | tee /tmp/neardata-final-same-height.json >/dev/null + +if jq -e 'type == "null"' /tmp/neardata-final-same-height.json >/dev/null; then + printf 'Finalized block %s is not available yet; finality has not caught up.\n' "$OPT_HEIGHT" +else + printf 'Finalized block %s is already available; compare the stable answer below.\n' "$OPT_HEIGHT" + contract_touch_summary "$TARGET_CONTRACT" < /tmp/neardata-final-same-height.json +fi ``` -Используйте `last_block/optimistic`, когда приложению важнее скорость, чем settled finality, например для реактивных status-view или ранних алертов. Используйте `last_block/final`, когда ответ пойдёт в accounting, reconciliation или любой workflow, который не должен откатываться назад. +Практический вывод такой: -### Как идти вперёд блок за блоком? +- optimistic — это ранний сигнал, на который цикл мониторинга может быстро отреагировать; +- finalized — это стабильный ответ, который уже можно показывать пользователям или использовать в устойчивой автоматизации. -Используйте этот шаблон, когда задача звучит как «начать с высоты N, получить блок, обработать его, увеличить высоту и повторить». Для детерминированной стартовой точки один раз прочитайте [`first-block`](https://docs.fastnear.com/ru/neardata/first-block), а затем идите вперёд. +### Какой shard действительно изменил мой контракт в этом блоке? -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz +Используйте это, когда недавний блок уже показал активность контракта, и теперь нужно доказательство на уровне shard того, где именно изменение реально приземлилось. -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" + Стратегия + Сначала используйте весь блок, чтобы найти нужный shard, а затем дайте block-shard доказать само изменение. -FINAL_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | awk -F/ '{print $4}')" -NEXT_HEIGHT=$((FINAL_HEIGHT + 1)) + 01Просканируйте список shard внутри финализированного блока и найдите state_changes по вашему контракту. + 02Откройте только тот shard, который действительно изменил контракт. + 03Сохраните совпадающие state_changes и нужные результаты исполнения как доказательство на уровне shard. -while true; do - HTTP_CODE="$( - curl -s -o /tmp/neardata-next-block.json -w '%{http_code}' \ - "$NEARDATA_BASE_URL/v0/block/$NEXT_HEIGHT" - )" +На момент написания недавний финализированный блок `194727131` дал чистый живой пример для `intents.near`: контракт сначала появился как входящий receipt на shard `8`, а затем действительно выполнился и изменил состояние на shard `7`. - if [ "$HTTP_CODE" = "200" ]; then - jq '{height: .block.header.height, hash: .block.header.hash}' \ - /tmp/neardata-next-block.json - NEXT_HEIGHT=$((NEXT_HEIGHT + 1)) - elif [ "$HTTP_CODE" = "404" ]; then - sleep 2 - else - printf 'Unexpected status: %s\n' "$HTTP_CODE" >&2 - break - fi -done -``` +Если для вашей задачи нужен более свежий блок, переиспользуйте ту же сводку из первого примера на нескольких соседних финализированных высотах, а затем подставьте найденную высоту в тот же вызов `block-shard`. -Это каноническая форма polling для финализированных данных: получить блок по высоте, обработать один блок, перейти к следующему и трактовать `404` как «ещё не финализирован, подождите и повторите». Если нужен тот же цикл на optimistic-скорости, переключитесь на `/v0/block_opt/` и примите optimistic semantics вместо final. +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near +EXAMPLE_HEIGHT=194727131 + +curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT" \ + | tee /tmp/neardata-block-194727131.json \ + | jq --arg contract "$TARGET_CONTRACT" '[ + .shards[] | { + shard_id, + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) + } + | select(.incoming_receipts + .execution_outcomes + .state_changes > 0) + ]' + +curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT/shard/7" \ + | jq --arg contract "$TARGET_CONTRACT" '{ + shard_id, + chunk_hash: .chunk.header.chunk_hash, + matching_state_changes: [ + .state_changes[] + | select(.change.account_id? == $contract) + | {type, cause, account_id: .change.account_id} + ][0:2], + matching_execution_outcomes: [ + .receipt_execution_outcomes[] + | select(.execution_outcome.outcome.executor_id == $contract) + | { + receipt_id: .execution_outcome.id, + executor_id: .execution_outcome.outcome.executor_id, + status: .execution_outcome.outcome.status, + predecessor_id: .receipt.predecessor_id + } + ][0:2] + }' +``` -## Частые ошибки +Практическое правило здесь простое: -- Воспринимать NEAR Data как push-стрим, а не как polling- или point-read API. -- Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. -- Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. -- Использовать optimistic-данные для settled accounting или reconciliation. -- Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. -- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data один точный tx hash или receipt id. +- используйте `block`, когда первый вопрос звучит как «какой shard вообще важен?»; +- используйте `block-shard`, когда настоящий вопрос уже стал таким: «покажи мне сам payload shard, который изменил состояние». -## Полезные связанные страницы +## Когда пора расширять поверхность -- [NEAR Data API](https://docs.fastnear.com/ru/neardata) -- [Transactions API](https://docs.fastnear.com/ru/tx) -- [RPC Reference](https://docs.fastnear.com/ru/rpc) -- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) -- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +- Используйте [Transactions API](https://docs.fastnear.com/ru/tx), когда у вас уже есть `tx_hash` и нужен человекочитаемый рассказ о транзакции. +- Используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. +- Используйте [Block Headers](https://docs.fastnear.com/ru/neardata/block-headers), когда нужна только динамика head/finality, а не проверка contract-touch. --- @@ -2516,65 +2526,93 @@ https://archival-rpc.testnet.fastnear.com # Примеры RPC -Используйте эту страницу, когда нужен быстрый точный ответ через RPC. Начните с одного чтения, а к транзакциям и сырому состоянию переходите только если простого запроса уже недостаточно. +Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. -## Быстрый старт +## Отправка и отслеживание транзакции -Если вы только открыли эту страницу, начните с одного точного чтения аккаунта. +Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=near +### Отправить транзакцию и затем проследить её от хеша до финального исполнения -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - finality: "final", - account_id: $account_id - } - }')" \ - | jq '.result | { - amount, - locked, - code_hash, - storage_usage - }' -``` +Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. -Это самый маленький надёжный RPC-пример на странице: один запрос, один точный ответ, без дерева receipts. +Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: -## Отправка и отслеживание транзакции +- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- signer: `mike.near` +- receiver: `social.near` +- высота блока включения: `79574923` +- высота блока исполнения receipt для записи в SocialDB: `79574924` -### Двухчастный паттерн: отправить транзакцию или отследить уже известный tx hash до финального исполнения +Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. -Базовый паттерн: + Стратегия + Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. -- `broadcast_tx_async` для отправки -- `tx` с `wait_until: "FINAL"` для отслеживания -- `EXPERIMENTAL_tx_status` только если следующий вопрос уже про receipts + 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. + 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. + 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. -Этот walkthrough намеренно разбит на две части: +**Что вы здесь решаете** -- отправить новую подписанную транзакцию и сохранить возвращённый хеш -- отследить один известный исторический tx hash с воспроизводимым выводом +- какой эндпоинт отправки брать первым +- что опрашивать после того, как у вас появился tx hash +- как `wait_until` связан с included-, optimistic- и final-гарантиями +- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` -Для части про отслеживание используется одна зафиксированная историческая транзакция, поэтому status-lookup идёт через архивный хост: +```mermaid +flowchart LR + S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] + A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] + T --> F["Транзакция полностью завершена"] + T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] + F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] +``` -- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- signer: `mike.near` -- receiver: `social.near` -- `https://archival-rpc.mainnet.fastnear.com` +| Метод | Когда использовать | Что вернётся | Роль здесь | +| --- | --- | --- | --- | +| [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | +| [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | +| [`broadcast_tx_commit`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | +| [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | +| [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | + +**Карта статусов и ожидания** + +Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. + +| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | +| --- | --- | --- | +| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | +| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | +| `INCLUDED_FINAL` | блок включения уже финален | `tx` | +| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | +| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | + +Практическое различие очень простое: + +- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash +- используйте `tx` как обычный цикл опроса +- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу + +**Что вы делаете** -1. Отправьте новую подписанную транзакцию и сохраните возвращённый хеш. +- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. +- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. +- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. +- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. ```bash RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near +RECEIVER_ID=social.near +``` + +1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. +```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data '{ @@ -2586,16 +2624,40 @@ curl -s "$RPC_URL" \ | jq . ``` -Этот первый шаг нужен только для формы отправки. Именно возвращённый хеш вы потом будете отслеживать для своей живой транзакции. +В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. -2. Отслеживайте один известный tx hash, пока не получите самый простой финальный ответ. +2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. ```bash -RPC_URL=https://archival-rpc.mainnet.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "INCLUDED_FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' ``` +Что здесь важно заметить: + +- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality +- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения +- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt + +3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. + ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ @@ -2613,12 +2675,18 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - transaction_status: .result.status, + status: .result.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -3. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда для этого известного tx уже нужен уровень receipts. +Что здесь важно заметить: + +- для исторической tx этот вызов тоже возвращается сразу +- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» +- для многих приложений именно здесь и стоит остановиться + +4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. ```bash curl -s "$RPC_URL" \ @@ -2637,49 +2705,82 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, + status: .result.status, transaction_handoff: .result.transaction_outcome.outcome.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Если вы хотите, чтобы узел ждал за вас, используйте [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Но базовый паттерн на этой странице остаётся таким: отправка через `broadcast_tx_async`, затем отслеживание хеша через `tx`. - -## Механика аккаунтов и ключей - -Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. - -### Может ли этот access key прямо сейчас вызвать этот контракт? +Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». -Используйте этот сценарий, когда у вас уже есть аккаунт, один public key и целевой контракт, а вам нужен простой ответ да или нет до того, как вы начнёте что-то подписывать. +5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. - Стратегия - Сначала отфильтруйте ключи аккаунта, затем прочитайте точный ключ и только потом классифицируйте его права. +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transaction_handoff: .transactions[0].transaction_outcome.outcome.status + }' +``` + +Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» + +**Рекомендуемый паттерн** + +- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. +- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. +- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. + +## Механика аккаунтов и ключей + +Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. - 01RPC view_access_key_list сужает список до ключей, которые вообще могут относиться к целевому контракту. - 02RPC view_access_key даёт точный permission-object для того public key, которым вы реально можете подписывать. - 03jq превращает этот permission-object в full_access, function_call_match, receiver_mismatch или method_not_allowed. +### Проверить и удалить старые function-call-ключи Near Social + +Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. + + Стратегия + Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. + + 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. + 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. + 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. **Что вы делаете** -- Получаете access key аккаунта и сужаете список до нужного контракта. -- Точно проверяете тот ключ, которым собираетесь подписывать. -- Решаете, может ли он вызвать этот receiver и method, не выходя за пределы RPC. +- Через сам RPC получаете полный список access key аккаунта. +- Сужаете этот список до function-call-ключей, привязанных к `social.near`. +- Точно проверяете один выбранный ключ перед удалением. +- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -TARGET_CONTRACT_ID=crossword.puzzle.near -TARGET_METHOD_NAME=new_puzzle -TARGET_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_WANT_TO_CHECK' +Сразу важны два ограничения: + +- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. +- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». -# Пример живых значений, проверенных 19 апреля 2026 года: -# ACCOUNT_ID=mike.near -# TARGET_CONTRACT_ID=crossword.puzzle.near -# TARGET_METHOD_NAME=new_puzzle -# TARGET_PUBLIC_KEY='ed25519:otwaB1X88ocpmUdC1B5XaifucfDLmLKaonb26KqTj96' +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export SOCIAL_RECEIVER_ID=social.near +export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' +export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' +export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' ``` -1. Получите ключи аккаунта и сузьте их до целевого контракта. +1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. ```bash curl -s "$RPC_URL" \ @@ -2694,28 +2795,246 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/access-key-list.json >/dev/null - -jq --arg target_contract_id "$TARGET_CONTRACT_ID" '{ - candidate_keys: [ - .result.keys[] - | select( - .access_key.permission == "FullAccess" - or ( - (.access_key.permission | type) == "object" - and .access_key.permission.FunctionCall.receiver_id == $target_contract_id - ) - ) - | { - public_key, - nonce: .access_key.nonce, - permission: .access_key.permission + | tee /tmp/fastnear-access-keys.json >/dev/null + +jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + nonce: .access_key.nonce, + receiver_id: .access_key.permission.FunctionCall.receiver_id, + method_names: .access_key.permission.FunctionCall.method_names, + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } +' /tmp/fastnear-access-keys.json +``` + +Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. + +2. Ещё раз проверьте конкретный ключ перед удалением. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$DELETE_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" } - ] -}' /tmp/access-key-list.json + }')" \ + | jq '{nonce: .result.nonce, permission: .result.permission}' +``` + +3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + is_function_call: true, + limit: 10 + }')" \ + | jq '{ + account_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_success + } + ] + }' +``` + +Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. + +4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. + +Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. + +```bash +SIGNED_TX_BASE64="$( + node --input-type=module <<'EOF' + +const { + ACCOUNT_ID, + NETWORK_ID = 'mainnet', + RPC_URL = 'https://rpc.mainnet.fastnear.com', + DELETE_PUBLIC_KEY, + FULL_ACCESS_PUBLIC_KEY, + FULL_ACCESS_PRIVATE_KEY, +} = process.env; + +for (const name of [ + 'ACCOUNT_ID', + 'DELETE_PUBLIC_KEY', + 'FULL_ACCESS_PUBLIC_KEY', + 'FULL_ACCESS_PRIVATE_KEY', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); +const derivedPublicKey = keyPair.getPublicKey().toString(); + +if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { + throw new Error( + `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: ACCOUNT_ID, + public_key: FULL_ACCESS_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const transaction = transactions.createTransaction( + ACCOUNT_ID, + utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), + ACCOUNT_ID, + BigInt(accessKey.nonce) + 1n, + [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +)" +``` + +5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. + +```bash +if curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' + .result.keys[] + | select(.public_key == $public_key) + ' >/dev/null; then + echo "Key is still present: $DELETE_PUBLIC_KEY" +else + echo "Key deleted: $DELETE_PUBLIC_KEY" +fi +``` + +**Зачем нужен следующий шаг?** + +Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. + +### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? + +Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. + + Стратегия + Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. + + 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. + 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. + 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. + +**Что вы делаете** + +- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. +- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. +- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. +- Подтягиваете кандидата по транзакциям и различаете три разных ключа: + - ключ, который был добавлен + - public key верхнеуровневого signer + - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` + +Сразу важны три детали про nonce: + +- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. +- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. +- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' + +# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: +# export ACCOUNT_ID=mike.near +# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' ``` -2. Прочитайте точное состояние того ключа, который хотите оценить. +1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. ```bash curl -s "$RPC_URL" \ @@ -2733,69 +3052,172 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/exact-access-key.json >/dev/null + | tee /tmp/key-origin-view.json >/dev/null + +CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" +ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" +SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" +SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" -jq '{nonce: .result.nonce, permission: .result.permission}' /tmp/exact-access-key.json +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_public_key "$TARGET_PUBLIC_KEY" \ + --argjson current_nonce "$CURRENT_NONCE" \ + --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ + --argjson search_from "$SEARCH_FROM" \ + --argjson search_to "$SEARCH_TO" \ + --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ + account_id: $account_id, + target_public_key: $target_public_key, + current_nonce: $current_nonce, + estimated_receipt_block: $estimated_receipt_block, + search_from_tx_block_height: $search_from, + search_to_tx_block_height: $search_to, + permission: ($permission | fromjson) + }' ``` -3. Превратите этот permission-object в ответ да или нет для этого контракта и метода. +Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. + +2. Ищите историю аккаунта только внутри этого диапазона блоков. ```bash -jq -n \ - --slurpfile key /tmp/exact-access-key.json \ - --arg target_contract_id "$TARGET_CONTRACT_ID" \ - --arg target_method_name "$TARGET_METHOD_NAME" ' - ($key[0].result.permission) as $permission - | if $permission == "FullAccess" then - { - can_call_now: true, - reason: "full_access" - } - elif $permission.FunctionCall.receiver_id != $target_contract_id then - { - can_call_now: false, - reason: "receiver_mismatch", - receiver_id: $permission.FunctionCall.receiver_id - } - elif ( - ($permission.FunctionCall.method_names | length) == 0 - or ($permission.FunctionCall.method_names | index($target_method_name)) - ) then - { - can_call_now: true, - reason: ( - if ($permission.FunctionCall.method_names | length) == 0 - then "function_call_any_method" - else "function_call_method_match" - end - ), - allowance: ($permission.FunctionCall.allowance // "unlimited") - } - else - { - can_call_now: false, - reason: "method_not_allowed", - allowed_methods: $permission.FunctionCall.method_names +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_tx_block_height "$SEARCH_FROM" \ + --argjson to_tx_block_height "$SEARCH_TO" '{ + account_id: $account_id, + is_real_signer: true, + from_tx_block_height: $from_tx_block_height, + to_tx_block_height: $to_tx_block_height, + desc: false, + limit: 50 + }')" \ + | tee /tmp/key-origin-candidates.json >/dev/null + +jq '{ + txs_count, + candidate_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_signer, + is_real_signer, + is_predecessor, + is_receiver } - end' + ] +}' /tmp/key-origin-candidates.json +``` + +Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. + +3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. + +```bash +TX_HASHES_JSON="$( + jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json +)" + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ + | tee /tmp/key-origin-transactions.json >/dev/null + +jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "direct", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $tx.transaction.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }), + ($tx.transaction.actions[]? + | .Delegate? + | .delegate_action as $delegate + | $delegate.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "delegated", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $delegate.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }) + ) + | { + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + tx_block_hash: $tx.execution_outcome.block_hash, + receiver_id: $tx.transaction.receiver_id + } + . +' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json +``` + +Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. + +Для примерного ключа `mike.near` выше совпадение оказывается делегированным: + +- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` +- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` +- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` +- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` + +4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. + +```bash +ADD_KEY_RECEIPT_ID="$( + jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | .receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) + | .receipt.receipt_id + ' /tmp/key-origin-transactions.json | head -n 1 +)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receipt_block_height: .receipt.block_height, + tx_block_height: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }' ``` -Для примерного ключа `mike.near` выше на 19 апреля 2026 года ответ получается `can_call_now: true`: это function-call-key для `crossword.puzzle.near`, а `method_names: ["new_puzzle"]` явно разрешает тот метод, который мы проверяем. +Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. **Зачем нужен следующий шаг?** -`view_access_key_list` — самый быстрый фильтр на уровне контракта. `view_access_key` — точная проверка полномочий для того public key, которым вы действительно хотите пользоваться. Если ответ `false`, вам нужен другой ключ или другая схема permissions, а не более глубокая историческая трассировка. +Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. -### Нужно ли этому получателю сначала зарегистрировать FT storage? +### Проверить регистрацию FT storage и затем перевести токены -Используйте этот сценарий, когда история звучит так: «я собираюсь отправить FT-токен и хочу получить простой ответ “нужен ли сначала `storage_deposit`?”». +Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». Стратегия - Сначала прочитайте storage-состояние получателя и остановитесь, как только станет понятно, может ли `ft_transfer` уже пройти. + Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. 02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит. - 03jq превращает эти два чтения в один ответ: «перевод уже может идти» или «сначала нужен `storage_deposit`». + 03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог. **Сеть** @@ -2806,19 +3228,24 @@ jq -n \ - [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) - [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) -В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Здесь важен именно read-only-ответ: нужен ли сначала `storage_deposit`, или путь перевода уже может продолжаться. +В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. **Что вы делаете** - Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. -- Получаете точный минимальный размер storage deposit на этом же контракте. -- Останавливаетесь, как только понимаете: `ft_transfer` уже может идти или сначала нужен `storage_deposit`. +- При необходимости получаете минимальный размер storage deposit. +- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. +- Подтверждаете баланс получателя тем же view-методом самого контракта. ```bash export NETWORK_ID=testnet export RPC_URL=https://rpc.testnet.fastnear.com export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet +export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' +export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' +export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' ``` 1. Проверьте, зарегистрирован ли получатель на FT-контракте. @@ -2854,7 +3281,7 @@ jq '{ }' /tmp/ft-storage-balance.json ``` -2. Получите минимальный storage deposit на этом же контракте. +2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. ```bash MIN_STORAGE_YOCTO="$( @@ -2879,111 +3306,334 @@ MIN_STORAGE_YOCTO="$( printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" ``` -3. Превратите эти два чтения в один ответ о готовности перевода. +3. Определите одну переиспользуемую функцию подписи для function-call к контракту. + +Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. ```bash -jq -n \ - --slurpfile balance /tmp/ft-storage-balance.json \ - --slurpfile bounds /tmp/ft-storage-bounds.json \ - --arg receiver_account_id "$RECEIVER_ACCOUNT_ID" ' - ( - $balance[0].result.result - | if length == 0 then null else (implode | fromjson) end - ) as $storage - | ( - $bounds[0].result.result - | implode - | fromjson - ) as $bounds - | { - receiver_account_id: $receiver_account_id, - receiver_registered: ($storage != null), - current_storage: $storage, - minimum_storage_deposit_yocto: $bounds.min, - next_step: ( - if $storage != null - then "получатель уже зарегистрирован; ft_transfer может продолжаться" - else "сначала отправьте storage_deposit, потом делайте ft_transfer" - end - ) - }' -``` +sign_function_call() { + METHOD_NAME="$1" \ + ARGS_JSON="$2" \ + DEPOSIT_YOCTO="$3" \ + GAS_TGAS="$4" \ + node --input-type=module <<'EOF' + +const { + NETWORK_ID = 'testnet', + RPC_URL = 'https://rpc.testnet.fastnear.com', + TOKEN_CONTRACT_ID, + SENDER_ACCOUNT_ID, + SENDER_PUBLIC_KEY, + SENDER_PRIVATE_KEY, + METHOD_NAME, + ARGS_JSON, + DEPOSIT_YOCTO = '0', + GAS_TGAS = '100', +} = process.env; + +for (const name of [ + 'TOKEN_CONTRACT_ID', + 'SENDER_ACCOUNT_ID', + 'SENDER_PUBLIC_KEY', + 'SENDER_PRIVATE_KEY', + 'METHOD_NAME', + 'ARGS_JSON', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} -**Зачем нужен следующий шаг?** +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} -Это чистый RPC-вопрос в этом workflow: «зарегистрирован ли уже получатель и какой минимальный депозит потребует контракт, если нет?» Подписанный write-path зависит уже от вашего wallet, CLI или backend-интеграции, поэтому в самый маленький core RPC-пример он не входит. +const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); +const signer = await InMemorySigner.fromKeyPair( + NETWORK_ID, + SENDER_ACCOUNT_ID, + keyPair +); -## Чтения контракта и сырое состояние +const derivedPublicKey = keyPair.getPublicKey().toString(); +if (derivedPublicKey !== SENDER_PUBLIC_KEY) { + throw new Error( + `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` + ); +} -Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: SENDER_ACCOUNT_ID, + public_key: SENDER_PUBLIC_KEY, + finality: 'final', +}); -### Прочитать счётчик прямо из состояния контракта, а потом подтвердить его через view-метод +const block = await rpc('block', { finality: 'final' }); -Используйте этот сценарий, когда вы уже знаете точное семейство storage-ключей и хотите увидеть самый короткий контраст между raw state и публичным методом чтения контракта. +const action = transactions.functionCall( + METHOD_NAME, + Buffer.from(ARGS_JSON), + BigInt(GAS_TGAS) * 10n ** 12n, + BigInt(DEPOSIT_YOCTO) +); -Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`: +const transaction = transactions.createTransaction( + SENDER_ACCOUNT_ID, + utils.PublicKey.fromString(SENDER_PUBLIC_KEY), + TOKEN_CONTRACT_ID, + BigInt(accessKey.nonce) + 1n, + [action], + utils.serialize.base_decode(block.header.hash) +); -- `view_state` читает сырой ключ `STATE` напрямую -- `call_function get_num` спрашивает у контракта то же текущее число +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + SENDER_ACCOUNT_ID, + NETWORK_ID +); -```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export CONTRACT_ID=counter.near-examples.testnet -export STATE_PREFIX_BASE64=U1RBVEU= +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +} ``` -1. Сначала прочитайте сырой ключ `STATE`. +4. При необходимости сначала зарегистрируйте storage для получателя. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$CONTRACT_ID" \ - --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ +if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then + SIGNED_TX_BASE64="$( + sign_function_call \ + storage_deposit \ + "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id, + registration_only: true + }')" \ + "$MIN_STORAGE_YOCTO" \ + 100 + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ jsonrpc: "2.0", id: "fastnear", - method: "query", + method: "send_tx", params: { - request_type: "view_state", - account_id: $account_id, - prefix_base64: $prefix_base64, - finality: "final" + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" } }')" \ - | tee /tmp/counter-view-state.json >/dev/null - -jq '{ - key: (.result.values[0].key | @base64d), - value_base64: .result.values[0].value -}' /tmp/counter-view-state.json + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash + }' +fi ``` -Здесь должен появиться `key: "STATE"`. Это и есть тот случай, когда `view_state` уместен: семейство ключей вам уже известно заранее. - -2. Декодируйте сырые байты. +5. После готовности storage переведите FT. ```bash -RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" - -python3 - "$RAW_VALUE_BASE64" <<'PY' - -raw = base64.b64decode(sys.argv[1]) -print(int.from_bytes(raw, "little", signed=True)) -PY -``` - -Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть гораздо менее дружелюбным: near-sdk-коллекции и Borsh-сериализованные структуры часто выводят storage-ключи из префиксов и внутренних схем ключей, поэтому `view_state` остаётся практичным только когда вы уже знаете точный layout, который хотите читать. Правило остаётся тем же: сначала байты, потом схема. - -3. Теперь спросите контракт привычным способом и сравните. +SIGNED_TX_BASE64="$( + sign_function_call \ + ft_transfer \ + "$(jq -nc \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ + receiver_id: $receiver_id, + amount: $amount, + memo: "FastNear RPC example" + }')" \ + 1 \ + 100 +)" -```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$CONTRACT_ID" '{ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ jsonrpc: "2.0", id: "fastnear", - method: "query", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Подтвердите FT-баланс получателя тем же view-методом контракта. + +```bash +RECEIVER_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "ft_balance_of", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + receiver_balance: (.result.result | implode | fromjson) + }' +``` + +**Зачем нужен следующий шаг?** + +Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. + +## Чтения контракта и сырое состояние + +Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» + +### Как прочитать сырое состояние контракта напрямую? + +Используйте этот сценарий, когда у контракта нет нужного view-метода или когда вам нужно проверить саму схему хранения, а не просто довериться ответу метода. + +В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: сначала вы читаете storage напрямую, а потом подтверждаете, что публичный view-метод контракта даёт тот же ответ: + +- `view_state` читает сырой ключ `STATE` прямо из storage контракта +- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API + + Стратегия + Сначала прочитайте storage напрямую, а уже потом дайте контракту подтвердить тот же ответ через view-метод. + + 01RPC view_state читает сырой ключ STATE, не запуская код контракта. + 02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта. + 03RPC call_function get_num — это удобная перепроверка того, что прямое чтение storage и view-метод по-прежнему дают один и тот же ответ. + +Здесь важнее ментальная модель, чем сам счётчик: + +- `view_state` — это прямое чтение storage из trie +- `call_function` исполняет read-only-метод контракта +- оба способа могут ответить на один и тот же вопрос, но делают разную работу + +```mermaid +flowchart LR + S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] + R --> D["Декодировать base64 + Borsh"] + D --> N["Знаковое значение счётчика"] + C["RPC call_function get_num"] --> J["JSON-результат метода"] + N --> X["Сравнить"] + J --> X + X --> A["Одно и то же текущее значение"] +``` + +**Что вы делаете** + +- Читаете сырой ключ `STATE` из storage контракта. +- Декодируете возвращённые байты в текущее знаковое значение счётчика. +- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export CONTRACT_ID=counter.near-examples.testnet +export STATE_PREFIX_BASE64=U1RBVEU= +``` + +1. Сначала прочитайте сырое состояние контракта. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$CONTRACT_ID" \ + --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_state", + account_id: $account_id, + prefix_base64: $prefix_base64, + finality: "final" + } + }')" \ + | tee /tmp/counter-view-state.json >/dev/null + +jq '{ + block_height: .result.block_height, + key_base64: .result.values[0].key, + value_base64: .result.values[0].value +}' /tmp/counter-view-state.json + +jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json +``` + +Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. + +2. Декодируйте байты значения в знаковое число счётчика. + +```bash +RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" + +python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . + +raw = base64.b64decode(sys.argv[1]) + +print(json.dumps({ + "value_base64": sys.argv[1], + "bytes": list(raw), + "hex": raw.hex(), + "signed_i8": int.from_bytes(raw, "little", signed=True), + "unsigned_u8": int.from_bytes(raw, "little", signed=False), +})) +PY +``` + +Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. + +Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. + +Переиспользуемый рецепт здесь короткий: + +- `view_state` возвращает сырые байты в base64 +- вы декодируете эти байты по известной схеме хранения контракта +- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема + +3. Теперь спросите контракт более привычным способом и сравните. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$CONTRACT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", params: { request_type: "call_function", account_id: $account_id, @@ -3000,7 +3650,7 @@ jq '{ }' /tmp/counter-call-function.json ``` -4. Сравните оба ответа. +4. Сравните оба ответа напрямую. ```bash RAW_STATE_NUMBER="$( @@ -3024,1168 +3674,2069 @@ jq -n \ }' ``` +Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: + +- `view_state` ответил на вопрос, прочитав storage напрямую +- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта + **Зачем нужен следующий шаг?** -Используйте `view_state`, когда вы уже знаете точное семейство storage-ключей и хотите raw bytes. Используйте `call_function`, когда вам нужен публичный метод чтения самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда уже стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). +Используйте `view_state`, когда настоящий вопрос относится к точному storage, отсутствующему view-методу или проверке известного семейства ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). + +## Трассировка чанков и шардов + +Начинайте отсюда, когда вопрос уже не просто «транзакция прошла или нет?», а «какой именно чанк на шарде исполнил каждый шаг работы?» -### Какие ERC-20 токены из Rainbow Bridge существуют на NEAR и сколько одного такого токена сейчас в обращении? +### Проследить, как сгенерированная `Transfer`-receipt переходит из одного чанка на шарде в другой -Используйте этот сценарий, когда хотите найти Rainbow Bridge ERC-20 контракты и посмотреть живой объём одного токена на NEAR. Rainbow Bridge развёртывает по одному NEAR-контракту на каждый bridged ERC-20 токен, а `factory.bridge.near` их перечисляет. +Используйте этот сценарий, когда вызов контракта был только началом истории. В этом зафиксированном mainnet-примере подписанная транзакция стартует на шарде `11`, а сгенерированная `Transfer`-receipt заканчивает путь уже на шарде `6`. Именно ради таких cross-shard handoff и имеет смысл смотреть на чанки. + +Этот walkthrough привязан к: + +- транзакции `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` от `7419369993.tg` к `game.hot.tg` с вызовом `l2_claim` +- исходному чанку `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` на шарде `11` в блоке `194623170` +- чанку первой receipt `FJWpAYzVXbZwqJUbGXELTnnBBkdvc6W8vWkwuUA3Zwz9` на шарде `11` в блоке `194623171` +- сгенерированной `Transfer`-receipt `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` +- конечному чанку `EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU` на шарде `6` в блоке `194623172` Стратегия - Одно чтение factory перечисляет token-контракты. Ещё два небольших view-вызова по одному токену показывают, что это за токен и сколько его сейчас на NEAR. + Сначала восстановите receipt-цепочку, потом напрямую посмотрите на сгенерированную receipt, а затем привяжите каждый шаг к тому чанку на шарде, который действительно нёс эту работу. + + 01RPC EXPERIMENTAL_tx_status быстро показывает граф receipts и в какие следующие блоки перешла работа. + 02RPC EXPERIMENTAL_receipt позволяет посмотреть на тело сгенерированной receipt напрямую, а не выводить его только из логов. + 03RPC chunk по блоку и шарду или по хешу чанка доказывает, какая именно единица исполнения на шарде нёсла каждый шаг. + +Оба experimental-метода здесь очень уместны: `EXPERIMENTAL_tx_status` быстро находит граф receipts, а `EXPERIMENTAL_receipt` показывает тело сгенерированной receipt ещё до того, как вы привяжете её обратно к чанкам. - 01RPC call_function get_tokens_accounts по factory.bridge.near возвращает развёрнутые bridged token-контракты. - 02Следующий RPC call_function по одному bridged token-контракту возвращает метаданные токена: имя, тикер и число десятичных знаков. - 03Ещё один RPC call_function по тому же контракту возвращает текущее сырое значение объёма в обращении на NEAR. +```mermaid +flowchart LR + A["Tx 8xrc...
блок 194623170
чанк Bfyd...
шард 11"] --> B["Receipt AFC2...
блок 194623171
чанк FJWp...
шард 11
логи ft_mint"] + B --> C["Сгенерированная receipt TtRn...
Transfer 1800930478788300000000 yoctoNEAR"] + C --> D["Чанк EPau...
блок 194623172
шард 6
receipt исполняется"] +``` **Что вы делаете** -- Спрашиваете у bridge factory обо всех bridged token-контрактах, которые она создала. -- Выбираете один bridged token-контракт и читаете его метаданные. -- Читаете total supply того же контракта и переводите его в человеческие единицы через `decimals`. +- Сначала восстанавливаете receipt-цепочку из транзакции. +- Напрямую смотрите на тело сгенерированной `Transfer`-receipt. +- Используете координаты блока и шарда там, где они уже известны. +- Используете хеш чанка там, где другой инструмент уже выдал точный конечный чанк. ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com -export FACTORY_ID=factory.bridge.near -export TOKENS_FILE=/tmp/rainbow-bridge-tokens.json +export TX_HASH=8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP +export SIGNER_ACCOUNT_ID=7419369993.tg +export ORIGIN_BLOCK_HEIGHT=194623170 +export ORIGIN_SHARD_ID=11 +export RECEIPT_BLOCK_HEIGHT=194623171 +export RECEIPT_SHARD_ID=11 +export GENERATED_RECEIPT_ID=TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4 +export DESTINATION_CHUNK_HASH=EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU ``` -1. Получите список bridged token-контрактов. +1. Начните с `EXPERIMENTAL_tx_status`, чтобы сначала увидеть граф receipts, а уже потом думать о чанках. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$FACTORY_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_tokens_accounts", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee "$TOKENS_FILE" >/dev/null + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: [$tx_hash, $signer_account_id] + }')" \ + | tee /tmp/chunk-trace-status.json >/dev/null -jq -r '.result.result | implode | fromjson | .[]' "$TOKENS_FILE" +jq '{ + final_execution_status: .result.final_execution_status, + transaction_handoff: .result.transaction_outcome.outcome.status, + receipts: ( + .result.receipts_outcome + | map({ + receipt_id: .id, + executor_id: .outcome.executor_id, + block_hash, + status: .outcome.status + }) + ) +}' /tmp/chunk-trace-status.json ``` -Каждая строка — это один bridged FT-контракт на NEAR в форме `.factory.bridge.near`. Например, bridged ERC-20 USDT с Ethereum-адреса `0xdAC17F958D2ee523a2206206994597C13D831ec7` появляется как `dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near`. +На что смотреть: -2. Прочитайте метаданные одного токен-контракта. +- подписанная транзакция передаёт работу в receipt `AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y` +- позже в том же графе receipts исполняется `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` для `7419369993.tg` +- уже одного tx status достаточно, чтобы увидеть: настоящая работа продолжилась после исходной подписанной транзакции -```bash -export TOKEN_ID=dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near +2. Посмотрите на сгенерированную receipt напрямую, чтобы доказать, что это действительно `Transfer`-receipt. +```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + --data "$(jq -nc --arg receipt_id "$GENERATED_RECEIPT_ID" '{ jsonrpc: "2.0", id: "fastnear", - method: "query", + method: "EXPERIMENTAL_receipt", params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_metadata", - args_base64: "e30=", - finality: "final" + receipt_id: $receipt_id } }')" \ - | tee /tmp/rainbow-bridge-token-metadata.json >/dev/null + | tee /tmp/chunk-trace-receipt.json >/dev/null -jq '.result.result | implode | fromjson | {name, symbol, decimals}' /tmp/rainbow-bridge-token-metadata.json +jq '{ + predecessor_id: .result.predecessor_id, + receiver_id: .result.receiver_id, + signer_id: .result.receipt.Action.signer_id, + signer_public_key: .result.receipt.Action.signer_public_key, + actions: .result.receipt.Action.actions +}' /tmp/chunk-trace-receipt.json ``` -3. Прочитайте текущий total supply на NEAR и переведите его в человеческие единицы. +Именно здесь история по шардам становится конкретной: эта цепочка исполнения контракта сгенерировала `Transfer` action receipt от `system` к `7419369993.tg` с депозитом `1800930478788300000000`. + +3. Используйте `chunk` по блоку и шарду, чтобы найти исходную подписанную транзакцию на шарде `11`. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_total_supply", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee /tmp/rainbow-bridge-token-supply.json >/dev/null - -RAW_SUPPLY="$( - jq -r '.result.result | implode | fromjson' /tmp/rainbow-bridge-token-supply.json -)" - -DECIMALS="$( - jq -r '.result.result | implode | fromjson | .decimals' /tmp/rainbow-bridge-token-metadata.json -)" + --data "$(jq -nc \ + --argjson block_id "$ORIGIN_BLOCK_HEIGHT" \ + --argjson shard_id "$ORIGIN_SHARD_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "chunk", + params: { + block_id: $block_id, + shard_id: $shard_id + } + }')" \ + | jq --arg tx_hash "$TX_HASH" '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created + }, + matching_transaction: ( + .result.transactions[] + | select(.hash == $tx_hash) + | { + hash, + signer_id, + receiver_id + } + ) + }' +``` -HUMAN_SUPPLY="$( - python3 - "$RAW_SUPPLY" "$DECIMALS" <<'PY' -from decimal import Decimal +Это самый чистый use case для `chunk` по блоку и шарду: координаты уже известны, а вам нужна точная единица исполнения на шарде, которая несла исходную подписанную транзакцию. -raw = Decimal(sys.argv[1]) -decimals = int(sys.argv[2]) -human = raw / (Decimal(10) ** decimals) -print(human) -PY -)" +4. Оставайтесь на том же маршруте и посмотрите, как первая receipt исполняется в следующем блоке на том же шарде. -jq -n \ - --arg token_id "$TOKEN_ID" \ - --arg raw_supply "$RAW_SUPPLY" \ - --argjson decimals "$DECIMALS" \ - --arg human_supply "$HUMAN_SUPPLY" '{ - token_id: $token_id, - raw_supply: $raw_supply, - decimals: $decimals, - human_supply: $human_supply - }' +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --argjson block_id "$RECEIPT_BLOCK_HEIGHT" \ + --argjson shard_id "$RECEIPT_SHARD_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "chunk", + params: { + block_id: $block_id, + shard_id: $shard_id + } + }')" \ + | jq '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created, + tx_root: .result.header.tx_root, + gas_used: .result.header.gas_used + }, + tx_count: (.result.transactions | length), + receipt_count: (.result.receipts | length), + matching_receipt: ( + .result.receipts[] + | select(.receipt_id == "AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y") + | { + receipt_id, + predecessor_id, + receiver_id + } + ) + }' ``` -Результат `ft_total_supply` приходит в минимальных единицах токена. Используйте `decimals` из ответа предыдущего шага, чтобы перевести его в человекочитаемый объём в обращении. +Вот здесь chunks наконец становятся естественными: + +- у чанка `tx_root = 11111111111111111111111111111111` +- `tx_count` равен `0` +- но шард всё равно жжёт gas и исполняет receipt `AFC2...` -#### Необязательное расширение: показать первые несколько bridged token-ов с метаданными и объёмом в обращении +То есть этот шард реально сделал работу в этом блоке, хотя новая подписанная транзакция прямо в самом чанке не появилась. -Используйте это расширение, когда нужен быстрый sample-инвентарь и вы всё ещё хотите оставаться в RPC. +5. Переключайтесь на `chunk` по хешу, когда другой инструмент уже выдал точный конечный чанк. ```bash -export TOKEN_SAMPLE_COUNT=5 - -python3 <<'PY' -from decimal import Decimal - -TOKENS_FILE = os.environ["TOKENS_FILE"] -LIMIT = int(os.environ.get("TOKEN_SAMPLE_COUNT", "5")) -RPC_URL = os.environ["RPC_URL"] - -def decode_result(result): - return json.loads("".join(chr(b) for b in result)) - -with open(TOKENS_FILE) as fh: - token_ids = decode_result(json.load(fh)["result"]["result"])[:LIMIT] - -def rpc_call(account_id, method_name): - payload = { - "jsonrpc": "2.0", - "id": "fastnear", - "method": "query", - "params": { - "request_type": "call_function", - "account_id": account_id, - "method_name": method_name, - "args_base64": "e30=", - "finality": "final", - }, +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg chunk_id "$DESTINATION_CHUNK_HASH" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "chunk", + params: { + chunk_id: $chunk_id } - import subprocess - raw = subprocess.check_output([ - "curl", "-s", RPC_URL, - "-H", "content-type: application/json", - "--data", json.dumps(payload), - ], text=True) - return decode_result(json.loads(raw)["result"]["result"]) - -print(f"{'token_id':<56} {'symbol':<12} {'decimals':>8} {'raw_supply':>24} {'human_supply':>24} name") -for token_id in token_ids: - metadata = rpc_call(token_id, "ft_metadata") - raw_supply = rpc_call(token_id, "ft_total_supply") - human_supply = Decimal(raw_supply) / (Decimal(10) ** metadata["decimals"]) - print( - f"{token_id:<56} " - f"{metadata['symbol']:<12} " - f"{metadata['decimals']:>8} " - f"{raw_supply:>24} " - f"{str(human_supply):>24} " - f"{metadata['name']}" - ) -PY + }')" \ + | jq --arg receipt_id "$GENERATED_RECEIPT_ID" '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created, + tx_root: .result.header.tx_root, + gas_used: .result.header.gas_used + }, + tx_count: (.result.transactions | length), + receipt_count: (.result.receipts | length), + matching_receipt: ( + .result.receipts[] + | select(.receipt_id == $receipt_id) + | { + receipt_id, + predecessor_id, + receiver_id + } + ) + }' ``` +Это и подтверждает cross-shard hop: + +- сгенерированная `Transfer`-receipt исполняется в чанке `EPau...` +- этот чанк живёт на шарде `6`, а не на шарде `11` +- подписанная транзакция стартовала на одном шарде, а следующая receipt завершилась уже на другом + **Зачем нужен следующий шаг?** -Оставайтесь в RPC, пока вопрос звучит как «какие bridged token-контракты существуют и сколько одного такого токена сейчас в обращении?» Factory — это источник истины для множества bridged token-ов, а каждый token-контракт сам отвечает за свои метаданные и объём в обращении через стандартные NEP-141 view-методы. Если следующий вопрос становится «кто держит этот токен?», переключайтесь на [V1 FT Top Holders](https://docs.fastnear.com/ru/api/v1/ft-top), а не пытайтесь обходить holders через RPC. +Используйте [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда вы знаете координаты блока и шарда и хотите буквально спросить: «что этот шард исполнил в этом блоке?» Используйте [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. Используйте [EXPERIMENTAL_tx_status](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) и [EXPERIMENTAL_receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), когда настоящий вопрос относится к трассировке на уровне receipts. Если ещё нужны state changes и produced receipts, расширяйтесь в [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard). -## Частые ошибки +## Точные чтения NEAR Social и BOS -- Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. -- Забывать переключаться с обычного RPC на архивный RPC для старого состояния. -- Воспринимать браузерную аутентификацию в интерфейсе документации как продовый паттерн для бэкенда. -- Продолжать пользоваться низкоуровневыми статусами транзакций, когда вопрос уже превратился в расследование или исторический разбор. +Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. -## Полезные связанные страницы +### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? -- [RPC Reference](https://docs.fastnear.com/ru/rpc) -- [Auth & Access](https://docs.fastnear.com/ru/auth) -- [FastNear API](https://docs.fastnear.com/ru/api) -- [Transactions API](https://docs.fastnear.com/ru/tx) -- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) -- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». ---- + Стратегия + Спросите у social.near ровно о двух вещах, которые важны до подписи. -## Снапшоты для валидаторов + 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. + 02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near. + 03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer. -- HTML-маршрут: https://docs.fastnear.com/ru/snapshots -- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots.md +Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: -**Источник:** [https://docs.fastnear.com/ru/snapshots](https://docs.fastnear.com/ru/snapshots) +- есть ли у целевого аккаунта storage на `social.near`? +- если есть, осталось ли там ещё место? +- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? -# Снапшоты блокчейна +**Официальные ссылки** -Этот раздел — для операторов узлов, которые поднимают или восстанавливают инфраструктуру NEAR. Это не поверхность для прикладных данных. Если задача — читать балансы, историю, блоки или состояние контракта, используйте документацию API и RPC, а не сценарии со снапшотами. +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -:::warning[Бесплатные снапшоты устарели] -Бесплатные снапшоты данных nearcore больше не выпускаются. +**Что вы делаете** -Infrastructure Committee и Near One рекомендуют Epoch Sync вместе с децентрализованной синхронизацией состояния. Актуальные рекомендации и режим подъёма смотрите на [NEAR Nodes](https://near-nodes.io). -::: +- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. +- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. +- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. +- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». -## Используйте этот раздел, когда +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mike.near +export SIGNER_ACCOUNT_ID=mike.near +``` -- нужно поднять узел mainnet или testnet из данных снапшота -- идёт восстановление RPC- или архивного узла -- уже известно, что нужен путь загрузки снапшота FastNear +1. Сначала проверьте сам аккаунт signer. -## Не используйте этот раздел, когда +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/social-publish-signer.json >/dev/null -- идёт запрос данных цепочки для приложения -- нужны свежие блоки, балансы, история или состояние контракта -- нужны общие рекомендации по продуктовому API, а не настройка оператором +jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + signer_account_id: $signer_account_id, + amount: .result.amount, + locked: .result.locked, + storage_usage: .result.storage_usage +}' /tmp/social-publish-signer.json +``` -В этих случаях используйте [Справочник 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). +Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. -## Перед загрузкой +2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. -- Сначала выберите сеть: mainnet или testnet. -- Решите, нужны обычные данные RPC или архивные. -- Убедитесь, что понимаете, где должны лежать горячие и холодные данные, прежде чем стартовать архивную загрузку. +```bash +SOCIAL_STORAGE_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" -- Установите `rclone` — скрипты загрузки от него зависят. +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_account_storage", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-account-storage.json >/dev/null -:::info[Установка `rclone`] -Установите `rclone` командой: +jq --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + storage: (.result.result | implode | fromjson), + storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) +}' /tmp/social-account-storage.json +``` + +Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. + +3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. ```bash -sudo -v ; curl https://rclone.org/install.sh | sudo bash +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + jq -n --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + signer_matches_target: true, + permission_granted: true, + reason: "owner write" + }' +else + WRITE_PERMISSION_ARGS_BASE64="$( + jq -nc \ + --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ + --arg key "$ACCOUNT_ID" '{ + predecessor_id: $predecessor_id, + key: $key + }' | base64 | tr -d '\n' + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + signer_matches_target: false, + permission_granted: (.result.result | implode | fromjson) + }' +fi ``` -::: -## Что покрывает каждый путь +4. Сведите проверку storage и разрешения в один читаемый итог. -- **Mainnet** включает оптимизированный `fast-rpc`, обычный RPC и архивные пути загрузки для горячих и холодных данных. -- **Testnet** включает RPC и архивные пути снапшотов для операторов testnet. +```bash +AVAILABLE_BYTES="$( + jq -r ' + .result.result + | if length == 0 then "0" + else (implode | fromjson | .available_bytes // 0 | tostring) + end + ' /tmp/social-account-storage.json +)" -Требования к узлам смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near), а исходники скриптов загрузки, которые используются в этих руководствах, — в [fastnear/static](https://github.com/fastnear/static). +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + PERMISSION_GRANTED=true +else + PERMISSION_GRANTED="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq -r '.result.result | implode | fromjson' + )" +fi -## Нужен сценарий? +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ + --argjson available_bytes "$AVAILABLE_BYTES" \ + --argjson permission_granted "$PERMISSION_GRANTED" '{ + account_id: $account_id, + signer_account_id: $signer_account_id, + storage_ready: ($available_bytes > 0), + permission_ready: $permission_granted, + ready_to_publish_now: (($available_bytes > 0) and $permission_granted) + }' +``` -Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. +Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. -## Выберите сеть +**Зачем нужен следующий шаг?** - - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) - - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) +Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. ---- +### Что прямо сейчас содержит `mob.near/widget/Profile`? -## Примеры Snapshot +Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». -- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/examples -- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/examples.md + Стратегия + Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. -**Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) + 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. + 02RPC call_function get читает точный исходник widget/Profile. + 03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples. -## Быстрый старт +**Официальные ссылки** + +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. +**Что вы делаете** -Эти helper-скрипты поддерживаются FastNear и оптимизированы под скорость восстановления. Если в вашей среде нужен review изменений, сначала скачайте скрипт, проверьте его и только потом запускайте, вместо прямого piping в `bash`. +- Спрашиваете у `social.near` каталог виджетов под `mob.near`. +- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. +- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. +- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). ```bash -DATA_PATH=~/.near/data - -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ - | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mob.near +export WIDGET_NAME=Profile ``` -Это самый короткий путь восстановления на странице. Всё остальное нужно только если вам нужен standard RPC или архивный hot/cold-сценарий. +1. Получите каталог виджетов и сохраните высоты блоков последней записи. -## Готовое расследование +```bash +WIDGET_KEYS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} + }' | base64 | tr -d '\n' +)" -### Выбрать и выполнить правильный сценарий восстановления mainnet +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "keys", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-keys.json >/dev/null -Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. +jq --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].widget + | to_entries + | sort_by(.value * -1) + | map({ + widget_name: .key, + last_write_block: .value + }) + | .[0:20] +' /tmp/social-widget-keys.json +``` - Стратегия - Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него. +2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. - 01Сначала решите, нужен ли вам optimized fast-rpc, обычный RPC или архивный режим. - 02Если нужен архив, сначала зафиксируйте одну точную высоту snapshot-блока и дальше переиспользуйте только её. - 03Выполняйте только команды выбранного пути и не смешивайте optimized, standard и archival шаги в одном сценарии. +```bash +WIDGET_GET_ARGS_BASE64="$( + jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + }' | base64 | tr -d '\n' +)" -**Цель** +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-source.json >/dev/null -- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:25] + | join("\n") + ' /tmp/social-widget-source.json +``` -| Путь или команда | Как используем | Зачем используем | -| --- | --- | --- | -| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | -| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | -| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | -| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | -| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | +3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. -**Что должен включать полезный ответ** +```bash +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + ' /tmp/social-widget-keys.json \ + | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +``` -- какой сценарий восстановления выбран и почему -- какие ключевые env vars важны для выбранного пути -- куда на диске должны попасть данные -- должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap +На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. -### Минимальная команда для optimized mainnet `fast-rpc` +**Зачем нужен следующий шаг?** -Используйте этот якорь, когда уже ясно, что нужен высокопроизводительный mainnet RPC-узел, и требуется только минимальная рабочая команда. +Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. -```bash -DATA_PATH=~/.near/data +## Частые задачи -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ - | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash -``` +### Проверить точное состояние аккаунта или ключа доступа -### Минимальная команда для стандартного mainnet RPC +**Начните здесь** -Используйте этот якорь, когда нужен обычный путь восстановления mainnet RPC без optimized-профиля `fast-rpc`. +- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для точных полей аккаунта. +- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. -```bash -DATA_PATH=~/.near/data +**Следующая страница при необходимости** -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ - | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet bash -``` +- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» -### Shell-сценарий архивного восстановления mainnet +**Остановитесь, когда** -Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. +- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. -**Что вы делаете** +**Переходите дальше, когда** -- Один раз получаете последнюю высоту архивного снапшота mainnet. -- Сохраняете её в `LATEST`. -- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. +- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. +- Пользователя интересует не текущее состояние, а недавняя история активности. -```bash -HOT_DATA_PATH=~/.near/data -COLD_DATA_PATH=/mnt/hdds/cold-data +### Трассировать исполнение на уровне шарда через чанки -LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" -echo "Latest archival mainnet snapshot block: $LATEST" +**Начните здесь** -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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash +- Начните с примера выше, если настоящий вопрос звучит как «какой чанк или шард вообще исполнил эту receipt?» +- [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда координаты блока и шарда уже известны. +- [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. -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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash -``` +**Следующая страница при необходимости** -**Зачем нужен следующий шаг?** +- [Experimental Receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), если нужно само тело сгенерированной receipt. +- [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard), если chunk payload уже недостаточен и ещё нужны state changes или produced receipts. +- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если вопрос превращается в более широкое async- или callback-расследование. -Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. +**Остановитесь, когда** -## Частые ошибки +- Уже можно назвать, какой именно чанк и какой шард несли ту работу, которая была важна. -- Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. -- Выбирать архивное восстановление, когда достаточно обычного или optimized RPC. -- Забывать про разделение hot/cold-данных для архивного режима. -- Переходить к командам до выбора сети и цели узла. +**Переходите дальше, когда** -## Полезные связанные страницы +- Пользователю нужны уже не детали исполнения на уровне шарда, а читаемая история транзакции. Тогда переходите к [Transactions API](https://docs.fastnear.com/ru/tx). -- [Обзор снапшотов](https://docs.fastnear.com/ru/snapshots) -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) -- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) -- [RPC Reference](https://docs.fastnear.com/ru/rpc) -- [NEAR Data API](https://docs.fastnear.com/ru/neardata) +### Проверить один точный блок или снимок состояния протокола ---- +**Начните здесь** -## mainnet +- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. +- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» +- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. -- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/mainnet -- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/mainnet.md +**Следующая страница при необходимости** -**Источник:** [https://docs.fastnear.com/ru/snapshots/mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) +- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. +- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» -# Mainnet +**Остановитесь, когда** -## Оптимизированный снапшот mainnet +- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. -Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и подходит для более узких задач. +**Переходите дальше, когда** -Узлы с достаточными ресурсами могут использовать значение `$RPC_TYPE=fast-rpc`. По умолчанию используется `rpc`. +- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: +### Что этот контракт возвращает прямо сейчас? -- `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:** +- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» +- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. +- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. +- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `RPC_TYPE=fast-rpc` — включает оптимизированный режим -::: +**Следующая страница при необходимости** -`RPC Mainnet Snapshot » ~/.near/data`: +- [FastNear API](https://docs.fastnear.com/ru/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. +- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» -```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 +- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. -Это стандартный способ получить снапшот без оптимизированного режима из предыдущего раздела. +**Переходите дальше, когда** -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: +- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. +- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» -- `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 -::: +- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. +- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. +- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. -`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 -``` +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. +- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» -## Архивный снапшот mainnet +**Остановитесь, когда** -:::warning -**Требует много времени и места на диске.** +- У вас уже есть результат отправки и нужный финальный статус. -Подготовьтесь к очень большому объёму загрузки и длительному времени выполнения. +**Переходите дальше, когда** -Размер снапшота составляет около 60 ТБ, и он содержит более 1 миллиона файлов. -::: +- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. +- Нужен уже не единичный статус, а более широкий сценарий расследования. -Перед запуском скрипта загрузки можно задать следующие переменные окружения: +## Частые ошибки -- `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` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. +- Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. +- Забывать переключаться с обычного RPC на архивный RPC для старого состояния. +- Воспринимать браузерную аутентификацию в интерфейсе документации как продовый паттерн для бэкенда. +- Продолжать пользоваться низкоуровневыми статусами транзакций, когда вопрос уже превратился в расследование или исторический разбор. -По умолчанию скрипт ожидает следующие пути для данных: +## Полезные связанные страницы -- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` -- Cold data, которые можно хранить на HDD: `/mnt/nvme/data/cold-data` +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [Auth & Access](https://docs.fastnear.com/ru/auth) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) -**Выполните следующие команды, чтобы скачать архивный снапшот mainnet:** +--- -1. Получите высоту блока последнего снапшота: +## Снапшоты для валидаторов -`Latest archival mainnet snapshot block`: +- HTML-маршрут: https://docs.fastnear.com/ru/snapshots +- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots.md -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` +**Источник:** [https://docs.fastnear.com/ru/snapshots](https://docs.fastnear.com/ru/snapshots) -2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. +# Снапшоты блокчейна -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=hot-data` — выбирает загрузку Hot data -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `BLOCK=$LATEST` — указывает блок снапшота +Этот раздел — для операторов узлов, которые поднимают или восстанавливают инфраструктуру NEAR. Это не поверхность для прикладных данных. Если задача — читать балансы, историю, блоки или состояние контракта, используйте документацию API и RPC, а не сценарии со снапшотами. + +:::warning[Бесплатные снапшоты устарели] +Бесплатные снапшоты данных nearcore больше не выпускаются. + +Infrastructure Committee и Near One рекомендуют Epoch Sync вместе с децентрализованной синхронизацией состояния. Актуальные рекомендации и режим подъёма смотрите на [NEAR Nodes](https://near-nodes.io). ::: -`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 -``` +- нужно поднять узел mainnet или testnet из данных снапшота +- идёт восстановление RPC- или архивного узла +- уже известно, что нужен путь загрузки снапшота FastNear -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` — указывает блок снапшота -::: +- идёт запрос данных цепочки для приложения +- нужны свежие блоки, балансы, история или состояние контракта +- нужны общие рекомендации по продуктовому API, а не настройка оператором -`Archival Mainnet Snapshot (cold-data) » /mnt/hdds/cold-data`: +В этих случаях используйте [Справочник 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 или архивные. +- Убедитесь, что понимаете, где должны лежать горячие и холодные данные, прежде чем стартовать архивную загрузку. + +- Установите `rclone` — скрипты загрузки от него зависят. + +:::info[Установка `rclone`] +Установите `rclone` командой: ```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 +sudo -v ; curl https://rclone.org/install.sh | sudo bash ``` +::: ---- +## Что покрывает каждый путь -## testnet +- **Mainnet** включает оптимизированный `fast-rpc`, обычный RPC и архивные пути загрузки для горячих и холодных данных. +- **Testnet** включает RPC и архивные пути снапшотов для операторов testnet. -- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/testnet -- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/testnet.md +Требования к узлам смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near), а исходники скриптов загрузки, которые используются в этих руководствах, — в [fastnear/static](https://github.com/fastnear/static). -**Источник:** [https://docs.fastnear.com/ru/snapshots/testnet](https://docs.fastnear.com/ru/snapshots/testnet) +## Нужен сценарий? -# Testnet +Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. -## RPC-снапшот testnet +## Выберите сеть -Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и нужен для более узких задач. + - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) + - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: +--- -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. +## Примеры Snapshot -**Выполните эту команду, чтобы скачать RPC-снапшот testnet:** +- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/examples.md -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=testnet` — явно выбирает данные testnet -::: +**Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) -`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 -``` +### Выбрать и выполнить правильный сценарий восстановления mainnet -## Архивный снапшот testnet +Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. -:::warning -**Требует много времени и места на диске.** + Стратегия + Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него. -Подготовьтесь к большому объёму загрузки и длительному времени выполнения. -::: + 01Сначала решите, нужен ли вам optimized fast-rpc, обычный RPC или архивный режим. + 02Если нужен архив, сначала зафиксируйте одну точную высоту snapshot-блока и дальше переиспользуйте только её. + 03Выполняйте только команды выбранного пути и не смешивайте optimized, standard и archival шаги в одном сценарии. -Перед запуском скрипта загрузки можно задать следующие переменные окружения: +**Цель** -- `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` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. +- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. -По умолчанию скрипт ожидает следующий путь для данных: +| Путь или команда | Как используем | Зачем используем | +| --- | --- | --- | +| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | +| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | +| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | +| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | +| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | -- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` +**Что должен включать полезный ответ** -**Выполните следующие команды, чтобы скачать архивный снапшот testnet:** +- какой сценарий восстановления выбран и почему +- какие ключевые env vars важны для выбранного пути +- куда на диске должны попасть данные +- должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap -1. Получите высоту блока последнего снапшота: +### Shell-сценарий архивного восстановления mainnet -`Latest archival testnet snapshot block`: +Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/testnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` +**Что вы делаете** -2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. +- Один раз получаете последнюю высоту архивного снапшота mainnet. +- Сохраняете её в `LATEST`. +- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=hot-data` — выбирает загрузку Hot data -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=testnet` — явно выбирает сеть testnet -- `BLOCK=$LATEST` — указывает блок снапшота -::: +```bash +HOT_DATA_PATH=~/.near/data +COLD_DATA_PATH=/mnt/hdds/cold-data -`Archival Testnet Snapshot (hot-data) » ~/.near/data`: +LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" +echo "Latest archival mainnet snapshot block: $LATEST" -```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 +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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash ``` ---- +**Зачем нужен следующий шаг?** -## API переводов +Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. -- HTML-маршрут: https://docs.fastnear.com/ru/transfers -- Markdown-маршрут: https://docs.fastnear.com/ru/transfers.md +## Частые задачи -**Источник:** [https://docs.fastnear.com/ru/transfers](https://docs.fastnear.com/ru/transfers) +### Поднять optimized `fast-rpc`-узел в mainnet -# API переводов +**Начните здесь** -API переводов — это самая узкая поверхность истории FastNear. Стартуйте здесь, когда вопрос именно о движении активов, а не о более широкой истории исполнения за этим движением. +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), конкретно путь для optimized `fast-rpc`. -## Базовый URL +**Следующая страница при необходимости** -```bash title="Transfers API Mainnet" -https://transfers.main.fastnear.com -``` +- Вернитесь к обычному сценарию mainnet RPC, если узел не подходит для optimized profile. -Эта поверхность сейчас доступна только в mainnet. `?network=testnet` не переключает бэкенд. +**Остановитесь, когда** -## Используйте этот API, когда +- Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. -- нужна история переводов NEAR или FT-токенов по аккаунту -- строятся ленты кошелька или представления активности только по переводам -- отвечаете на вопросы поддержки или комплаенса про отправку и получение +**Переходите дальше, когда** -## Не стартуйте здесь, когда +- На самом деле требуется архивное хранение, а не просто быстрый запуск. -- нужна более широкая история транзакций или квитанций -- нужны балансы, активы, NFT или представления стейкинга -- нужен трафик testnet +### Восстановить обычный RPC-узел в стандартный каталог nearcore -Используйте [Транзакции API](https://docs.fastnear.com/ru/tx) для более широкой истории исполнения и [FastNear API](https://docs.fastnear.com/ru/api) для ответов в стиле состояния аккаунта. +**Начните здесь** -## Минимально полезные входы +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) или [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) в зависимости от сети и выберите обычный RPC-сценарий для нужного окружения. -- `account_id` -- опциональные фильтры: актив, направление, сумма или время -- нужно пользователю несколько событий или более длинный обзор истории +**Следующая страница при необходимости** -## Рабочий цикл по умолчанию +- Настраивайте `DATA_PATH`, `THREADS` или ограничения по пропускной способности только после того, как понятен стандартный сценарий. -1. Начните с [Запроса переводов](https://docs.fastnear.com/ru/transfers/query), используя самый узкий набор фильтров, который всё ещё отвечает на вопрос. -2. Читайте возвращённые события как историю только переводов. Не пересобирайте полную хронологию квитанций, пока пользователь не попросит. -3. Переиспользуйте непрозрачный `resume_token` ровно в том виде, в каком его вернул сервис, при листании дальше. -4. Остановитесь, как только можете ответить, кто, что, когда и в каком активе отправил. +**Остановитесь, когда** -## Аутентификация и доступность +- Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. -- Публичные чтения истории переводов часто работают и без ключа. -- Если вы стандартизируете один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. -- Ответы включают непрозрачный `resume_token` для пагинации. -- Сервис сейчас доступен только в mainnet. +**Переходите дальше, когда** -## Расширяйтесь, только если +- Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. -- пользователь начинает спрашивать о квитанциях или действиях кроме переводов -- пользователь хочет более широкий контекст транзакции за переводом -- пользователь на самом деле спрашивает о балансах или текущих активах, а не о движении +### Правильно поднять архивные hot- и cold-данные mainnet -Тогда расширяйтесь на [Транзакции API](https://docs.fastnear.com/ru/tx) или [FastNear API](https://docs.fastnear.com/ru/api), а не перегружайте представление переводов. +**Начните здесь** -## Типовые стартовые страницы +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), раздел архивного режима. -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) — лента по аккаунту с фильтрами по направлению, активу, сумме и времени +**Следующая страница при необходимости** -## Нужен сценарий? +- Сначала получите последнюю высоту архивного снапшота, затем запускайте отдельные загрузки hot- и cold-данных с правильными путями. -Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для простых пошаговых сценариев: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. +**Остановитесь, когда** -## Устранение неполадок +- План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. -### Нужны полные метаданные транзакции +**Переходите дальше, когда** -Переходите на [Транзакции API](https://docs.fastnear.com/ru/tx), если одной истории переводов недостаточно. +- Оператору нужны уже общие гайды nearcore по bootstrap, а не только FastNear snapshots. -### `resume_token` перестал работать +### Поднять архивные hot-данные в testnet -Считайте токен непрозрачным и переиспользуйте его только с тем же эндпоинтом и фильтрами, которые его вернули. +**Начните здесь** ---- +- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet), раздел архивного режима. -## Примеры Transfers API +**Следующая страница при необходимости** -- HTML-маршрут: https://docs.fastnear.com/ru/transfers/examples -- Markdown-маршрут: https://docs.fastnear.com/ru/transfers/examples.md +- Получите последнюю высоту архивного снапшота testnet перед шагом загрузки. -**Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) +**Остановитесь, когда** -## Быстрый старт +- Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. -Начните с одного отфильтрованного входящего запроса и сразу выведите поля, ради которых вообще стоит использовать Transfers API. +**Переходите дальше, когда** -```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=intents.near -ASSET_ID=native:near -MIN_AMOUNT_YOCTO=1000000000000000000000000 +- Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. -curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - ignore_system: true, - min_amount: $min_amount, - desc: true, - limit: 5 - }')" \ - | jq '{ - resume_token, - transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - asset_id, - amount, - human_amount: ( - if .human_amount == null then null - else (.human_amount * 1000 | round / 1000) - end - ), - usd_amount: ( - if .usd_amount == null then null - else (.usd_amount * 100 | round / 100) - end - ), - block_timestamp, - method_name, - transfer_type, - start_of_block_balance, - end_of_block_balance, - other_account_id, - block_height - } - ] - }' -``` +## Частые ошибки -Это самый короткий путь к вопросу «какие переводы от 1+ NEAR пришли на этот аккаунт, чего они стоили и какую строку стоит разбирать дальше?» `usd_amount` может быть `null`, если для этой строки нет ценового покрытия. +- Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. +- Выбирать архивное восстановление, когда достаточно обычного или optimized RPC. +- Забывать про разделение hot/cold-данных для архивного режима. +- Переходить к командам до выбора сети и цели узла. -## Готовый сценарий +## Полезные связанные страницы -### Какие входящие переводы от 1+ NEAR попали на этот аккаунт и какую строку стоит разобрать? +- [Обзор снапшотов](https://docs.fastnear.com/ru/snapshots) +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) +- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [NEAR Data API](https://docs.fastnear.com/ru/neardata) -Используйте этот сценарий, когда история звучит так: «сначала мне нужен один узкий поиск переводов, я хочу поля, которые уже похожи на wallet- или analytics-данные, и только после этого решу, нужна ли одной строке более глубокая расшифровка». +--- - Стратегия - Сначала используйте Transfers API ради отфильтрованного ответа о движении средств, а расширяйтесь только если одной строке всё ещё нужен chain-контекст. +## mainnet - 01POST /v0/transfers сначала делает всю фильтрацию: входящая сторона, один asset, скрытие system-переводов и порог по минимальной сумме. - 02Сначала выведите отличительные поля строки: human_amount, usd_amount, method_name, transfer_type и running balances. - 03Если нужны ещё строки, переиспользуйте непрозрачный resume_token с точно теми же фильтрами. - 04И только потом выбирайте одну строку и решайте, нужен ли вам её receipt_id как execution-anchor или её transaction_id как якорь для читаемой истории. +- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/mainnet +- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/mainnet.md -**Что вы делаете** +**Источник:** [https://docs.fastnear.com/ru/snapshots/mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) -- Запрашиваете отфильтрованное окно входящих переводов для одного активного mainnet-аккаунта. -- Сначала печатаете поля строки, которые Transfers API уже нормализует за вас. -- Переиспользуете тот же `resume_token`, если вам нужна следующая страница. -- Поднимаете либо `receipt_id`, либо `transaction_id` только тогда, когда одной строке всё ещё нужна более глубокая история. +# Mainnet -```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=intents.near -ASSET_ID=native:near -MIN_AMOUNT_YOCTO=1000000000000000000000000 -TRANSFER_INDEX=0 +## Оптимизированный снапшот mainnet -curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - ignore_system: true, - min_amount: $min_amount, - desc: true, - limit: 5 - }')" \ - | tee /tmp/transfers-window.json >/dev/null +Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и подходит для более узких задач. -jq '{ - resume_token, - transfers: [ - .transfers - | to_entries[] - | { - transfer_index: .key, - transaction_id: .value.transaction_id, - receipt_id: .value.receipt_id, - asset_id: .value.asset_id, - amount: .value.amount, - human_amount: ( - if .value.human_amount == null then null - else (.value.human_amount * 1000 | round / 1000) - end - ), - usd_amount: ( - if .value.usd_amount == null then null - else (.value.usd_amount * 100 | round / 100) - end - ), - block_timestamp: .value.block_timestamp, - method_name: .value.method_name, - transfer_type: .value.transfer_type, - start_of_block_balance: .value.start_of_block_balance, - end_of_block_balance: .value.end_of_block_balance, - other_account_id: .value.other_account_id, - block_height: .value.block_height - } - ] -}' /tmp/transfers-window.json +Узлы с достаточными ресурсами могут использовать значение `$RPC_TYPE=fast-rpc`. По умолчанию используется `rpc`. -RESUME_TOKEN="$( - jq -r '.resume_token // empty' /tmp/transfers-window.json -)" +Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: -if [ -n "$RESUME_TOKEN" ]; then - curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" \ - --arg resume_token "$RESUME_TOKEN" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - ignore_system: true, - min_amount: $min_amount, - desc: true, - limit: 5, - resume_token: $resume_token - }')" \ - | jq '{ - next_page_resume_token: .resume_token, - next_transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - human_amount: ( - if .human_amount == null then null - else (.human_amount * 1000 | round / 1000) - end - ), - transfer_type, - other_account_id, - block_height - } - ] - }' -fi +- `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` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. -TRANSACTION_ID="$( - jq -r --argjson transfer_index "$TRANSFER_INDEX" \ - '.transfers[$transfer_index].transaction_id // empty' \ - /tmp/transfers-window.json -)" +**Выполните эту команду, чтобы скачать RPC-снапшот mainnet:** -RECEIPT_ID="$( - jq -r --argjson transfer_index "$TRANSFER_INDEX" \ - '.transfers[$transfer_index].receipt_id // empty' \ - /tmp/transfers-window.json -)" +:::info +Будут заданы следующие переменные окружения: +- `DATA_PATH=~/.near/data` — стандартный путь nearcore +- `CHAIN_ID=mainnet` — явно выбирает данные mainnet +- `RPC_TYPE=fast-rpc` — включает оптимизированный режим +::: -printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" -printf 'Chosen transaction id: %s\n' "$TRANSACTION_ID" -printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" +`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 -#### Необязательное продолжение: execution-anchor или transaction-story? +Это стандартный способ получить снапшот без оптимизированного режима из предыдущего раздела. -Используйте `receipt_id`, когда нужен execution-anchor именно для этой строки. Используйте `transaction_id`, когда нужна читаемая история того, что именно подписал signer. +Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: -```bash -if [ -n "$RECEIPT_ID" ]; then - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' -fi +- `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` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. -if [ -n "$TRANSACTION_ID" ]; then - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TRANSACTION_ID" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ) - }' -fi -``` +**Выполните эту команду, чтобы скачать RPC-снапшот mainnet:** -**Зачем нужен следующий шаг?** +:::info +Будут заданы следующие переменные окружения: +- `DATA_PATH=~/.near/data` — стандартный путь nearcore +- `CHAIN_ID=mainnet` — явно выбирает данные mainnet +::: -Именно здесь Transfers API показывает свою ценность. Первый запрос уже отвечает на вопрос о движении средств в терминах, удобных для wallet- и analytics-сценариев: отфильтрованные строки, humanized amount, тип перевода, method-clue и running balances. Если всё ещё нужна следующая страница, переиспользуйте тот же `resume_token` с теми же фильтрами. Если нужен chain-контекст, следуйте по `receipt_id` ради execution-anchor или по `transaction_id` ради читаемой истории транзакции. +`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 +``` -- Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. -- Считать историю переводов полной историей исполнения вместо отфильтрованного movement-view. -- Переиспользовать `resume_token` с другими фильтрами. -- Игнорировать `method_name`, `transfer_type` или running balances, хотя именно из-за них этот API часто удобнее сырой transaction-history. -- Начинать здесь с вопросов про testnet, хотя этот API сегодня работает только в mainnet. +## Архивный снапшот mainnet -## Полезные связанные страницы +:::warning +**Требует много времени и места на диске.** -- [Transfers API](https://docs.fastnear.com/ru/transfers) -- [Transactions API](https://docs.fastnear.com/ru/tx) -- [FastNear API](https://docs.fastnear.com/ru/api) -- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) -- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +Подготовьтесь к очень большому объёму загрузки и длительному времени выполнения. ---- +Размер снапшота составляет около 60 ТБ, и он содержит более 1 миллиона файлов. +::: -## Транзакции API +Перед запуском скрипта загрузки можно задать следующие переменные окружения: -- HTML-маршрут: https://docs.fastnear.com/ru/tx -- Markdown-маршрут: https://docs.fastnear.com/ru/tx.md +- `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` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. -**Источник:** [https://docs.fastnear.com/ru/tx](https://docs.fastnear.com/ru/tx) +По умолчанию скрипт ожидает следующие пути для данных: -# Транзакции API +- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` +- Cold data, которые можно хранить на HDD: `/mnt/nvme/data/cold-data` -Транзакции API — это поверхность истории. Используйте её, когда нужны индексированные представления транзакций или квитанций без постоянного опроса сырых RPC-методов и ручного объединения результатов. +**Выполните следующие команды, чтобы скачать архивный снапшот mainnet:** -## Базовые URL +1. Получите высоту блока последнего снапшота: -```bash title="Transactions API Mainnet" -https://tx.main.fastnear.com -``` +`Latest archival mainnet snapshot block`: -```bash title="Transactions API Testnet" -https://tx.test.fastnear.com +```bash +LATEST=$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt") +echo "Latest snapshot block: $LATEST" ``` -## Лучше всего подходит для +2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. -- лент активности аккаунта; -- инструментов отладки и поддержки; -- поиска транзакций и квитанций по хешу; -- запросов по блокам и диапазонам блоков. +:::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`: -- Используйте [FastNear API](https://docs.fastnear.com/ru/api), когда нужны балансы, NFT, стейкинг или поиск по публичному ключу. -- Используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), когда нужно каноническое поведение узла или отправка транзакций. +```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. -- Публичные запросы по истории часто работают и без ключа. -- Если ваша интеграция стандартизирует один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. -- Для testnet используйте `https://tx.test.fastnear.com`; поиск квитанций там тоже доступен. -- Сервис предназначен для индексированного доступа к истории, а не для отправки транзакций. +:::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`: -- [Транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) — когда вы уже знаете идентификатор транзакции. -- [История аккаунта](https://docs.fastnear.com/ru/tx/account) — для лент активности и отладки аккаунта. -- [Поиск квитанции](https://docs.fastnear.com/ru/tx/receipt) — для расследования цепочки исполнения. -- [Диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) — когда нужен ограниченный по диапазону просмотр истории. +```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 +``` -## Нужен сценарий? +--- -Используйте [примеры API транзакций](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. +## testnet -## Устранение неполадок +- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/testnet +- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/testnet.md -### Я ожидал, что здесь можно отправлять транзакции +**Источник:** [https://docs.fastnear.com/ru/snapshots/testnet](https://docs.fastnear.com/ru/snapshots/testnet) -Это семейство предназначено для индексированных запросов по истории, а не для отправки подписанных транзакций. Для отправки используйте сырой RPC. +# Testnet -### Мне нужны пояснения по пагинации +## RPC-снапшот testnet -`/v0/account` использует непрозрачный `resume_token`, а `/v0/blocks` опирается на диапазон и лимит. Повторно используйте непрозрачные токены ровно в том виде, в каком их вернул сервис. +Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и нужен для более узких задач. -### Мне нужен только один канонический результат статуса транзакции из RPC +Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: -Используйте сырой RPC вместо индексированного семейства истории. +- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) +- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) +- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) +- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) +- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) +- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. ---- +**Выполните эту команду, чтобы скачать RPC-снапшот testnet:** -## Примеры Transactions API +:::info +Будут заданы следующие переменные окружения: +- `DATA_PATH=~/.near/data` — стандартный путь nearcore +- `CHAIN_ID=testnet` — явно выбирает данные testnet +::: -- HTML-маршрут: https://docs.fastnear.com/ru/tx/examples -- Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples.md +`RPC Testnet Snapshot » ~/.near/data`: -**Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) +```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 + +:::warning +**Требует много времени и места на диске.** + +Подготовьтесь к большому объёму загрузки и длительному времени выполнения. +::: + +Перед запуском скрипта загрузки можно задать следующие переменные окружения: + +- `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` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. + +По умолчанию скрипт ожидает следующий путь для данных: + +- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` + +**Выполните следующие команды, чтобы скачать архивный снапшот testnet:** + +1. Получите высоту блока последнего снапшота: + +`Latest archival testnet snapshot block`: + +```bash +LATEST=$(curl -s "https://snapshot.neardata.xyz/testnet/archival/latest.txt") +echo "Latest snapshot block: $LATEST" +``` + +2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. + +:::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 +``` + +--- + +## API переводов + +- HTML-маршрут: https://docs.fastnear.com/ru/transfers +- Markdown-маршрут: https://docs.fastnear.com/ru/transfers.md + +**Источник:** [https://docs.fastnear.com/ru/transfers](https://docs.fastnear.com/ru/transfers) + +# API переводов + +API переводов — это самая узкая поверхность истории FastNear. Стартуйте здесь, когда вопрос именно о движении активов, а не о более широкой истории исполнения за этим движением. + +## Базовый URL + +```bash title="Transfers API Mainnet" +https://transfers.main.fastnear.com +``` + +Эта поверхность сейчас доступна только в mainnet. `?network=testnet` не переключает бэкенд. + +## Используйте этот API, когда + +- нужна история переводов NEAR или FT-токенов по аккаунту +- строятся ленты кошелька или представления активности только по переводам +- отвечаете на вопросы поддержки или комплаенса про отправку и получение + +## Не стартуйте здесь, когда + +- нужна более широкая история транзакций или квитанций +- нужны балансы, активы, NFT или представления стейкинга +- нужен трафик testnet + +Используйте [Транзакции API](https://docs.fastnear.com/ru/tx) для более широкой истории исполнения и [FastNear API](https://docs.fastnear.com/ru/api) для ответов в стиле состояния аккаунта. + +## Минимально полезные входы + +- `account_id` +- опциональные фильтры: актив, направление, сумма или время +- нужно пользователю несколько событий или более длинный обзор истории + +## Рабочий цикл по умолчанию + +1. Начните с [Запроса переводов](https://docs.fastnear.com/ru/transfers/query), используя самый узкий набор фильтров, который всё ещё отвечает на вопрос. +2. Читайте возвращённые события как историю только переводов. Не пересобирайте полную хронологию квитанций, пока пользователь не попросит. +3. Переиспользуйте непрозрачный `resume_token` ровно в том виде, в каком его вернул сервис, при листании дальше. +4. Остановитесь, как только можете ответить, кто, что, когда и в каком активе отправил. + +## Аутентификация и доступность + +- Публичные чтения истории переводов часто работают и без ключа. +- Если вы стандартизируете один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. +- Ответы включают непрозрачный `resume_token` для пагинации. +- Сервис сейчас доступен только в mainnet. + +## Расширяйтесь, только если + +- пользователь начинает спрашивать о квитанциях или действиях кроме переводов +- пользователь хочет более широкий контекст транзакции за переводом +- пользователь на самом деле спрашивает о балансах или текущих активах, а не о движении + +Тогда расширяйтесь на [Транзакции API](https://docs.fastnear.com/ru/tx) или [FastNear API](https://docs.fastnear.com/ru/api), а не перегружайте представление переводов. + +## Типовые стартовые страницы + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) — лента по аккаунту с фильтрами по направлению, активу, сумме и времени + +## Нужен сценарий? + +Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для простых пошаговых сценариев: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. + +## Устранение неполадок + +### Нужны полные метаданные транзакции + +Переходите на [Транзакции API](https://docs.fastnear.com/ru/tx), если одной истории переводов недостаточно. + +### `resume_token` перестал работать + +Считайте токен непрозрачным и переиспользуйте его только с тем же эндпоинтом и фильтрами, которые его вернули. + +--- + +## Примеры Transfers API + +- HTML-маршрут: https://docs.fastnear.com/ru/transfers/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/transfers/examples.md + +**Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) + +## Готовый сценарий + +### Отфильтровать и листать ленту переводов одного аккаунта + +Используйте этот сценарий, когда история звучит так: «покажи мне осмысленную ленту переводов этого аккаунта, дай мне спокойно листать её дальше, и только потом, если нужно, помоги догнать одну строку до истории исполнения». + + Стратегия + Сначала соберите саму ленту аккаунта, а `receipt` поднимайте только тогда, когда одна строка действительно требует истории исполнения. + + 01POST /v0/transfers даёт первую страницу отфильтрованной ленты одного аккаунта. + 02jq поднимает сами строки плюс resume_token, чтобы вы могли продолжать листать ту же ленту. + 03POST /v0/receipt — это уже необязательный следующий шаг, если одной строке нужна её история исполнения. + +**Сеть** + +- только mainnet + +**Что вы делаете** + +- Забираете первую страницу одной отфильтрованной ленты переводов для выбранного аккаунта. +- Используете сами параметры ленты как главный учебный материал: `account_id`, `direction`, `asset_id`, `min_amount`, `desc` и `limit`. +- Сначала смотрите на строки ответа и `resume_token`, а не прыгаете сразу в историю исполнения. +- Только если какая-то строка действительно требует дополнительной истории, переиспользуете её `receipt_id` в Transactions API. + +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=YOUR_ACCOUNT_ID +ASSET_ID=native:near +MIN_AMOUNT=1000000000000000000000000 + +curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg asset_id "$ASSET_ID" \ + --arg min_amount "$MIN_AMOUNT" '{ + account_id: $account_id, + direction: "receiver", + asset_id: $asset_id, + min_amount: $min_amount, + desc: true, + limit: 10 + }')" \ + | tee /tmp/transfers-feed.json >/dev/null + +jq '{ + resume_token, + transfers: [ + .transfers[] + | { + transaction_id, + receipt_id, + asset_id, + amount, + human_amount, + usd_amount, + other_account_id, + block_height + } + ] +}' /tmp/transfers-feed.json +``` + +Необязательный следующий шаг: если одной строке всё-таки нужна её точка исполнения, поднимите её `receipt_id` и один раз перейдите в Transactions API. + +```bash +RECEIPT_ID="$(jq -r '.transfers[0].receipt_id' /tmp/transfers-feed.json)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' +``` + +**Зачем нужен следующий шаг?** + +Запрос переводов напрямую отвечает на первый вопрос: как сейчас выглядит отфильтрованная лента этого аккаунта и как её продолжать без потери места? Только после того как сама лента подскажет, какая строка действительно важна, имеет смысл переходить по `receipt_id` и забирать историю исполнения из `/tx`. + +## Частые задачи + +### Отфильтровать ленту переводов одного аккаунта + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом и самым узким стабильным набором фильтров для ленты: направление, актив, сумма и порядок. + +**Следующая страница при необходимости** + +- Уточните те же фильтры по активу или сумме, если в первой странице всё ещё слишком много лишних строк. + +**Остановитесь, когда** + +- Уже можно объяснить, как выглядит эта отфильтрованная лента и как листать её дальше. + +**Переходите дальше, когда** + +- Одна конкретная строка уже требует истории исполнения или следа по receipt. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Листать ленту переводов дальше и не потерять своё место + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. + +**Следующая страница при необходимости** + +- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. +- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. + +**Остановитесь, когда** + +- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. + +**Переходите дальше, когда** + +- Пользователь просит метаданные транзакции сверх самих переводов. +- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). + +### Перейти от истории переводов к полному расследованию транзакции + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. + +**Следующая страница при необходимости** + +- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. + +**Остановитесь, когда** + +- Уже определено правильное событие перевода и понятно, какой API открывать следующим. + +**Переходите дальше, когда** + +- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. + +## Частые ошибки + +- Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. +- Считать историю переводов полной историей исполнения. +- Переиспользовать `resume_token` с другими фильтрами. + +## Полезные связанные страницы + +- [Transfers API](https://docs.fastnear.com/ru/transfers) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) + +--- + +## Транзакции API + +- HTML-маршрут: https://docs.fastnear.com/ru/tx +- Markdown-маршрут: https://docs.fastnear.com/ru/tx.md + +**Источник:** [https://docs.fastnear.com/ru/tx](https://docs.fastnear.com/ru/tx) + +# Транзакции API + +Транзакции API — это поверхность истории. Используйте её, когда нужны индексированные представления транзакций или квитанций без постоянного опроса сырых RPC-методов и ручного объединения результатов. + +## Базовые URL + +```bash title="Transactions API Mainnet" +https://tx.main.fastnear.com +``` + +```bash title="Transactions API Testnet" +https://tx.test.fastnear.com +``` + +## Лучше всего подходит для + +- лент активности аккаунта; +- инструментов отладки и поддержки; +- поиска транзакций и квитанций по хешу; +- запросов по блокам и диапазонам блоков. + +## Когда его не стоит использовать + +- Используйте [FastNear API](https://docs.fastnear.com/ru/api), когда нужны балансы, NFT, стейкинг или поиск по публичному ключу. +- Используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), когда нужно каноническое поведение узла или отправка транзакций. + +## Аутентификация и доступность + +- Публичные запросы по истории часто работают и без ключа. +- Если ваша интеграция стандартизирует один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. +- Для testnet используйте `https://tx.test.fastnear.com`; поиск квитанций там тоже доступен. +- Сервис предназначен для индексированного доступа к истории, а не для отправки транзакций. + +## С чего обычно начинают + +- [Транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) — когда вы уже знаете идентификатор транзакции. +- [История аккаунта](https://docs.fastnear.com/ru/tx/account) — для лент активности и отладки аккаунта. +- [Поиск квитанции](https://docs.fastnear.com/ru/tx/receipt) — для расследования цепочки исполнения. +- [Диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) — когда нужен ограниченный по диапазону просмотр истории. + +## Нужен сценарий? + +Используйте [примеры API транзакций](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. + +## Устранение неполадок + +### Я ожидал, что здесь можно отправлять транзакции + +Это семейство предназначено для индексированных запросов по истории, а не для отправки подписанных транзакций. Для отправки используйте сырой RPC. + +### Мне нужны пояснения по пагинации + +`/v0/account` использует непрозрачный `resume_token`, а `/v0/blocks` опирается на диапазон и лимит. Повторно используйте непрозрачные токены ровно в том виде, в каком их вернул сервис. + +### Мне нужен только один канонический результат статуса транзакции из RPC + +Используйте сырой RPC вместо индексированного семейства истории. + +--- + +## Примеры Transactions API + +- HTML-маршрут: https://docs.fastnear.com/ru/tx/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples.md + +**Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) + +Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. + +## С чего начать + +Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. + +### У меня есть один хеш транзакции. Что вообще произошло? + +Используйте это расследование, когда история максимально простая: «мне прислали один хеш транзакции. Я просто хочу понять, сработала ли она, что именно сделала и в какой блок попала». + +Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. + + Стратегия + Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно. + + 01POST /v0/transactions даёт signer, receiver, типы действий, высоту блока и первую receipt-точку передачи. + 02RPC EXPERIMENTAL_tx_status нужен только для точной протокольной семантики успеха. + 03POST /v0/receipt имеет смысл только тогда, когда именно первая receipt становится новой опорной точкой. + +**Цель** + +- Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. + +Для этого зафиксированного примера: + +- хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- высота включающего блока: `194263342` +- ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` + +Простой человеческий ответ для этого случая такой: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. + +```mermaid +flowchart LR + H["Один tx hash
AdgNifPY..."] --> T["Получаем транзакцию"] + T --> A["Читаем signer, receiver, actions, block"] + A --> S["Короткая человеческая история"] + T -. "если потом понадобится" .-> R["Первая receipt
5GhZcpfK..."] +``` + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Читаемая история транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с хеша транзакции и печатаем signer, receiver, включающий блок, список действий и handoff в первую receipt | Даёт самый быстрый читаемый ответ на вопрос «что вообще сделала эта транзакция?» | +| Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | +| Переход к receipt | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | + +**Что должен включать полезный ответ** + +- кто подписал транзакцию +- какой аккаунт её получил +- какой тип действия она несла +- в какой блок попала +- одно простое предложение, которое объясняет транзакцию без receipt-жаргона + +#### Shell-сценарий: от хеша транзакции к человеческой истории + +Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. + +**Что вы делаете** + +- Получаете транзакцию по хешу и печатаете её основные поля. +- Подтверждаете финальный статус только если нужны точные RPC-семантики. +- Сохраняете первую receipt только как необязательный следующий шаг. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp +SIGNER_ACCOUNT_ID=mike.near +``` + +1. Получите транзакцию и распечатайте базовую историю. + +```bash +FIRST_RECEIPT_ID="$( + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/basic-tx-story.json \ + | jq -r '.transactions[0].transaction_outcome.outcome.status.SuccessReceiptId' +)" + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/basic-tx-story.json + +# Ожидаемый список действий: ["Transfer"] +# Ожидаемая первая receipt: 5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq +``` + +2. Если нужны точные RPC-семантики статуса, подтвердите их через `EXPERIMENTAL_tx_status`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' +``` + +3. Если следующий вопрос уже звучит как «что это была за первая receipt?», один раз перейдите по ней и остановитесь. + +```bash +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$FIRST_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, + transaction_hash: .receipt.transaction_hash + }' +``` + +Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. + +**Зачем нужен следующий шаг?** + +`POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. + +### Превратить один страшный receipt ID из логов в понятную человеческую историю + +Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. + +Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. + + Стратегия + Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой. + + 01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt. + 02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий. + 03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола». + +**Цель** + +- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. + +Для этого зафиксированного примера «страшный receipt ID из логов» такой: + +- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` +- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` +- signer: `mike.near` +- receiver: `global-counter.mike.near` +- высота блока транзакции: `194263342` +- высота блока исполнения receipt: `194263343` + +Человеческая история за этим receipt простая: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. + +```mermaid +flowchart LR + L["Один страшный receipt ID
5GhZcpfK..."] --> R["Ищем receipt"] + R --> T["Восстанавливаем tx hash
AdgNifPY..."] + T --> S["Читаем действия транзакции"] + S --> H["Человеческая история:
mike.near отправил 5 NEAR в global-counter.mike.near"] +``` + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и печатаем аккаунты, блок исполнения, флаг успеха и связанный хеш транзакции | Даёт самый короткий путь от сырого receipt ID к пониманию, что вообще за объект перед вами | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | +| Каноническое продолжение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | + +**Что должен включать полезный ответ** + +- какие аккаунты создали и исполнили квитанцию +- к какой транзакции относится эта квитанция +- что транзакция на самом деле сделала +- была ли квитанция главным событием или только шагом в большом каскаде +- одно предложение простым языком, которое можно без правок вставить коллеге в чат + +#### Shell-сценарий: от страшного receipt ID к человеческой истории + +## Ошибки и async + +Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и порядок callback-ов. + +Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. + +**Что вы делаете** + +- Сначала разрешаете receipt. +- Извлекаете `receipt.transaction_hash` через `jq`. +- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. +- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +``` + +1. Разрешите receipt и поймите, что за объект вы смотрите. + +```bash +TX_HASH="$( + curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | tee /tmp/receipt-lookup.json \ + | jq -r '.receipt.transaction_hash' +)" + +jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + receipt_type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block_height: .receipt.block_height, + transaction_hash: .receipt.transaction_hash, + tx_block_height: .receipt.tx_block_height + } +}' /tmp/receipt-lookup.json +``` + +2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/receipt-parent-transaction.json >/dev/null + +jq '{ + transaction: { + transaction_hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + tx_block_height: .transactions[0].execution_outcome.block_height, + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transfer_deposit_yocto: ( + .transactions[0].transaction.actions[0].Transfer.deposit // null + ) + }, + receipt_count: (.transactions[0].receipts | length) +}' /tmp/receipt-parent-transaction.json +``` + +3. Сведите это к одному человеческому предложению. + +```bash +jq -r ' + .transactions[0] as $tx + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." +' /tmp/receipt-parent-transaction.json +``` + +Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. + +В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. + +**Зачем нужен следующий шаг?** + +`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. + +### Доказать, что одно неудачное действие сорвало весь пакет + +Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? + +В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. + + Стратегия + Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов. + + 01POST /v0/transactions показывает упорядоченный пакет ровно в том виде, в каком его подписал signer. + 02RPC EXPERIMENTAL_tx_status показывает падающий FunctionCall и точную причину отказа на уровне протокола. + 03RPC view_account по предполагаемому новому аккаунту доказывает, закрепились ли вообще ранние create, fund и add-key действия. + +**Цель** + +- На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. + +**Официальные ссылки** + +- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) +- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) + +Этот зафиксированный сбой был получен в **testnet 18 апреля 2026 года**: + +- хеш транзакции: `CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M` +- аккаунт signer: `temp.mike.testnet` +- целевой новый аккаунт: `rollback-mo4vmkig.temp.mike.testnet` +- высота включающего блока: `246365118` +- хеш включающего блока: `6f5zTKDqQRwrxMywzvxeRvYcCERJmAnatJaqUEtQYUNM` +- порядок действий: `CreateAccount -> Transfer -> AddKey -> FunctionCall` +- упавший метод: `definitely_missing_method` +- RPC-ошибка: `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet` + +```mermaid +flowchart LR + T["Одна подписанная транзакция"] --> A["CreateAccount"] + A --> B["Transfer 0.01 NEAR"] + B --> C["AddKey"] + C --> D["FunctionCall definitely_missing_method()"] + D --> E["Сбой: CodeDoesNotExist"] + E --> R["Весь пакет не закрепился"] + R --> N["Новый аккаунт не появился"] + R --> K["Новый ключ не закрепился"] + R --> F["У получателя нет профинансированного состояния"] +``` + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Задуманный пакет | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированный хеш транзакции и печатаем упорядоченный список действий, получателя и метаданные включающего блока | Показывает, что именно signer пытался сделать, ещё до разговора о том, что закрепилось | +| Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | +| Доказательство по состоянию после исполнения | RPC [`query(view_account)`](https://docs.fastnear.com/ru/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | + +Перед shell-сценарием важно отметить одну деталь: индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. + +**Что должен включать полезный ответ** + +- точный порядок действий, который отправил signer +- какой индекс действия упал и почему +- высоту и хеш включающего блока для этого батча +- доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality +- короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` + +#### Shell-сценарий неудачной транзакции с пакетом действий + +Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. + +**Что вы делаете** -Начните с одного tx hash и сначала получите самый короткий читаемый ответ. +- Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. +- Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. +- Через один RPC-запрос к состоянию после исполнения доказываете, что новый аккаунт так и не появился после finality. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp +TX_BASE_URL=https://tx.test.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M +SIGNER_ACCOUNT_ID=temp.mike.testnet +NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +``` +1. Получите транзакцию и распечатайте задуманный пакет действий. + +```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, - receipt_count: (.transactions[0].receipts | length) - }' + | tee /tmp/failed-batch-transaction.json >/dev/null + +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height, + included_block_hash: .transactions[0].execution_outcome.block_hash + }, + batch: { + action_count: (.transactions[0].transaction.actions | length), + action_types: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + final_function_call_method_name: ( + .transactions[0].transaction.actions[3].FunctionCall.method_name + ) + }, + first_receipt_handoff: .transactions[0].transaction_outcome.outcome.status +}' /tmp/failed-batch-transaction.json + +# Ожидаемый порядок действий: +# 1. CreateAccount +# 2. Transfer +# 3. AddKey +# 4. FunctionCall ``` -Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. +2. Запросите RPC transaction status и посмотрите точную верхнеуровневую ошибку. -## С чего начать +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/failed-batch-rpc-status.json >/dev/null -Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. +jq '{ + final_execution_status: .result.final_execution_status, + failed_action_index: .result.status.Failure.ActionError.index, + failure: .result.status.Failure.ActionError.kind.FunctionCallError.CompilationError.CodeDoesNotExist +}' /tmp/failed-batch-rpc-status.json -### У меня есть один хеш транзакции. Что вообще произошло? +# Ожидаемый failed_action_index: 3 +# Ожидаемый failure account_id: rollback-mo4vmkig.temp.mike.testnet +``` -Используйте это расследование, когда история максимально простая: «мне прислали один хеш транзакции. Я просто хочу понять, сработала ли она, что именно сделала и в какой блок попала». +3. Запросите предполагаемый новый аккаунт после finality и докажите, что его всё ещё нет. -Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_account", + account_id: $account_id, + finality: "final" + } + }')" \ + | tee /tmp/failed-batch-view-account.json >/dev/null + +jq '{ + error: .error.cause.name, + message: .error.data, + requested_account_id: .error.cause.info.requested_account_id, + proof_block_height: .error.cause.info.block_height +}' /tmp/failed-batch-view-account.json + +# Ожидаемая ошибка: "UNKNOWN_ACCOUNT" +``` + +Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. + +**Зачем нужен следующий шаг?** + +Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. + +### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? + +Используйте это расследование, когда один вызов контракта залогировал успех, изменил своё локальное состояние, и даже верхнеуровневый RPC `status` выглядит успешным, но приложение всё равно сломалось, потому что позже упал отдельный cross-contract receipt. + +Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. Стратегия - Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно. + Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась. - 01POST /v0/transactions даёт signer, receiver, типы действий, высоту блока и первую receipt-точку передачи. - 02RPC EXPERIMENTAL_tx_status нужен только для точной протокольной семантики успеха. - 03POST /v0/receipt имеет смысл только тогда, когда именно первая receipt становится новой опорной точкой. + 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. + 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. + 03RPC call_function на роутере показывает, закрепилось ли собственное локальное изменение состояния из первой receipt. **Цель** -- Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. +- Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. -Для этого зафиксированного примера: +**Официальные ссылки** -- хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- высота включающего блока: `194263342` -- ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` +- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) +- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) -Простой человеческий ответ для этого случая такой: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. +Этот зафиксированный асинхронный сбой был получен в **testnet 18 апреля 2026 года**: + +- хеш транзакции: `AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz` +- аккаунт signer: `temp.mike.testnet` +- первый контракт-получатель: `seq-dr.mike.testnet` +- аккаунт detached-цели: `asyncfail-in2hwikn.temp.mike.testnet` +- блок включения транзакции: `246368568` +- успешная первая receipt: `6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS` в блоке `246368569` +- более поздняя упавшая receipt: `2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es` в блоке `246368570` +- первый метод: `kickoff_append` +- более поздний упавший метод: `append` +- верхнеуровневый RPC `status`: `SuccessValue` ```mermaid flowchart LR - H["Один tx hash
AdgNifPY..."] --> T["Получаем транзакцию"] - T --> A["Читаем signer, receiver, actions, block"] - A --> S["Короткая человеческая история"] - T -. "если потом понадобится" .-> R["Первая receipt
5GhZcpfK..."] + T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] + R --> S["Роутер сохраняет локальное состояние
kicked += late-failure"] + R --> D["Detached cross-contract receipt
append(...)"] + D --> F["Более поздний сбой
CodeDoesNotExist"] + S -. "состояние из первой receipt всё равно закрепилось" .-> K["kicked() всё ещё содержит late-failure"] ``` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Читаемая история транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с хеша транзакции и печатаем signer, receiver, включающий блок, список действий и handoff в первую receipt | Даёт самый быстрый читаемый ответ на вопрос «что вообще сделала эта транзакция?» | -| Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | -| Переход к receipt | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | +| Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | +| Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | +| Текущее состояние контракта | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `seq-dr.mike.testnet.kicked()` | Показывает, что локальное изменение состояния из первой receipt закрепилось, хотя более поздняя detached-receipt упала | + +Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не откатил более раннее изменение состояния роутера. **Что должен включать полезный ответ** -- кто подписал транзакцию -- какой аккаунт её получил -- какой тип действия она несла -- в какой блок попала -- одно простое предложение, которое объясняет транзакцию без receipt-жаргона +- что подписанная транзакция успешно передала управление в первую router-receipt +- что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` +- что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` +- что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился +- одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции -#### Shell-сценарий: от хеша транзакции к человеческой истории +#### Shell-сценарий более позднего сбоя receipt -Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. +Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». **Что вы делаете** -- Получаете транзакцию по хешу и печатаете её основные поля. -- Подтверждаете финальный статус только если нужны точные RPC-семантики. -- Сохраняете первую receipt только как необязательный следующий шаг. +- Читаете транзакцию и её таймлайн receipt из индексированного представления. +- Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. +- Читаете текущее состояние роутера, чтобы показать: локальный побочный эффект первой receipt закрепился. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp -SIGNER_ACCOUNT_ID=mike.near +TX_BASE_URL=https://tx.test.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz +SIGNER_ACCOUNT_ID=temp.mike.testnet +ROUTER_ACCOUNT_ID=seq-dr.mike.testnet +FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS +FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es ``` -1. Получите транзакцию и распечатайте базовую историю. +1. Получите транзакцию и распечатайте таймлайн receipt по порядку блоков. ```bash -FIRST_RECEIPT_ID="$( - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/basic-tx-story.json \ - | jq -r '.transactions[0].transaction_outcome.outcome.status.SuccessReceiptId' -)" +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/later-receipt-failure-transaction.json >/dev/null jq '{ transaction: { hash: .transactions[0].transaction.hash, signer_id: .transactions[0].transaction.signer_id, receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height + tx_block_height: .transactions[0].execution_outcome.block_height, + tx_handoff: .transactions[0].transaction_outcome.outcome.status }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/basic-tx-story.json + receipts: [ + .transactions[0].receipts[] + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + block_height: .execution_outcome.block_height, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system_transfer"), + status: .execution_outcome.outcome.status + } + ] +}' /tmp/later-receipt-failure-transaction.json -# Ожидаемый список действий: ["Transfer"] -# Ожидаемая первая receipt: 5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq +# На что смотреть: +# - первая receipt контракта на seq-dr.mike.testnet успешно прошла в блоке 246368569 +# - более поздняя receipt append(...) упала в блоке 246368570 ``` -2. Если нужны точные RPC-семантики статуса, подтвердите их через `EXPERIMENTAL_tx_status`. +2. Запросите RPC transaction status и сравните верхнеуровневую историю с более поздней упавшей receipt. ```bash curl -s "$RPC_URL" \ @@ -4202,890 +5753,998 @@ curl -s "$RPC_URL" \ wait_until: "FINAL" } }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' + | tee /tmp/later-receipt-failure-rpc.json >/dev/null + +jq \ + --arg first_receipt_id "$FIRST_RECEIPT_ID" \ + --arg failed_receipt_id "$FAILED_RECEIPT_ID" '{ + top_level_status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status, + first_contract_receipt: ( + .result.receipts_outcome[] + | select(.id == $first_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + logs: .outcome.logs, + status: .outcome.status + } + ), + later_failed_receipt: ( + .result.receipts_outcome[] + | select(.id == $failed_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + status: .outcome.status + } + ) + }' /tmp/later-receipt-failure-rpc.json + +# На что смотреть: +# - top_level_status всё ещё равен SuccessValue +# - первая receipt контракта залогировала dishonest_router:kickoff:late-failure +# - более поздняя receipt append(...) упала с CodeDoesNotExist ``` -3. Если следующий вопрос уже звучит как «что это была за первая receipt?», один раз перейдите по ней и остановитесь. +3. Прочитайте текущее состояние роутера и подтвердите, что локальный побочный эффект первой receipt закрепился. ```bash -curl -s "$TX_BASE_URL/v0/receipt" \ +curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$FIRST_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash - }' + --data "$(jq -nc --arg account_id "$ROUTER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "kicked", + args_base64: "e30=", + finality: "final" + } + }')" \ + | tee /tmp/later-receipt-failure-kicked.json >/dev/null + +jq '{ + kicked: (.result.result | implode | fromjson), + contains_late_failure: ((.result.result | implode | fromjson) | index("late-failure") != null) +}' /tmp/later-receipt-failure-kicked.json ``` -Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. +Этот последний read и есть практическое доказательство того, что локальное изменение из первой receipt закрепилось. Более поздняя упавшая receipt не откатила более ранний `kicked.push(...)` внутри роутера. **Зачем нужен следующий шаг?** -`POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. - -### Какая receipt выдала этот лог или event? +Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. -Используйте это расследование, когда история звучит так: «у меня есть один tx hash и один фрагмент лога, и мне нужно точно понять, какая именно receipt его выдала». +### Проследить асинхронную promise-цепочку и доказать порядок callback-ов -Это другой вопрос, чем более поздний сценарий «дошёл ли callback?». Здесь цель проще: привязать одну наблюдаемую строку лога к одному точному `receipt_id`, одному методу и одному исполнителю. +Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» Стратегия - Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога. + Смотрите на два хеша как на одну async-историю: докажите, что работа была жива, восстановите запрошенный порядок и сравните его с видимым downstream-state. - 01POST /v0/transactions даёт полный индексированный список receipt для одного tx hash, включая receipt-логи. - 02jq сужает этот список до receipt, в логах которых встречается нужный вам фрагмент. - 03Как только совпадение осталось одно, сохраняйте его receipt_id, executor и имя метода как точный ответ. + 01RPC call_function на view отложенной работы доказывает, что promise-работа действительно уже была жива до resume-шага. + 02POST /v0/transactions даёт оба block-anchor и точный порядок, который запросила resume-транзакция. + 03RPC EXPERIMENTAL_tx_status вместе с downstream-view доказывают, где callback-и реально выполнились и в каком видимом порядке. **Цель** -- Начать с одного mainnet tx hash и одного фрагмента лога и определить точную receipt, которая выдала этот лог. - -Для этого зафиксированного mainnet-примера используйте: - -- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` -- фрагмент лога: `Refund` -- ожидаемый matching `receipt_id`: `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` -- ожидаемый executor: `wrap.near` -- ожидаемый метод: `ft_resolve_transfer` +- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. -Эта транзакция полезна тем, что в ней есть две разные logged receipt внутри одной истории: +Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: -- ранний лог `Transfer ...` на receipt с `ft_transfer_call` -- более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` +- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом +- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке +- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и +- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору ```mermaid flowchart LR - T["Один tx hash
2KhhB1uD..."] --> L["Читаем все receipt-логи"] - L --> X["Ищем фрагмент:
Refund"] - X --> R["Точная receipt
9sLHQpaG..."] - R --> A["Ответ:
wrap.near / ft_resolve_transfer"] + Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] + H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] + R --> C["Async cross-contract callback-и"] + C --> B["Recorder state
beta"] + B --> A["Recorder state
alpha"] + A --> G["Recorder state
gamma"] + Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] + R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] + G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] ``` +Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. + +Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | +| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | +| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | +| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | +| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | +| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | +| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | +| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | + +**Что должен включать полезный ответ** + +- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» +- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования +- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался +- в каких блоках стали видны изменения состояния +- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования + +## Доказательства по SocialDB + +Эти примеры стартуют с читаемого состояния в NEAR Social и откатываются назад к точной записи, которая это состояние создала. + +### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB + +Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». + + Стратегия + Начните с читаемого значения поля, а затем превратите его field-level block в один receipt и одну транзакцию записи. + + 01NEAR Social POST /get даёт текущее значение profile.name и field-level :block. + 02POST /v0/block превращает этот блок в конкретный receipt и хеш транзакции mike.near -> social.near. + 03POST /v0/transactions доказывает payload записи, а RPC call_function get подтверждает, что поле и сейчас разрешается так же. + +**Цель** + +- Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. + +**Официальные ссылки** + +- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) +- [Живая поверхность чтения NEAR Social](https://api.near.social) + +Этот сценарий следует тому же рецепту доказательства, что и расследование по подписке, но добавляет ещё один важный нюанс SocialDB: для исторического доказательства `:block` на уровне конкретного поля обычно точнее, чем `:block` у родительского объекта. В этом живом примере `mike.near/profile/name` был записан на блоке `78675795`, тогда как более широкий объект `mike.near/profile` позже сдвинулся на другой блок из-за изменений в соседних полях. Роль FastNear в этом сценарии — превратить этот блок уровня поля в receipt, затем в транзакцию и потом в читаемый payload записи. + +Для этого живого примера текущее значение `profile.name` равно `Mike Purvis`, блок записи SocialDB на уровне поля равен `78675795`, ID receipt — `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN`, хеш исходной транзакции — `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ`, а внешний блок транзакции — `78675794`. + | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Атрибуция лога | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | -| Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | +| Семантическое чтение поля | NEAR Social `POST /get` | Читаем `mike.near/profile/name` с включёнными метаданными блока | Даёт читаемое значение поля и опорный `:block` SocialDB на уровне поля, где это значение было записано | +| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем блок уровня поля из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи уровня поля в конкретный receipt и хеш исходной транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который нёс `profile.name` и окружающие поля профиля в одном payload | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что поле и сейчас имеет это значение, хотя предыдущие шаги уже доказали конкретную историческую запись | **Что должен включать полезный ответ** -- какой `receipt_id` выдал лог -- какой контракт исполнил эту receipt -- какой метод там выполнился -- точную строку лога, которая совпала -- одно простое предложение вроде «лог `Refund` пришёл из `wrap.near` в receipt с методом `ft_resolve_transfer`» +- разрешается ли `mike.near/profile/name` сейчас в `Mike Purvis` +- высоту блока записи SocialDB на уровне поля (`78675795`) и объяснение, почему для этого вопроса этот якорь лучше, чем блок родительского профиля +- конкретный ID receipt и хеш исходной транзакции за этой записью +- доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload +- различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) -#### Shell-сценарий атрибуции лога +#### Shell-сценарий доказательства поля профиля в NEAR Social -Используйте этот сценарий, когда у вас уже есть tx hash и следующий вопрос звучит как «какая receipt это сказала?» +Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. **Что вы делаете** -- Один раз получаете транзакцию и сохраняете список её receipt. -- Фильтруете receipt по одному фрагменту лога. -- Останавливаетесь, как только у вас есть один точный `receipt_id`, один executor и одно имя метода. +- Читаете текущее поле `profile.name` из NEAR Social и сохраняете блок записи SocialDB на уровне поля. +- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`, несущей `profile.name`. +- Завершаете каноническим RPC-подтверждением того, что поле всё ещё разрешается в то же значение на `final`. ```bash +SOCIAL_API_BASE_URL=https://api.near.social TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL -LOG_FRAGMENT=Refund +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +PROFILE_FIELD=profile/name ``` -1. Получите транзакцию и сохраните список receipt. +1. Прочитайте поле профиля из NEAR Social и сохраните блок записи SocialDB на уровне поля. ```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/log-attribution-transaction.json >/dev/null +PROFILE_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-profile-name.json \ + | jq -r --arg account_id "$ACCOUNT_ID" \ + '.[ $account_id ].profile.name[":block"]' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + current_name: .[$account_id].profile.name[""], + field_block_height: .[$account_id].profile.name[":block"], + parent_profile_block_height: .[$account_id].profile[":block"] +}' /tmp/mike-profile-name.json + +# Ожидаемое current_name: "Mike Purvis" +# Ожидаемая высота блока уровня поля: 78675795 +``` + +2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. + +```bash +PROFILE_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-profile-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + profile_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-profile-block.json + +# Ожидаемый receipt ID: 2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN +# Ожидаемый хеш транзакции: 6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ ``` -2. Отфильтруйте список receipt до логов, которые содержат нужный вам фрагмент. +3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. ```bash -jq --arg fragment "$LOG_FRAGMENT" '{ +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-profile-transaction.json >/dev/null + +jq '{ transaction: { hash: .transactions[0].transaction.hash, signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height }, - matching_receipts: [ - .transactions[0].receipts[] - | select(any(.execution_outcome.outcome.logs[]?; contains($fragment))) + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - block_height: .execution_outcome.block_height, - logs: .execution_outcome.outcome.logs + method_name, + profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), + description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), + tags: ( + .args + | @base64d + | fromjson + | .data["mike.near"].profile.tags + | keys + ) } - ] -}' /tmp/log-attribution-transaction.json - -# На что смотреть: -# - фрагмент `Refund` совпадает ровно с одной receipt -# - это receipt 9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w -# - receipt исполнилась на wrap.near -# - имя метода — ft_resolve_transfer + ) +}' /tmp/mike-profile-transaction.json ``` -3. Если хотите увидеть все logged receipt рядом, распечатайте только те receipt, где вообще были логи. +4. Завершите каноническим подтверждением текущего состояния через raw RPC. ```bash -jq '{ - logged_receipts: [ - .transactions[0].receipts[] - | select((.execution_outcome.outcome.logs | length) > 0) - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - logs: .execution_outcome.outcome.logs - } - ] -}' /tmp/log-attribution-transaction.json +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)] + } | @base64' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-profile-rpc.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" '{ + finality: "final", + current_name: ( + .result.result + | implode + | fromjson + | .[$account_id].profile.name + ) +}' /tmp/mike-profile-rpc.json ``` -Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. +Этот последний шаг подтверждает, что поле и сейчас разрешается в `Mike Purvis`. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись установила это поле и какая транзакция несла эту запись. **Зачем нужен следующий шаг?** -Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. - -### Превратить один страшный receipt ID из логов в понятную человеческую историю +NEAR Social даёт семантическое значение поля. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемый payload профиля. RPC даёт каноническое подтверждение текущего состояния. -Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. +### Доказать, что `mike.near` подписался на `mob.near`, а затем восстановить транзакцию записи в SocialDB -Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. +Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». Стратегия - Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой. - - 01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt. - 02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий. - 03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола». + Начните с семантической связи подписки, а затем используйте блок записи как мост назад к одному receipt и одной транзакции. + + 01NEAR Social POST /get даёт читаемую связь подписки и SocialDB :block, где она была записана. + 02POST /v0/block превращает этот блок записи в конкретный receipt и хеш транзакции за этой связью. + 03POST /v0/transactions доказывает payload с graph.follow и index.graph, а RPC call_function get подтверждает, что связь и сейчас существует. **Цель** -- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. +- Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. -Для этого зафиксированного примера «страшный receipt ID из логов» такой: +**Официальные ссылки** -- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` -- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- высота блока транзакции: `194263342` -- высота блока исполнения receipt: `194263343` +- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) +- [Живая поверхность чтения NEAR Social](https://api.near.social) -Человеческая история за этим receipt простая: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. +Читаемая связь подписки приходит из данных NEAR Social, а не из FastNear. Ключевой мост здесь — метаданные SocialDB `:block`: они указывают на блок, в котором исполнился receipt, записавший это значение. Этот блок не совпадает с блоком, в который была включена внешняя транзакция. Роль FastNear в этом сценарии — превратить эту высоту блока в receipt, затем в транзакцию и, наконец, в читаемую историю исполнения. -```mermaid -flowchart LR - L["Один страшный receipt ID
5GhZcpfK..."] --> R["Ищем receipt"] - R --> T["Восстанавливаем tx hash
AdgNifPY..."] - T --> S["Читаем действия транзакции"] - S --> H["Человеческая история:
mike.near отправил 5 NEAR в global-counter.mike.near"] -``` +Для этого живого примера текущая связь выглядит как `mike.near -> mob.near`, блок записи SocialDB равен `79574924`, ID receipt — `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th`, хеш исходной транзакции — `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb`, а внешний блок транзакции — `79574923`. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и печатаем аккаунты, блок исполнения, флаг успеха и связанный хеш транзакции | Даёт самый короткий путь от сырого receipt ID к пониманию, что вообще за объект перед вами | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | -| Каноническое продолжение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | +| Семантическое чтение связи | NEAR Social `POST /get` | Читаем `mike.near/graph/follow/mob.near` с включёнными метаданными блока | Даёт читаемую связь подписки и опорный `:block` из SocialDB, где это значение было записано | +| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем высоту блока из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи SocialDB в конкретный receipt и хеш исходной транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который записал и `graph.follow`, и записи `index.graph` | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что связь подписки существует и сейчас, хотя предыдущие шаги уже доказали конкретную историческую запись | **Что должен включать полезный ответ** -- какие аккаунты создали и исполнили квитанцию -- к какой транзакции относится эта квитанция -- что транзакция на самом деле сделала -- была ли квитанция главным событием или только шагом в большом каскаде -- одно предложение простым языком, которое можно без правок вставить коллеге в чат +- существует ли сейчас связь подписки `mike.near -> mob.near` +- высоту блока записи SocialDB (`79574924`) и объяснение, почему это блок исполнения receipt +- конкретный ID receipt и хеш исходной транзакции за этой записью +- доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` +- различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) -#### Shell-сценарий: от страшного receipt ID к человеческой истории +#### Shell-сценарий доказательства подписки в NEAR Social + +Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. + +**Что вы делаете** + +- Читаете текущую связь подписки из NEAR Social и сохраняете блок записи SocialDB. +- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`. +- Завершаете каноническим RPC-подтверждением того, что связь всё ещё существует на `final`. ```bash +SOCIAL_API_BASE_URL=https://api.near.social TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +TARGET_ACCOUNT_ID=mob.near ``` -1. Разрешите receipt и поймите, что за объект вы смотрите. +1. Прочитайте связь подписки из NEAR Social и сохраните блок записи SocialDB. ```bash -TX_HASH="$( - curl -s "$TX_BASE_URL/v0/receipt" \ +FOLLOW_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | tee /tmp/receipt-lookup.json \ - | jq -r '.receipt.transaction_hash' + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + keys: [($account_id + "/graph/follow/" + $target_account_id)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-follow-edge.json \ + | jq -r --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" \ + '.[ $account_id ].graph.follow[ $target_account_id ][":block"]' )" -jq '{ - receipt: { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - receipt_type: .receipt.receipt_type, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash, - tx_block_height: .receipt.tx_block_height - } -}' /tmp/receipt-lookup.json +jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + follow_edge: .[$account_id].graph.follow[$target_account_id][""], + follow_block_height: .[$account_id].graph.follow[$target_account_id][":block"] +}' /tmp/mike-follow-edge.json + +# Ожидаемая высота блока записи: 79574924 ``` -2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. +2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. + +```bash +FOLLOW_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$FOLLOW_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-follow-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" + +jq --arg account_id "$ACCOUNT_ID" '{ + follow_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-follow-block.json + +# Ожидаемый receipt ID: UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th +# Ожидаемый хеш транзакции: FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +``` + +3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. ```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/receipt-parent-transaction.json >/dev/null + --data "$(jq -nc --arg tx_hash "$FOLLOW_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-follow-transaction.json >/dev/null jq '{ transaction: { - transaction_hash: .transactions[0].transaction.hash, + hash: .transactions[0].transaction.hash, signer_id: .transactions[0].transaction.signer_id, receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transfer_deposit_yocto: ( - .transactions[0].transaction.actions[0].Transfer.deposit // null - ) + included_block_height: .transactions[0].execution_outcome.block_height }, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/receipt-parent-transaction.json + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + follow_edge: (.args | @base64d | fromjson | .data["mike.near"].graph.follow["mob.near"]), + index_graph: ( + .args + | @base64d + | fromjson + | .data["mike.near"].index.graph + | fromjson + | map(select(.value.accountId == "mob.near")) + ) + } + ) +}' /tmp/mike-follow-transaction.json ``` -3. Сведите это к одному человеческому предложению. +4. Завершите каноническим подтверждением текущего состояния через raw RPC. ```bash -jq -r ' - def zeros($n): - reduce range(0; $n) as $i (""; . + "0"); - def yocto_to_near($yocto): - ($yocto | tostring) as $digits - | if ($digits | length) <= 24 then - ("0." + zeros(24 - ($digits | length)) + $digits) - else - ($digits[0:(($digits | length) - 24)] + "." + $digits[-24:]) - end - | sub("0+$"; "") - | sub("\\.$"; ""); - .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил \(yocto_to_near($tx.transaction.actions[0].Transfer.deposit)) NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." -' /tmp/receipt-parent-transaction.json -``` - -Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + keys: [($account_id + "/graph/follow/" + $target_account_id)] + } | @base64' +)" -В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/mike-follow-rpc.json >/dev/null -**Зачем нужен следующий шаг?** +jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ + finality: "final", + current_follow_edge: ( + .result.result + | implode + | fromjson + | .[$account_id].graph.follow[$target_account_id] + ) +}' /tmp/mike-follow-rpc.json +``` -`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. +Этот последний шаг подтверждает, что связь подписки существует и сейчас. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись создала эту связь и какая транзакция несла эту запись. -## Ошибки и async +**Зачем нужен следующий шаг?** -Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. +NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. -Используйте этот раздел, когда уже понятно, что транзакция жила дольше одной receipt, и следующий вопрос относится уже к форме исполнения, а не к простому поиску объекта. +### Какая транзакция записала `mob.near/widget/Profile`? -### Доказать, что одно неудачное действие сорвало весь пакет +Используйте это расследование, когда вопрос звучит так: «я уже знаю, что `mob.near/widget/Profile` существует прямо сейчас. Какая именно транзакция записала ту версию виджета, на которую я смотрю?» -Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? +Это естественное tx-продолжение к более лёгкому RPC-сценарию про виджет и к provenance-NFT-сценарию. Задача здесь прямолинейная: -В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. +- стартуем с собственного SocialDB-блока виджета +- превращаем этот блок в один `mob.near -> social.near` receipt +- восстанавливаем исходную транзакцию +- декодируем payload `set` и доказываем, что он действительно нёс исходник виджета Стратегия - Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов. + Смотрите на write-block виджета как на весь мост сразу: блок в receipt, receipt в транзакцию, транзакцию в исходник. - 01POST /v0/transactions показывает упорядоченный пакет ровно в том виде, в каком его подписал signer. - 02RPC EXPERIMENTAL_tx_status показывает падающий FunctionCall и точную причину отказа на уровне протокола. - 03RPC view_account по предполагаемому новому аккаунту доказывает, закрепились ли вообще ранние create, fund и add-key действия. + 01POST /v0/block начинает с блока виджета и сужает его до одного receipt mob.near -> social.near. + 02POST /v0/transactions превращает этот receipt в один читаемый payload set с исходником виджета. + 03RPC call_function get — это финальное подтверждение текущего состояния, что виджет и сейчас существует. **Цель** -- На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. +- Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. **Официальные ссылки** -- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) -- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) - -Этот зафиксированный сбой был получен в **testnet 18 апреля 2026 года**: +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -- хеш транзакции: `CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M` -- аккаунт signer: `temp.mike.testnet` -- целевой новый аккаунт: `rollback-mo4vmkig.temp.mike.testnet` -- высота включающего блока: `246365118` -- хеш включающего блока: `6f5zTKDqQRwrxMywzvxeRvYcCERJmAnatJaqUEtQYUNM` -- порядок действий: `CreateAccount -> Transfer -> AddKey -> FunctionCall` -- упавший метод: `definitely_missing_method` -- RPC-ошибка: `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet` +Для этого живого якоря: -```mermaid -flowchart LR - T["Одна подписанная транзакция"] --> A["CreateAccount"] - A --> B["Transfer 0.01 NEAR"] - B --> C["AddKey"] - C --> D["FunctionCall definitely_missing_method()"] - D --> E["Сбой: CodeDoesNotExist"] - E --> R["Весь пакет не закрепился"] - R --> N["Новый аккаунт не появился"] - R --> K["Новый ключ не закрепился"] - R --> F["У получателя нет профинансированного состояния"] -``` +- аккаунт: `mob.near` +- виджет: `Profile` +- блок записи в SocialDB: `86494825` +- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` +- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- внешний блок транзакции: `86494824` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Задуманный пакет | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированный хеш транзакции и печатаем упорядоченный список действий, получателя и метаданные включающего блока | Показывает, что именно signer пытался сделать, ещё до разговора о том, что закрепилось | -| Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | -| Доказательство по состоянию после исполнения | RPC [`query(view_account)`](https://docs.fastnear.com/ru/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | - -Перед shell-сценарием важно отметить одну деталь: индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. +| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `86494825` с `with_receipts: true`, а затем фильтруем его обратно до `mob.near -> social.near` | Превращает блок записи виджета в один конкретный receipt и один конкретный хеш транзакции | +| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `mob.near/widget/Profile` | +| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | **Что должен включать полезный ответ** -- точный порядок действий, который отправил signer -- какой индекс действия упал и почему -- высоту и хеш включающего блока для этого батча -- доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality -- короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` +- высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции +- конкретный receipt ID и хеш исходной транзакции за этой записью виджета +- доказательство, что payload записи был `set` с `mob.near/widget/Profile` +- одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» -#### Shell-сценарий неудачной транзакции с пакетом действий +#### Shell-сценарий доказательства записи виджета в NEAR Social -Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. +## Трассировка расчёта + +Это самое насыщенное расследование на странице: один живой расчёт NEAR Intents от верхнеуровневой транзакции до receipts и событий, которые его объясняют. + +Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. **Что вы делаете** -- Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. -- Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. -- Через один RPC-запрос к состоянию после исполнения доказываете, что новый аккаунт так и не появился после finality. +- Стартуете с блока последней записи виджета. +- Переиспользуете эту высоту в FastNear block receipts, чтобы получить receipt и мост к транзакции. +- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы декодировать записанный исходник виджета. +- Завершаете сырым RPC-подтверждением, что виджет всё ещё существует сейчас. ```bash -TX_BASE_URL=https://tx.test.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com -TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M -SIGNER_ACCOUNT_ID=temp.mike.testnet -NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mob.near +WIDGET_NAME=Profile +WIDGET_BLOCK_HEIGHT=86494825 ``` -1. Получите транзакцию и распечатайте задуманный пакет действий. +1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. ```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/failed-batch-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height, - included_block_hash: .transactions[0].execution_outcome.block_hash - }, - batch: { - action_count: (.transactions[0].transaction.actions | length), - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - final_function_call_method_name: ( - .transactions[0].transaction.actions[3].FunctionCall.method_name - ) - }, - first_receipt_handoff: .transactions[0].transaction_outcome.outcome.status -}' /tmp/failed-batch-transaction.json +WIDGET_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$WIDGET_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mob-widget-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" -# Ожидаемый порядок действий: -# 1. CreateAccount -# 2. Transfer -# 3. AddKey -# 4. FunctionCall +jq --arg account_id "$ACCOUNT_ID" '{ + widget_write_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mob-widget-block.json + +# Ожидаемый receipt ID: CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6 +# Ожидаемый хеш транзакции: 9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia ``` -2. Запросите RPC transaction status и посмотрите точную верхнеуровневую ошибку. +2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. ```bash -curl -s "$RPC_URL" \ +curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/failed-batch-rpc-status.json >/dev/null + --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mob-widget-transaction.json >/dev/null jq '{ - final_execution_status: .result.final_execution_status, - failed_action_index: .result.status.Failure.ActionError.index, - failure: .result.status.Failure.ActionError.kind.FunctionCallError.CompilationError.CodeDoesNotExist -}' /tmp/failed-batch-rpc-status.json - -# Ожидаемый failed_action_index: 3 -# Ожидаемый failure account_id: rollback-mo4vmkig.temp.mike.testnet + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].transaction.actions[0].FunctionCall + | { + method_name, + widget_source_head: ( + .args + | @base64d + | fromjson + | .data["mob.near"].widget.Profile[""] + | split("\n")[0:12] + ) + } + ) +}' /tmp/mob-widget-transaction.json ``` -3. Запросите предполагаемый новый аккаунт после finality и докажите, что его всё ещё нет. +Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `9QDup...` вызвала `social.near set` и пронесла в `args` настоящий исходник `mob.near/widget/Profile`. + +3. Завершите каноническим подтверждением текущего состояния через сырой RPC. ```bash +SOCIAL_GET_ARGS_BASE64="$( + jq -nr --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + } | @base64' +)" + curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ + --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { - request_type: "view_account", - account_id: $account_id, + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, finality: "final" } }')" \ - | tee /tmp/failed-batch-view-account.json >/dev/null - -jq '{ - error: .error.cause.name, - message: .error.data, - requested_account_id: .error.cause.info.requested_account_id, - proof_block_height: .error.cause.info.block_height -}' /tmp/failed-batch-view-account.json + | tee /tmp/mob-widget-rpc.json >/dev/null -# Ожидаемая ошибка: "UNKNOWN_ACCOUNT" +jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + finality: "final", + current_widget_head: ( + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:5] + ) +}' /tmp/mob-widget-rpc.json ``` -Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. +Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. **Зачем нужен следующий шаг?** -Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. - -### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? +Блок записи виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. -Используйте это расследование, когда один вызов контракта залогировал успех, изменил своё локальное состояние, и даже верхнеуровневый RPC `status` выглядит успешным, но приложение всё равно сломалось, потому что позже упал отдельный cross-contract receipt. +### Проследить один расчёт NEAR Intents и показать, что именно произошло -Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. +Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». Стратегия - Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась. + Смотрите на один расчёт как на читаемую трассу, а не как на теорию протокола с первой строки. - 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. - 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. - 03Как только эти два представления сходятся на одном и том же разрезе истории, остановитесь. Этот пример держится за сохранённые исторические свидетельства, а не за живой read состояния роутера. + 01POST /v0/transactions даёт каркас расчёта: входную точку, первые downstream-контракты и ранние логи. + 02POST /v0/block переиспользует тот же якорь, когда нужен контекст включающего блока вокруг этого расчёта. + 03RPC EXPERIMENTAL_tx_status нужен там, где уже требуется канонический DAG по receipt и имена событий, которые доказывают реальное движение активов. **Цель** -- Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. +- Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. **Официальные ссылки** -- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) -- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) +- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) +- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) +- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) -Этот зафиксированный асинхронный сбой был получен в **testnet 18 апреля 2026 года**: +Для живой трассировки ниже используйте этот фиксированный якорь расчёта из mainnet, зафиксированный **18 апреля 2026 года**: -- хеш транзакции: `AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz` -- аккаунт signer: `temp.mike.testnet` -- первый контракт-получатель: `seq-dr.mike.testnet` -- аккаунт detached-цели: `asyncfail-in2hwikn.temp.mike.testnet` -- блок включения транзакции: `246368568` -- успешная первая receipt: `6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS` в блоке `246368569` -- более поздняя упавшая receipt: `2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es` в блоке `246368570` -- первый метод: `kickoff_append` -- более поздний упавший метод: `append` -- верхнеуровневый RPC `status`: `SuccessValue` +- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` +- аккаунт `signer` и `receiver`: `intents.near` +- высота включающего блока: `194573310` + +Быстрая полезная модель здесь простая: + +- `intents.near` выполняет входную точку расчёта +- последующие receipt расходятся по контрактам, которые реально переводят или выводят активы +- журналы событий показывают, какие действия расчёта случились, через имена вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` + +Для этого конкретного расчёта короткий человеческий ответ уже неплохой: + +- `intents.near` вызвал `execute_intents` +- downstream receipt ушли в `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` +- трасса выдала события `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` + +Если нужен протокольный фон, базовая форма сопоставления здесь — это двухсторонний `token_diff` intent: одна сторона подписывает желаемую разницу по активам, вторая сторона подписывает противоположную разницу, а затем совпавшая пара отправляется на расчёт. Но для рабочего расследования обычно понятнее начать с одной реальной транзакции расчёта и читать доказательства прямо с цепочки. ```mermaid flowchart LR - T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] - R --> D["Detached cross-contract receipt
append(...)"] - D --> F["Более поздний сбой
CodeDoesNotExist"] - T -. "внешняя транзакция всё равно завершается" .-> X["RPC top-level status
SuccessValue"] + T["Одна mainnet-транзакция
4cfei8p4..."] --> I["intents.near
execute_intents"] + I --> R["Последующие receipt"] + R --> C["Подключаются другие контракты"] + R --> E["Появляются журналы событий"] + E --> TD["token_diff"] + E --> IE["intents_executed"] + E --> MT["mt_transfer / mt_withdraw"] ``` +Публичных FastNear-поверхностей уже достаточно, чтобы ответить на практический вопрос: + | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | -| Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | - -Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. +| Каркас расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и печатаем основную транзакцию плюс первые последующие receipt | Даёт самый быстрый читаемый ответ на вопрос «во что этот расчёт пошёл дальше?» | +| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Показывает, в какой блок попал расчёт и какие receipt этой транзакции видны в блоке | +| Каноническое доказательство по receipt и событиям | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` плюс логи `EVENT_JSON` | Даёт протокольно-канонический DAG, `executor_id` и имена событий, которые объясняют, что реально сделал расчёт | **Что должен включать полезный ответ** -- что подписанная транзакция успешно передала управление в первую router-receipt -- что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` -- что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` -- что RPC всё ещё показывает верхнеуровневый `SuccessValue`, хотя более поздняя detached-receipt упала -- одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции +- какую входную точку расчёта вы увидели на `intents.near` +- какие downstream-контракты и методы появились сразу после неё +- какие семейства событий выпустила трассировка +- одно итоговое предложение простым языком о том, что произошло -#### Shell-сценарий более позднего сбоя receipt +Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. -Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». +#### Shell-сценарий расчёта NEAR Intents + +Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. **Что вы делаете** -- Читаете транзакцию и её таймлайн receipt из индексированного представления. -- Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. -- Останавливаетесь, как только эти два сохранённых представления сходятся на одном и том же разрезе истории. +- Получаете читаемую историю расчёта через Transactions API. +- Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. +- Подтверждаете канонический DAG по receipt и семейства событий через `EXPERIMENTAL_tx_status`. ```bash -TX_BASE_URL=https://tx.test.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com -TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz -SIGNER_ACCOUNT_ID=temp.mike.testnet -FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS -FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 +INTENTS_SIGNER_ID=intents.near ``` -1. Получите транзакцию и распечатайте таймлайн receipt по порядку блоков. +1. Начните с самой транзакции расчёта и восстановите первый читаемый поток последующих receipt. ```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/later-receipt-failure-transaction.json >/dev/null +INTENTS_BLOCK_HASH="$( + curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$INTENTS_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/intents-transaction.json \ + | jq -r '.transactions[0].execution_outcome.block_hash' +)" jq '{ transaction: { hash: .transactions[0].transaction.hash, signer_id: .transactions[0].transaction.signer_id, receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - tx_handoff: .transactions[0].transaction_outcome.outcome.status + included_block_height: .transactions[0].execution_outcome.block_height }, - receipts: [ - .transactions[0].receipts[] + receipt_flow: [ + .transactions[0].receipts[:6][] | { receipt_id: .receipt.receipt_id, receiver_id: .receipt.receiver_id, block_height: .execution_outcome.block_height, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system_transfer"), - status: .execution_outcome.outcome.status + methods: ( + [.receipt.receipt.Action.actions[]?.FunctionCall.method_name] + | map(select(. != null)) + ), + first_log: (.execution_outcome.outcome.logs[0] // null) } ] -}' /tmp/later-receipt-failure-transaction.json +}' /tmp/intents-transaction.json +``` -# На что смотреть: -# - первая receipt контракта на seq-dr.mike.testnet успешно прошла в блоке 246368569 -# - более поздняя receipt append(...) упала в блоке 246368570 +Этот первый шаг уже даёт сильный операторский ответ: `intents.near` выполнил транзакцию расчёта, а ранние последующие receipt показывают, какие контракты вошли в каскад сразу после этого. + +2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. + +```bash +curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_id "$INTENTS_BLOCK_HASH" '{ + block_id: $block_id, + with_receipts: true, + with_transactions: false + }')" \ + | tee /tmp/intents-block.json >/dev/null + +jq --arg tx_hash "$INTENTS_TX_HASH" '{ + block_height: .block.block_height, + block_hash: .block.block_hash, + tx_receipts: [ + .block_receipts[] + | select(.transaction_hash == $tx_hash) + | { + receipt_id, + predecessor_id, + receiver_id, + block_height + } + ] +}' /tmp/intents-block.json ``` -2. Запросите RPC transaction status и сравните верхнеуровневую историю с более поздней упавшей receipt. +3. Подтвердите канонический DAG по receipt и извлеките семейства событий через RPC. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + --arg tx_hash "$INTENTS_TX_HASH" \ + --arg sender_account_id "$INTENTS_SIGNER_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "EXPERIMENTAL_tx_status", params: { tx_hash: $tx_hash, - sender_account_id: $signer_account_id, + sender_account_id: $sender_account_id, wait_until: "FINAL" } }')" \ - | tee /tmp/later-receipt-failure-rpc.json >/dev/null + | tee /tmp/intents-rpc.json >/dev/null -jq \ - --arg first_receipt_id "$FIRST_RECEIPT_ID" \ - --arg failed_receipt_id "$FAILED_RECEIPT_ID" '{ - top_level_status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status, - first_contract_receipt: ( - .result.receipts_outcome[] - | select(.id == $first_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - logs: .outcome.logs, - status: .outcome.status - } - ), - later_failed_receipt: ( - .result.receipts_outcome[] - | select(.id == $failed_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - status: .outcome.status - } - ) - }' /tmp/later-receipt-failure-rpc.json +jq '{ + final_execution_status: .result.final_execution_status, + receipts_outcome: [ + .result.receipts_outcome[:6][] + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + first_log: (.outcome.logs[0] // null) + } + ] +}' /tmp/intents-rpc.json -# На что смотреть: -# - top_level_status всё ещё равен SuccessValue -# - первая receipt контракта залогировала dishonest_router:kickoff:late-failure -# - более поздняя receipt append(...) упала с CodeDoesNotExist +jq -r ' + .result.receipts_outcome[] + | .outcome.logs[] + | select(startswith("EVENT_JSON:")) + | capture("event\":\"(?[^\"]+)\"").event +' /tmp/intents-rpc.json | sort -u ``` -Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. - **Зачем нужен следующий шаг?** -Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. - -### Дошёл ли callback вообще? - -Используйте это расследование, когда одна транзакция запустила downstream-работу на другом контракте, а настоящий вопрос звучит не как «успешно ли отработал receiver?», а как «вернулся ли callback обратно в исходный контракт?» - -Это самый короткий полезный сценарий про callback на странице: - -- стартуйте с одного tx hash -- найдите downstream-receipt на другом контракте -- найдите более поздний callback-receipt, который вернулся в исходный контракт -- остановитесь, как только доказаны сам факт callback и его результат - - Стратегия - Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а. - - 01POST /v0/transactions показывает downstream-вызов и более поздний receipt, который возвращается в исходный контракт. - 02jq сужает этот список receipt до одного downstream-вызова и одного callback-receipt. - 03RPC EXPERIMENTAL_tx_status нужен только как дополнительное подтверждение, если вам важны канонический результат callback-а и его логи. - -**Цель** +`POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. -- Доказать на одном фиксированном mainnet-примере, что `wrap.near` отправил `ft_transfer_call` в `v2.ref-finance.near`, receiver выполнил `ft_on_transfer`, а затем `wrap.near` получил callback `ft_resolve_transfer` обратно. +## Частые задачи -Этот фиксированный mainnet-пример с callback был замечен **19 апреля 2026 года**: +### Найти одну транзакцию -- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` -- аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` -- исходный контракт: `wrap.near` -- downstream-receiver: `v2.ref-finance.near` -- верхнеуровневый метод: `ft_transfer_call` -- downstream-метод: `ft_on_transfer` -- callback-метод: `ft_resolve_transfer` -- блок транзакции: `194692298` -- блок downstream-receipt: `194692300` -- блок callback-receipt: `194692301` +**Начните здесь** -```mermaid -flowchart LR - T["Одна mainnet-транзакция
ft_transfer_call на wrap.near"] --> D["Downstream-receipt
v2.ref-finance.near.ft_on_transfer"] - D --> F["Receiver упал
E51: contract paused"] - F --> C["Callback-receipt обратно в wrap.near
ft_resolve_transfer"] - C --> R["Лог refund на wrap.near"] -``` +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. -Здесь хорошо видна одна полезная деталь NEAR: downstream-сбой не означает, что callback исчез. В этом случае `v2.ref-finance.near` уронил свой `ft_on_transfer`, но `wrap.near` всё равно позже получил `ft_resolve_transfer` и залогировал refund. +**Следующая страница при необходимости** -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | -| Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. +- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. -**Что должен включать полезный ответ** +**Остановитесь, когда** -- какой контракт получил downstream-вызов -- какой метод выполнился на downstream-контракте -- вернулся ли более поздний receipt в исходный контракт -- какой callback-метод там выполнился и в каком блоке -- одно простое предложение вроде «receiver упал, но исходный контракт всё равно получил callback и завершил перевод» +- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. -#### Shell-сценарий проверки callback-а +**Переходите дальше, когда** -Используйте этот сценарий, когда нужен один конкретный proof callback-а без превращения страницы в полный курс по теории promises. +- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. +- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. -**Что вы делаете** +### Исследовать квитанцию -- Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. -- Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. -- Останавливаетесь сразу, как только можете сказать, вернулся ли callback и что он сделал. +**Начните здесь** -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL -SENDER_ACCOUNT_ID=7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72 -ORIGIN_CONTRACT_ID=wrap.near -DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near -``` +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. -1. Получите транзакцию и сохраните receipt-цепочку. +**Следующая страница при необходимости** -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/callback-check-transaction.json >/dev/null -``` +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. +- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. -2. Сначала ответьте на самый короткий полезный вопрос: вернулся ли callback вообще? +**Остановитесь, когда** -```bash -jq --arg origin "$ORIGIN_CONTRACT_ID" ' - [ - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - ] | length > 0 -' /tmp/callback-check-transaction.json -``` +- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. -3. Если ответ `true`, распечатайте downstream-receipt вместе с callback-receipt. +**Переходите дальше, когда** -```bash +- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. -CALLBACK_RECEIPT_ID="$( - jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | .receipt.receipt_id - ) - ' /tmp/callback-check-transaction.json -)" +### Посмотреть недавнюю активность аккаунта -jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - tx_block_height: .transactions[0].execution_outcome.block_height - }, - downstream_receipt: ( - first( - .transactions[0].receipts[] - | select(.receipt.receiver_id == $downstream) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - status: .execution_outcome.outcome.status, - block_height: .execution_outcome.block_height - } - ) - ), - callback_receipt: ( - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: .receipt.receipt.Action.actions[0].FunctionCall.method_name, - logs: .execution_outcome.outcome.logs, - status: .execution_outcome.outcome.status, - block_height: .execution_outcome.block_height - } - ) - ), - callback_ran: ( - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | true - ) // false - ) -}' /tmp/callback-check-transaction.json +**Начните здесь** -# На что смотреть: -# - downstream-receipt выполнил ft_on_transfer на v2.ref-finance.near -# - более поздний callback-receipt выполнил ft_resolve_transfer на wrap.near -# - callback_ran равно true, даже несмотря на downstream-сбой -``` +- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. -4. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. +**Следующая страница при необходимости** -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg sender_account_id "$SENDER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $sender_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/callback-check-rpc.json >/dev/null +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. -jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ - top_level_status: .result.status, - callback_receipt: ( - first( - .result.receipts_outcome[] - | select(.id == $callback_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - logs: .outcome.logs, - status: .outcome.status - } - ) - ) -}' /tmp/callback-check-rpc.json +**Остановитесь, когда** -# На что смотреть: -# - downstream ft_on_transfer receipt упал на v2.ref-finance.near -# - wrap.near всё равно позже получил ft_resolve_transfer -# - лог callback-а показывает refund обратно отправителю -``` +- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. -**Зачем нужен следующий шаг?** +**Переходите дальше, когда** + +- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). +- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). + +### Восстановить ограниченное окно по блокам + +**Начните здесь** + +- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. +- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. -Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. +**Остановитесь, когда** + +- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. + +**Переходите дальше, когда** + +- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). ## Частые ошибки - Пытаться отправлять транзакцию через history API вместо сырого RPC. - Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. - Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». +- Повторно использовать непрозрачные токены пагинации с другим эндпоинтом или другим набором фильтров. ## Полезные связанные страницы @@ -5093,68 +6752,138 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ - [RPC Reference](https://docs.fastnear.com/ru/rpc) - [FastNear API](https://docs.fastnear.com/ru/api) - [NEAR Data API](https://docs.fastnear.com/ru/neardata) -- [Berry Club: живая доска и один путь исторической реконструкции](https://docs.fastnear.com/ru/tx/examples/berry-club) -- [OutLayer: связать одну транзакцию запроса с одним ответом воркера](https://docs.fastnear.com/ru/tx/examples/outlayer) -- [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) --- -## Berry Club: как читать живую доску и разбирать одну эпоху +## Berry Club: как восстанавливать исторические доски - HTML-маршрут: https://docs.fastnear.com/ru/tx/examples/berry-club - Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples/berry-club.md **Источник:** [https://docs.fastnear.com/ru/tx/examples/berry-club](https://docs.fastnear.com/ru/tx/examples/berry-club) -{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} +{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как восстанавливать доски Berry Club через FastNear. Он отделяет текущее состояние из get_lines от исторического разбора через диапазоны блоков, историю аккаунта, раскрытие транзакций и проигрывание draw-аргументов. */} + +# Berry Club: как восстанавливать исторические доски -# Berry Club: как читать живую доску и разбирать одну эпоху +Используйте этот разбор, когда вопрос звучит так: «как Berry Club выглядел в определённую эпоху и какие `draw`-вызовы сделали доску именно такой?» -Используйте этот walkthrough, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. +Это read-only разбор из семейства Transactions examples. Если нужна только доска прямо сейчас, используйте `get_lines` и остановитесь. Если нужно объяснить, как доска пришла к такому виду, переключайтесь на историю блоков, историю аккаунта, раскрытые `draw`-вызовы и проигрывание. -Начните с живой доски. Если этого уже достаточно для ответа, на этом можно остановиться. + Стратегия + Сначала прочитайте живую доску, затем ограничьте эпоху и только после этого проигрывайте draw-вызовы, которые её объясняют. -Переходите к Transactions API только тогда, когда вопрос становится историческим: «как Berry Club выглядел в одной более ранней эпохе и какие `draw`-вызовы сделали доску именно такой?» + 01RPC call_function get_lines даёт текущую доску 50x50 и показывает, как выглядит «сейчас». + 02POST /v0/blocks вместе с POST /v0/account ограничивают одну эпоху и дают кандидатные хеши draw. + 03POST /v0/transactions раскрывает эти draw-вызовы, чтобы их можно было проиграть в исторические контрольные точки. -Карточка живой доски: запрашивает `berryclub.ek.near` `get_lines` через mainnet RPC и рендерит текущую сетку 50x50 в интерфейсе документации. +Держите рядом: -## 1. Прочитайте живую доску +- [js.fastnear.com](https://js.fastnear.com/) +- [fastnear/js-monorepo](https://github.com/fastnear/js-monorepo) +- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +- [Transactions API: диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) +- [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) -Это самый короткий полезный запрос: +В этом руководстве история Berry Club разбирается только на mainnet. Снимки ниже собраны из воспроизводимых данных mainnet, которые уже сохранены в этом репозитории. -```bash -ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" +## Короткая версия -curl -sS https://rpc.mainnet.fastnear.com \ - -H 'content-type: application/json' \ - --data "{ - \"jsonrpc\": \"2.0\", - \"id\": \"berry-live-board\", - \"method\": \"query\", - \"params\": { - \"request_type\": \"call_function\", - \"finality\": \"final\", - \"account_id\": \"berryclub.ek.near\", - \"method_name\": \"get_lines\", - \"args_base64\": \"$ARGS_BASE64\" - } - }" | jq '.result | {block_height, line_count: (.result | implode | fromjson | length)}' +Berry Club даёт чистый view текущего состояния через `get_lines`, но не даёт готового эндпоинта вида «доска на блоке N». + +Из-за этого задача делится на две части: + +- используйте RPC `call_function`, когда вопрос звучит как «как доска выглядит сейчас?» +- используйте индексированную историю, когда вопрос звучит как «какие записи привели к этой доске?» +- используйте архивный RPC только тогда, когда нужно напрямую материализовать уже известную контрольную точку + +```mermaid +flowchart TD + A["RPC call_function: get_lines"] --> B["Текущая доска 50x50"] + C["Transactions API: /v0/blocks"] --> D["Ограничить эпоху"] + D --> E["/v0/account для berryclub.ek.near"] + E --> F["Кандидатные хеши draw-транзакций"] + F --> G["Раскрытие через /v0/transactions"] + G --> H["Проигрывание draw-записей в историческую доску"] +``` + +## Почему Berry Club хорошо учит истории в NEAR + +Berry Club удобно показывает обе стороны задачи: + +- чистое чтение текущего состояния через `get_lines` +- длинную историю вызовов `draw` с обычными аргументами `FunctionCall` +- формат доски, который легко декодировать и рендерить обычным JavaScript + +Это очень NEAR-подобная форма: один view-метод для текущего состояния, один write-метод для изменений и индексированная история, когда нужно объяснить, как это состояние вообще появилось. + +## 1. Сначала прочитайте текущую доску + +Живое демо использует `berryclub.ek.near` и читает доску через view-вызов `get_lines`: + +```javascript +await near.view({ + contractId: 'berryclub.ek.near', + methodName: 'get_lines', + args: { + lines: [...Array(50).keys()], + }, +}); +``` + +Это путь текущего состояния. Он не отвечает на вопрос, как доска пришла к такому виду. + +| Вопрос | Лучшая поверхность | Почему | +| --- | --- | --- | +| как доска выглядит сейчас? | [RPC `call_function`](https://docs.fastnear.com/ru/rpc/contract/call-function) | контракт уже отдаёт текущее состояние через `get_lines` | +| какие `draw` были в этой эпохе? | [`/v0/account`](https://docs.fastnear.com/ru/tx/account) + [`/v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | индексированная история даёт ограниченный набор записей и раскрытые аргументы | +| как доска выглядела в известной контрольной точке? | архивный RPC или полное проигрывание | можно напрямую материализовать состояние из архива или восстановить его самому по историческим записям | + +## 2. Как декодировать `get_lines` в сетку 50x50 + +Полезная часть Berry Club-разметки из `js.fastnear.com` — это декодер строк: + +- каждая строка приходит в base64 +- её нужно декодировать в байты +- первые 4 байта нужно пропустить +- дальше цвета читаются как 32-битные little-endian значения каждые 8 байт + +```javascript +function decodeLine(encodedLine) { + const bytes = Buffer.from(encodedLine, 'base64'); + const colors = []; + + for (let offset = 4; offset < bytes.length; offset += 8) { + colors.push(bytes.readUInt32LE(offset) & 0xffffff); + } + + return colors; +} ``` -Этот запрос отдаёт текущую доску 50x50 прямо из контракта. Дальше нужно только декодировать каждую base64-строку в 50 цветов пикселей. +Примените это ко всем 50 строкам — и получите полную сетку 50x50, готовую к рендерингу. -## 2. Восстановите одну более раннюю эпоху +## 3. Ограничьте эпоху, которую хотите изучить -Когда нужна история, держите путь коротким: +Сначала ограничьте эпоху, прежде чем искать draw-записи. Проверочный снимок запуска в этом репозитории находится на блоке `21898354`, а средний снимок — на блоке `97601515`. -1. ограничьте одну эпоху -2. получите кандидатные `draw`-транзакции для `berryclub.ek.near` -3. раскройте эти хеши -4. проиграйте массивы `pixels` от старых к новым +Сначала зафиксируйте ближайший диапазон блоков: + +```bash +curl -sS https://tx.main.fastnear.com/v0/blocks \ + -H 'content-type: application/json' \ + --data '{ + "from_block_height": 21898350, + "to_block_height": 21898355, + "desc": false, + "limit": 5 + }' +``` -В этом примере используется узкое окно вокруг блока `97601515`: +Затем переключитесь на историю аккаунта и запросите активность Berry Club внутри ограниченного диапазона блоков: ```bash curl -sS https://tx.main.fastnear.com/v0/account \ @@ -5166,14 +6895,21 @@ curl -sS https://tx.main.fastnear.com/v0/account \ "is_real_receiver": true, "from_tx_block_height": 97576515, "to_tx_block_height": 97601516, - "desc": false, - "limit": 200 - }' | jq '.account_txs | map({transaction_hash, tx_block_height}) | .[-5:]' + "desc": true, + "limit": 40 + }' ``` -Если окно ещё нужно подобрать, сначала можно использовать [`/v0/blocks`](https://docs.fastnear.com/ru/tx/blocks). Это не часть основного Berry Club-сценария. +Здесь полезна именно такая последовательность: + +- `/v0/blocks` помогает понять соседство по высотам блоков +- `/v0/account` возвращает кандидатные хеши транзакций Berry Club внутри этого диапазона + +## 4. Раскройте транзакции и оставьте только `draw` -Раскройте кандидатные хеши и оставьте только верхнеуровневые вызовы `draw`: +Когда кандидатные хеши уже есть, раскройте их и оставьте только верхнеуровневые вызовы `draw`, где получатель — `berryclub.ek.near`. + +Аргументы вызова — это обычные данные `FunctionCall` вида `{ pixels: [...] }`: ```bash curl -sS https://tx.main.fastnear.com/v0/transactions \ @@ -5187,279 +6923,329 @@ curl -sS https://tx.main.fastnear.com/v0/transactions \ | select(.transaction.receiver_id == "berryclub.ek.near") | .transaction.actions[]?.FunctionCall | select(.method_name == "draw") - | {method_name, args: (.args | @base64d | fromjson)}' + | { + method_name, + args: (.args | @base64d | fromjson) + }' ``` -Затем проиграйте массивы `pixels` от старых к новым: +Это даёт всё, что нужно для проигрывания: + +- какая транзакция записывала пиксели +- какие координаты были затронуты +- какие цвета были записаны + +## 5. Проиграйте исторические `draw`-вызовы в доску + +Для полного проигрывания держите в памяти массив 50x50 и применяйте раскрытые транзакции `draw` от старых к новым. ```javascript const board = Array.from({ length: 50 }, () => Array(50).fill(0)); -for (const drawTx of drawTransactionsOldestFirst) { - for (const pixel of drawTx.args.pixels) { +function applyDraw(boardState, drawArgs) { + for (const pixel of drawArgs.pixels) { if (pixel.x < 0 || pixel.x >= 50 || pixel.y < 0 || pixel.y >= 50) { continue; } - board[pixel.y][pixel.x] = pixel.color; + boardState[pixel.y][pixel.x] = pixel.color; } } + +for (const drawTx of drawTransactionsOldestFirst) { + applyDraw(board, drawTx.args); +} ``` -В этом и состоит исторический паттерн. У Berry Club нет готового эндпоинта «доска на блоке N», поэтому старые эпохи восстанавливаются проигрыванием `draw`-записей. +Важно не путать два разных пути: -## Связанные руководства +- `get_lines` — это текущее состояние +- `tx/account` плюс `tx/transactions` — это материал для проигрывания -- [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) -- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) -- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +## 6. Готовые контрольные точки по эпохам + +Галерея ниже использует уже сохранённые данные снимков, собранные из mainnet-истории Berry Club: + +- `launch` — последний успешный `draw` в пределах первых 24 часов после первого успешного draw +- `mid` — последний успешный `draw` не позже средней временной точки всей истории Berry Club +- `recent` — последний успешный `draw`, который увидел скрипт при пересборке снимков + +Галерея снимков: контрольные точки launch, mid и recent из сохранённого `src/data/berryClubSnapshots.json`. + +Сейчас эти снимки привязаны к таким транзакциям: + +- `launch`: `BDNFpCpLXjBrgjR6z6wCZmB9EWdHnVMdqau3iTWTRE5H` на блоке `21898354` +- `mid`: `Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ` на блоке `97601515` +- `recent`: `8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL` на блоке `194588754` + +## Куда идти за подписанными взаимодействиями + +Эта страница должна оставаться в режиме чтения. + +Если нужны живые подписанные сценарии для `draw` и `buy_tokens`, переходите сюда: + +- [js.fastnear.com](https://js.fastnear.com/) +- [Berry Club example в fastnear/js-monorepo](https://github.com/fastnear/js-monorepo/tree/main/examples/static/berryclub) + +Именно там уместны кошелёк и подписанные действия. Эта страница посвящена историческому восстановлению. --- -## OutLayer: что сделала эта пара request/resolution? +## OutLayer: как проследить один запрос от вызова до callback - HTML-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer - Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer.md **Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) -{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как прочитать один caller-side запрос OutLayer вместе с более поздним worker-side resolution, а затем разобрать finish-receipts только если это действительно нужно. */} +{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как использовать FastNear RPC и Transactions API, чтобы разбирать живое исполнение OutLayer в терминах NEAR. Он отделяет видимый request/worker/callback-поток, который уже можно трассировать через FastNear, от задокументированного внутреннего пути yield/resume и CKD/MPC. */} -# OutLayer: что сделала эта пара request/resolution? +# OutLayer: как проследить один запрос от вызова до callback -Используйте этот walkthrough, когда вопрос звучит так: «что сделал этот запрос OutLayer, какое более позднее resolution к нему относится и нужно ли мне вообще смотреть на finish-receipts?» +Используйте этот разбор, когда вопрос звучит так: «я вижу OutLayer в цепочке. Какая транзакция открыла работу, какая более поздняя транзакция пришла от воркера и где проявились callback, списание и возврат средств?» -Оставайтесь в пределах публичных chain-данных: прочитайте request tx, прочитайте более поздний resolution tx и только потом решайте, нужны ли вам finish-receipts. +Это продвинутый разбор асинхронного исполнения в семействе Transactions examples. Держите NEAR-рамку первой: один `FunctionCall` со стороны вызывающего, одна более поздняя транзакция со стороны воркера, и квитанции только тогда, когда действительно нужно разбирать фазу завершения. -## Компактный shell-сценарий + Стратегия + Сначала найдите caller-транзакцию и worker-транзакцию, а receipts подключайте только тогда, когда настоящим вопросом становится finish-путь. -Эта пара работала 18 апреля 2026 года: + 01POST /v0/account — самый быстрый способ найти caller-side и worker-side хеши из одной и той же истории. + 02POST /v0/transactions раскрывает оба хеша и показывает читаемые request, worker-resolution и ранние логи. + 03Только после этого имеет смысл разбирать callback, списание и refund на уровне receipts или уходить в точные RPC-проверки идентичности. -- caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` -- worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` +Полезные ссылки: -### 1. Основной ответ: сразу раскройте request tx и resolution tx вместе +- [История аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +- [Просмотр аккаунта](https://docs.fastnear.com/ru/rpc/account/view-account) +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) -```bash -TX_BASE_URL=https://tx.main.fastnear.com -REQUEST_TX_HASH=AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4 -WORKER_TX_HASH=AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs +## Короткая версия -curl -sS "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg request_tx_hash "$REQUEST_TX_HASH" --arg worker_tx_hash "$WORKER_TX_HASH" '{ - tx_hashes: [$request_tx_hash, $worker_tx_hash] - }')" \ - | tee /tmp/outlayer-pair.json >/dev/null +Если вы видите активность OutLayer в цепочке, практические вопросы обычно такие: -jq '{ - transactions: [ - .transactions[] - | { - hash: .transaction.hash, - signer_id: .transaction.signer_id, - receiver_id: .transaction.receiver_id, - methods: [ - .transaction.actions[] - | .FunctionCall.method_name? - | select(. != null) - ], - first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) - } - ] -}' /tmp/outlayer-pair.json -``` +- какая транзакция создала асинхронную единицу работы? +- какая более поздняя транзакция пришла от воркера? +- где именно проявились callback, списание и возврат средств? -Это и есть основной ответ: один request tx, один более поздний resolution tx и читаемые signer-, receiver-, method- и log-доказательства для обеих транзакций. +Это не вопрос о текущем состоянии. Это вопрос об истории исполнения. -### Необязательное продолжение: Что сделал finish-путь? +Полезный ход через FastNear — связать одну транзакцию `request_execution` со стороны вызывающего с одной транзакцией разрешения со стороны воркера, а к receipt переходить только на этапе завершения. -```bash -jq --arg worker_tx_hash "$WORKER_TX_HASH" ' - .transactions[] - | select(.transaction.hash == $worker_tx_hash) - | { - worker_tx_hash: .transaction.hash, - receipts: [ - .receipts[] - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - actions: [.receipt.receipt.Action.actions[] | keys[0]], - logs: .execution_outcome.outcome.logs - } - ] - } -' /tmp/outlayer-pair.json +```mermaid +sequenceDiagram + autonumber + participant Caller as "Вызывающая сторона" + participant Outlayer as "outlayer.*" + participant Worker as "worker.outlayer.*" + participant Near as "исполняющая среда NEAR" + + Caller->>Outlayer: FunctionCall request_execution + Outlayer-->>Near: создаёт асинхронную работу и контекст учёта + Worker->>Outlayer: resolve_execution или submit_execution_output_and_resolve + Outlayer-->>Near: логи завершения, путь callback, списание, возвраты ``` -Смотрите на самое маленькое читаемое доказательство finish-пути: +Всё это уже видно сегодня через FastNear и RPC. -- `FunctionCall`-receipts, которые продолжают finish-путь -- логи списания вроде `[[yNEAR charged: "..."]]` -- последующие `Transfer`-receipts, которые похожи на refund или settlement-движение +## 1. Раскройте одну транзакцию запроса и одно разрешение воркера -Именно здесь receipts становятся нужной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции здесь относятся друг к другу?» +Если хотите сразу увидеть всю форму потока, начните с уже известной пары хешей и раскройте оба. -### Необязательный шаг: сначала найдите два хеша +Эта пара работала 18 апреля 2026 года: -```bash -curl -sS "$TX_BASE_URL/v0/account" \ +- `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` — `request_execution` со стороны вызывающего +- `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` — `submit_execution_output_and_resolve` со стороны воркера + +```bash title="Раскройте хеш запроса и хеш разрешения воркера" +curl -sS https://tx.main.fastnear.com/v0/transactions \ -H 'content-type: application/json' \ - --data '{"account_id":"outlayer.near","desc":true,"limit":10}' \ - | jq '{ - txs_count, - recent_hashes: [.account_txs[:10][] | .transaction_hash] - }' + --data '{ + "tx_hashes":[ + "AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4", + "AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs" + ] + }' | jq '.transactions[] | { + hash: .transaction.hash, + signer: .transaction.signer_id, + receiver: .transaction.receiver_id, + actions: [.transaction.actions[] | keys[0]], + logs: (.receipts[0].execution_outcome.outcome.logs[:2]) + }' ``` -Используйте это только если вы ещё не знаете пару хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` превращает их в читаемый ответ. +В этом выборочном выводе: -## Полезные связанные страницы +- хеш запроса шёл от `solarflux.near` к `outlayer.near` +- в логах фигурировал разрешённый проект: `zavodil.near/near-email` +- хеш воркера шёл от `worker.outlayer.near` к `outlayer.near` +- в логах воркера было `Stored pending output` и `Resolving execution ... (combined flow)` -- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) -- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) -- [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) +Этого уже достаточно для видимой истории в терминах NEAR: исходный `FunctionCall` создал асинхронную единицу работы, позже воркер вернулся как отдельный подписант, а контракт разрешил результат в цепочке. ---- +Если копировать с этой страницы только одну команду, то именно эту. -## Расширенный поиск записи SocialDB +## 2. Найдите два нужных хеша сами -- HTML-маршрут: https://docs.fastnear.com/ru/tx/socialdb-proofs -- Markdown-маршрут: https://docs.fastnear.com/ru/tx/socialdb-proofs.md +Если пары хешей у вас ещё нет, переключитесь на [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account). -**Источник:** [https://docs.fastnear.com/ru/tx/socialdb-proofs](https://docs.fastnear.com/ru/tx/socialdb-proofs) +```bash title="Недавняя mainnet-активность для outlayer.near" +curl -sS https://tx.main.fastnear.com/v0/account \ + -H 'content-type: application/json' \ + --data '{"account_id":"outlayer.near","desc":true}' \ + | jq '{txs_count, first: .account_txs[0]}' +``` -# Расширенный поиск записи SocialDB +18 апреля 2026 года эта поверхность показывала более 5 000 трассированных транзакций для `outlayer.near`, а самый свежий выборочный хеш был таким: -Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому поиску записи. +```text +AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs +``` -Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" +Этот хеш не был исходным пользовательским запросом. Это уже было последующее действие со стороны воркера. -## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` +Именно поэтому история аккаунта — правильный первый поиск: здесь задача не в том, чтобы описать контракт целиком, а в том, чтобы найти две конкретные транзакции в одной истории исполнения. -Используйте этот сценарий, когда читаемый факт уже звучит как "текущее `profile.name` равно `Mike Purvis`", а остаётся только вопрос, какая запись сделала это поле истинным. +```mermaid +flowchart TD + A["Transactions API: /v0/account для outlayer.*"] --> B["Найдите недавние хеши транзакций"] + B --> C["Хеш request_execution со стороны вызывающего"] + B --> D["Хеш разрешения со стороны воркера"] + C --> E["Раскройте оба через /v0/transactions"] + D --> E + E --> F["Проверьте логи, request_id, project/source, callback-receipt, списание и возвраты"] +``` -Здесь достаточно сохранить один важный нюанс SocialDB: для исторического provenance правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. +## 3. Разберите фазу callback и возврата средств -Для этого живого якоря: +Если нужно пройти дальше, чем просто «воркер вернул результат», посмотрите список receipt у раскрытой воркерской транзакции. -- текущее `profile.name`: `Mike Purvis` -- блок записи SocialDB на уровне поля: `78675795` -- receipt ID: `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN` -- хеш исходной транзакции: `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ` -- внешний блок транзакции: `78675794` +```bash title="Показать последующие действия на уровне receipt для разрешения воркером" +curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H 'content-type: application/json' \ + --data '{"tx_hashes":["AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs"]}' \ + | jq '.transactions[0] | { + hash: .transaction.hash, + receipts: [ + .receipts[] | { + predecessor: .receipt.predecessor_id, + receiver: .receiver_id, + actions: [.receipt.receipt.Action.actions[] | keys[0]], + logs: .execution_outcome.outcome.logs + } + ] + }' +``` -### Shell-сценарий +На что смотреть: -1. Прочитайте поле из NEAR Social и сохраните block записи на уровне поля. +- `FunctionCall:on_execution_response` +- логи списания вроде `[[yNEAR charged: "..."]]` +- события завершения вроде `execution_completed` +- последующие receipt `Transfer` -```bash -SOCIAL_API_BASE_URL=https://api.near.social -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near -PROFILE_FIELD=profile/name +Здесь понятие receipt как раз становится правильной абстракцией: не в начале урока, а тогда, когда уже отлаживается реальный путь завершения. -PROFILE_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-profile-name.json \ - | jq -r --arg account_id "$ACCOUNT_ID" \ - '.[ $account_id ].profile.name[":block"]' -)" +## 4. Подтвердите контракт, если нужна точная проверка -jq --arg account_id "$ACCOUNT_ID" '{ - current_name: .[$account_id].profile.name[""], - field_block_height: .[$account_id].profile.name[":block"], - parent_profile_block_height: .[$account_id].profile[":block"] -}' /tmp/mike-profile-name.json +Если нужна точная проверка аккаунта и `code_hash`, используйте сырой RPC. Это шаг для проверки идентичности, а не для восстановления истории исполнения. + +```bash title="Mainnet: view_account для outlayer.near" +curl -sS https://rpc.mainnet.fastnear.com \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "id":"1", + "method":"query", + "params":{ + "request_type":"view_account", + "finality":"final", + "account_id":"outlayer.near" + } + }' | jq '.result | {amount, locked, code_hash, storage_usage}' ``` -2. Переиспользуйте этот block уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. +```bash title="Testnet: view_account для outlayer.testnet" +curl -sS https://rpc.testnet.fastnear.com \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "id":"1", + "method":"query", + "params":{ + "request_type":"view_account", + "finality":"final", + "account_id":"outlayer.testnet" + } + }' | jq '.result | {amount, locked, code_hash, storage_usage}' +``` -```bash -PROFILE_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-profile-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" +По состоянию на 18 апреля 2026 года оба контракта возвращали один и тот же `code_hash`: -jq --arg account_id "$ACCOUNT_ID" '{ - profile_receipt: ( - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | { - receipt_id, - transaction_hash, - block_height, - tx_block_height - } - ) - ) -}' /tmp/mike-profile-block.json +```text +94uKcoDB3QbEpxDj1xsw9CQwu9bAY1PoVPr2BZYRRv4K ``` -3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. +Это сильный сигнал, что на обеих сетях развёрнут один и тот же бинарник контракта. -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-profile-transaction.json >/dev/null +## 5. Что происходит внутри? -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), - description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), - tags: ( - .args - | @base64d - | fromjson - | .data["mike.near"].profile.tags - | keys - ) - } - ) -}' /tmp/mike-profile-transaction.json -``` +Видимая история выше — это то, что NEAR-разработчику нужно сначала. Более глубокий слой объясняет, почему этот поток вообще интересен. + +### Что можно наблюдать уже сейчас + +Через FastNear и RPC уже видно: + +- вызывающую транзакцию `request_execution` +- воркерскую `resolve_execution` или `submit_execution_output_and_resolve` +- завершающие receipt, где материализуются callback, списание и возврат средств + +Интеграция вашего контракта при этом остаётся обычной асинхронной композицией в NEAR: вы вызываете `outlayer.*`, а потом обрабатываете свой callback. + +### Что задокументировано как внутренний механизм + +Документация OutLayer описывает более глубокий внутренний слой: `outlayer.*` использует семантику NEAR `yield/resume` как свою внутреннюю асинхронную границу, внешняя работа выполняется в TEE-воркерах, а защищённые секреты проходят через отдельный путь доверия, где TEE-keystore получает DAO-gated CKD через MPC signer. + +Для NEAR-разработчика здесь важна точность: мы не говорим, что ваш вызывающий контракт сам пишет `promise_yield_create`. Примитивы `yield/resume` в NEAR работают только в рамках одного и того же аккаунта, поэтому если этот механизм используется здесь, то yielding и resuming делает `outlayer.*`, а не исходный вызывающий контракт. Для сырой модели выполнения смотрите [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features). -Это и есть весь паттерн lookup-а: читаемое значение, block уровня поля, мост через receipt и payload транзакции. +Документация Secrets / CKD описывает этот путь keystore как двухуровневый: сначала keystore получает derivation key через DAO-gated путь к MPC, а затем использует уже полученную derivation capability для защищённых секретов во время исполнений приложения. Это объяснение доверительной модели, а не утверждение, что каждое обычное исполнение OutLayer делает новый DAO -> MPC round trip. -Тот же мост работает и для других читаемых значений SocialDB: +Публичный gateway-аккаунт для этого пути keystore / DAO в наших текущих публичных chain-данных всё ещё не подтверждён, поэтому эту часть надо держать в корзине «задокументировано внутри», а не в корзине «уже наблюдается сейчас». + +```mermaid +flowchart TB + subgraph Observable["Что уже можно наблюдать через FastNear / RPC"] + Caller["Пользователь или вызывающий контракт"] -->|FunctionCall request_execution| Entry["outlayer.*"] + Entry -->|поздняя активность воркера| Worker["worker.outlayer.*"] + Worker -->|resolve_execution или submit_execution_output_and_resolve| Entry + Entry -->|on_execution_response, списание, refund-переводы| Finish["Callback / finish receipt"] + end + + subgraph Internal["Что задокументировано как внутренний слой"] + Yield["внутренняя yield-точка в outlayer.*"] --> TEE["TEE-воркер исполняет WASM"] + TEE -->|resume с результатом| Yield + TEE --> Keystore["TEE-keystore для защищённых секретов"] + Keystore -->|задокументированный DAO-gated request_key| DAO["DAO gateway (задокументирован)"] + DAO -->|задокументированный CKD request| MPC["v1.signer / v1.signer-prod.testnet"] + MPC -->|derivation key для keystore| Keystore + end + + Entry -. та же логическая история исполнения .-> Yield + + classDef observable fill:#e7f1ff,stroke:#2563eb,color:#0f172a; + classDef internal fill:#fff4e5,stroke:#b45309,color:#0f172a; + class Caller,Entry,Worker,Finish observable; + class Yield,TEE,Keystore,DAO,MPC internal; +``` -- вариант для связи подписки: `mike.near -> mob.near`, блок `79574924`, tx `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- вариант для исходника виджета: `mob.near/widget/Profile`, блок `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +## Куда читать дальше -Ключевая идея не меняется: начните с читаемого значения и его write-block, восстановите receipt `*.near -> social.near` из блока, а затем декодируйте payload `social.near set` из исходной транзакции. +- [Transactions API](https://docs.fastnear.com/ru/tx) для истории аккаунта, receipt и раскрытия транзакций +- [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features) для семантики `yield/resume` в NEAR +- [Асинхронная модель](https://docs.fastnear.com/ru/transaction-flow/async-model) для лексики promise и callback +- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) для задокументированного контрактного интерфейса +- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) для задокументированного пути keystore, DAO и MPC --- diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index acda451..b8ab3b3 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -2,65 +2,93 @@ # Примеры RPC -Используйте эту страницу, когда нужен быстрый точный ответ через RPC. Начните с одного чтения, а к транзакциям и сырому состоянию переходите только если простого запроса уже недостаточно. +Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. -## Быстрый старт +## Отправка и отслеживание транзакции -Если вы только открыли эту страницу, начните с одного точного чтения аккаунта. +Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=near +### Отправить транзакцию и затем проследить её от хеша до финального исполнения -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - finality: "final", - account_id: $account_id - } - }')" \ - | jq '.result | { - amount, - locked, - code_hash, - storage_usage - }' -``` +Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. -Это самый маленький надёжный RPC-пример на странице: один запрос, один точный ответ, без дерева receipts. +Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: -## Отправка и отслеживание транзакции +- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- signer: `mike.near` +- receiver: `social.near` +- высота блока включения: `79574923` +- высота блока исполнения receipt для записи в SocialDB: `79574924` -### Двухчастный паттерн: отправить транзакцию или отследить уже известный tx hash до финального исполнения +Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. -Базовый паттерн: + Стратегия + Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. + + 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. + 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. + 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. + +**Что вы здесь решаете** + +- какой эндпоинт отправки брать первым +- что опрашивать после того, как у вас появился tx hash +- как `wait_until` связан с included-, optimistic- и final-гарантиями +- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` + +```mermaid +flowchart LR + S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] + A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] + T --> F["Транзакция полностью завершена"] + T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] + F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] +``` -- `broadcast_tx_async` для отправки -- `tx` с `wait_until: "FINAL"` для отслеживания -- `EXPERIMENTAL_tx_status` только если следующий вопрос уже про receipts +| Метод | Когда использовать | Что вернётся | Роль здесь | +| --- | --- | --- | --- | +| [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | +| [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | +| [`broadcast_tx_commit`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | +| [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | +| [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | -Этот walkthrough намеренно разбит на две части: +**Карта статусов и ожидания** -- отправить новую подписанную транзакцию и сохранить возвращённый хеш -- отследить один известный исторический tx hash с воспроизводимым выводом +Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. -Для части про отслеживание используется одна зафиксированная историческая транзакция, поэтому status-lookup идёт через архивный хост: +| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | +| --- | --- | --- | +| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | +| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | +| `INCLUDED_FINAL` | блок включения уже финален | `tx` | +| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | +| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | -- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- signer: `mike.near` -- receiver: `social.near` -- `https://archival-rpc.mainnet.fastnear.com` +Практическое различие очень простое: -1. Отправьте новую подписанную транзакцию и сохраните возвращённый хеш. +- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash +- используйте `tx` как обычный цикл опроса +- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу + +**Что вы делаете** + +- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. +- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. +- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. +- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. ```bash RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near +RECEIVER_ID=social.near +``` +1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. + +```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data '{ @@ -72,16 +100,40 @@ curl -s "$RPC_URL" \ | jq . ``` -Этот первый шаг нужен только для формы отправки. Именно возвращённый хеш вы потом будете отслеживать для своей живой транзакции. +В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. -2. Отслеживайте один известный tx hash, пока не получите самый простой финальный ответ. +2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. ```bash -RPC_URL=https://archival-rpc.mainnet.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "INCLUDED_FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' ``` +Что здесь важно заметить: + +- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality +- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения +- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt + +3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. + ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ @@ -99,12 +151,18 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - transaction_status: .result.status, + status: .result.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -3. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда для этого известного tx уже нужен уровень receipts. +Что здесь важно заметить: + +- для исторической tx этот вызов тоже возвращается сразу +- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» +- для многих приложений именно здесь и стоит остановиться + +4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. ```bash curl -s "$RPC_URL" \ @@ -123,49 +181,82 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, + status: .result.status, transaction_handoff: .result.transaction_outcome.outcome.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Если вы хотите, чтобы узел ждал за вас, используйте [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Но базовый паттерн на этой странице остаётся таким: отправка через `broadcast_tx_async`, затем отслеживание хеша через `tx`. +Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». + +5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transaction_handoff: .transactions[0].transaction_outcome.outcome.status + }' +``` + +Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» + +**Рекомендуемый паттерн** + +- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. +- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. +- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. ## Механика аккаунтов и ключей Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. -### Может ли этот access key прямо сейчас вызвать этот контракт? +### Проверить и удалить старые function-call-ключи Near Social -Используйте этот сценарий, когда у вас уже есть аккаунт, один public key и целевой контракт, а вам нужен простой ответ да или нет до того, как вы начнёте что-то подписывать. +Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. Стратегия - Сначала отфильтруйте ключи аккаунта, затем прочитайте точный ключ и только потом классифицируйте его права. + Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. - 01RPC view_access_key_list сужает список до ключей, которые вообще могут относиться к целевому контракту. - 02RPC view_access_key даёт точный permission-object для того public key, которым вы реально можете подписывать. - 03jq превращает этот permission-object в full_access, function_call_match, receiver_mismatch или method_not_allowed. + 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. + 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. + 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. **Что вы делаете** -- Получаете access key аккаунта и сужаете список до нужного контракта. -- Точно проверяете тот ключ, которым собираетесь подписывать. -- Решаете, может ли он вызвать этот receiver и method, не выходя за пределы RPC. +- Через сам RPC получаете полный список access key аккаунта. +- Сужаете этот список до function-call-ключей, привязанных к `social.near`. +- Точно проверяете один выбранный ключ перед удалением. +- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -TARGET_CONTRACT_ID=crossword.puzzle.near -TARGET_METHOD_NAME=new_puzzle -TARGET_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_WANT_TO_CHECK' +Сразу важны два ограничения: + +- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. +- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». -# Пример живых значений, проверенных 19 апреля 2026 года: -# ACCOUNT_ID=mike.near -# TARGET_CONTRACT_ID=crossword.puzzle.near -# TARGET_METHOD_NAME=new_puzzle -# TARGET_PUBLIC_KEY='ed25519:otwaB1X88ocpmUdC1B5XaifucfDLmLKaonb26KqTj96' +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export SOCIAL_RECEIVER_ID=social.near +export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' +export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' +export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' ``` -1. Получите ключи аккаунта и сузьте их до целевого контракта. +1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. ```bash curl -s "$RPC_URL" \ @@ -180,28 +271,246 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/access-key-list.json >/dev/null - -jq --arg target_contract_id "$TARGET_CONTRACT_ID" '{ - candidate_keys: [ - .result.keys[] - | select( - .access_key.permission == "FullAccess" - or ( - (.access_key.permission | type) == "object" - and .access_key.permission.FunctionCall.receiver_id == $target_contract_id - ) - ) - | { - public_key, - nonce: .access_key.nonce, - permission: .access_key.permission + | tee /tmp/fastnear-access-keys.json >/dev/null + +jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + nonce: .access_key.nonce, + receiver_id: .access_key.permission.FunctionCall.receiver_id, + method_names: .access_key.permission.FunctionCall.method_names, + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } +' /tmp/fastnear-access-keys.json +``` + +Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. + +2. Ещё раз проверьте конкретный ключ перед удалением. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$DELETE_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" } - ] -}' /tmp/access-key-list.json + }')" \ + | jq '{nonce: .result.nonce, permission: .result.permission}' +``` + +3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + is_function_call: true, + limit: 10 + }')" \ + | jq '{ + account_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_success + } + ] + }' +``` + +Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. + +4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. + +Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. + +```bash +SIGNED_TX_BASE64="$( + node --input-type=module <<'EOF' + +const { + ACCOUNT_ID, + NETWORK_ID = 'mainnet', + RPC_URL = 'https://rpc.mainnet.fastnear.com', + DELETE_PUBLIC_KEY, + FULL_ACCESS_PUBLIC_KEY, + FULL_ACCESS_PRIVATE_KEY, +} = process.env; + +for (const name of [ + 'ACCOUNT_ID', + 'DELETE_PUBLIC_KEY', + 'FULL_ACCESS_PUBLIC_KEY', + 'FULL_ACCESS_PRIVATE_KEY', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); +const derivedPublicKey = keyPair.getPublicKey().toString(); + +if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { + throw new Error( + `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: ACCOUNT_ID, + public_key: FULL_ACCESS_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const transaction = transactions.createTransaction( + ACCOUNT_ID, + utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), + ACCOUNT_ID, + BigInt(accessKey.nonce) + 1n, + [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +)" +``` + +5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. + +```bash +if curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' + .result.keys[] + | select(.public_key == $public_key) + ' >/dev/null; then + echo "Key is still present: $DELETE_PUBLIC_KEY" +else + echo "Key deleted: $DELETE_PUBLIC_KEY" +fi +``` + +**Зачем нужен следующий шаг?** + +Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. + +### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? + +Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. + + Стратегия + Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. + + 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. + 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. + 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. + +**Что вы делаете** + +- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. +- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. +- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. +- Подтягиваете кандидата по транзакциям и различаете три разных ключа: + - ключ, который был добавлен + - public key верхнеуровневого signer + - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` + +Сразу важны три детали про nonce: + +- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. +- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. +- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' + +# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: +# export ACCOUNT_ID=mike.near +# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' ``` -2. Прочитайте точное состояние того ключа, который хотите оценить. +1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. ```bash curl -s "$RPC_URL" \ @@ -219,69 +528,172 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/exact-access-key.json >/dev/null + | tee /tmp/key-origin-view.json >/dev/null -jq '{nonce: .result.nonce, permission: .result.permission}' /tmp/exact-access-key.json +CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" +ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" +SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" +SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" + +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_public_key "$TARGET_PUBLIC_KEY" \ + --argjson current_nonce "$CURRENT_NONCE" \ + --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ + --argjson search_from "$SEARCH_FROM" \ + --argjson search_to "$SEARCH_TO" \ + --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ + account_id: $account_id, + target_public_key: $target_public_key, + current_nonce: $current_nonce, + estimated_receipt_block: $estimated_receipt_block, + search_from_tx_block_height: $search_from, + search_to_tx_block_height: $search_to, + permission: ($permission | fromjson) + }' ``` -3. Превратите этот permission-object в ответ да или нет для этого контракта и метода. +Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. + +2. Ищите историю аккаунта только внутри этого диапазона блоков. ```bash -jq -n \ - --slurpfile key /tmp/exact-access-key.json \ - --arg target_contract_id "$TARGET_CONTRACT_ID" \ - --arg target_method_name "$TARGET_METHOD_NAME" ' - ($key[0].result.permission) as $permission - | if $permission == "FullAccess" then - { - can_call_now: true, - reason: "full_access" - } - elif $permission.FunctionCall.receiver_id != $target_contract_id then - { - can_call_now: false, - reason: "receiver_mismatch", - receiver_id: $permission.FunctionCall.receiver_id - } - elif ( - ($permission.FunctionCall.method_names | length) == 0 - or ($permission.FunctionCall.method_names | index($target_method_name)) - ) then - { - can_call_now: true, - reason: ( - if ($permission.FunctionCall.method_names | length) == 0 - then "function_call_any_method" - else "function_call_method_match" - end - ), - allowance: ($permission.FunctionCall.allowance // "unlimited") - } - else - { - can_call_now: false, - reason: "method_not_allowed", - allowed_methods: $permission.FunctionCall.method_names +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_tx_block_height "$SEARCH_FROM" \ + --argjson to_tx_block_height "$SEARCH_TO" '{ + account_id: $account_id, + is_real_signer: true, + from_tx_block_height: $from_tx_block_height, + to_tx_block_height: $to_tx_block_height, + desc: false, + limit: 50 + }')" \ + | tee /tmp/key-origin-candidates.json >/dev/null + +jq '{ + txs_count, + candidate_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_signer, + is_real_signer, + is_predecessor, + is_receiver } - end' + ] +}' /tmp/key-origin-candidates.json +``` + +Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. + +3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. + +```bash +TX_HASHES_JSON="$( + jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json +)" + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ + | tee /tmp/key-origin-transactions.json >/dev/null + +jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "direct", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $tx.transaction.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }), + ($tx.transaction.actions[]? + | .Delegate? + | .delegate_action as $delegate + | $delegate.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "delegated", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $delegate.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }) + ) + | { + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + tx_block_hash: $tx.execution_outcome.block_hash, + receiver_id: $tx.transaction.receiver_id + } + . +' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json +``` + +Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. + +Для примерного ключа `mike.near` выше совпадение оказывается делегированным: + +- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` +- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` +- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` +- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` + +4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. + +```bash +ADD_KEY_RECEIPT_ID="$( + jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | .receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) + | .receipt.receipt_id + ' /tmp/key-origin-transactions.json | head -n 1 +)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receipt_block_height: .receipt.block_height, + tx_block_height: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }' ``` -Для примерного ключа `mike.near` выше на 19 апреля 2026 года ответ получается `can_call_now: true`: это function-call-key для `crossword.puzzle.near`, а `method_names: ["new_puzzle"]` явно разрешает тот метод, который мы проверяем. +Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. **Зачем нужен следующий шаг?** -`view_access_key_list` — самый быстрый фильтр на уровне контракта. `view_access_key` — точная проверка полномочий для того public key, которым вы действительно хотите пользоваться. Если ответ `false`, вам нужен другой ключ или другая схема permissions, а не более глубокая историческая трассировка. +Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. -### Нужно ли этому получателю сначала зарегистрировать FT storage? +### Проверить регистрацию FT storage и затем перевести токены -Используйте этот сценарий, когда история звучит так: «я собираюсь отправить FT-токен и хочу получить простой ответ “нужен ли сначала `storage_deposit`?”». +Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». Стратегия - Сначала прочитайте storage-состояние получателя и остановитесь, как только станет понятно, может ли `ft_transfer` уже пройти. + Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. 02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит. - 03jq превращает эти два чтения в один ответ: «перевод уже может идти» или «сначала нужен `storage_deposit`». + 03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог. **Сеть** @@ -292,19 +704,24 @@ jq -n \ - [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) - [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) -В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Здесь важен именно read-only-ответ: нужен ли сначала `storage_deposit`, или путь перевода уже может продолжаться. +В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. **Что вы делаете** - Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. -- Получаете точный минимальный размер storage deposit на этом же контракте. -- Останавливаетесь, как только понимаете: `ft_transfer` уже может идти или сначала нужен `storage_deposit`. +- При необходимости получаете минимальный размер storage deposit. +- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. +- Подтверждаете баланс получателя тем же view-методом самого контракта. ```bash export NETWORK_ID=testnet export RPC_URL=https://rpc.testnet.fastnear.com export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet +export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' +export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' +export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' ``` 1. Проверьте, зарегистрирован ли получатель на FT-контракте. @@ -340,7 +757,7 @@ jq '{ }' /tmp/ft-storage-balance.json ``` -2. Получите минимальный storage deposit на этом же контракте. +2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. ```bash MIN_STORAGE_YOCTO="$( @@ -365,103 +782,326 @@ MIN_STORAGE_YOCTO="$( printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" ``` -3. Превратите эти два чтения в один ответ о готовности перевода. +3. Определите одну переиспользуемую функцию подписи для function-call к контракту. + +Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. ```bash -jq -n \ - --slurpfile balance /tmp/ft-storage-balance.json \ - --slurpfile bounds /tmp/ft-storage-bounds.json \ - --arg receiver_account_id "$RECEIVER_ACCOUNT_ID" ' - ( - $balance[0].result.result - | if length == 0 then null else (implode | fromjson) end - ) as $storage - | ( - $bounds[0].result.result - | implode - | fromjson - ) as $bounds - | { - receiver_account_id: $receiver_account_id, - receiver_registered: ($storage != null), - current_storage: $storage, - minimum_storage_deposit_yocto: $bounds.min, - next_step: ( - if $storage != null - then "получатель уже зарегистрирован; ft_transfer может продолжаться" - else "сначала отправьте storage_deposit, потом делайте ft_transfer" - end - ) - }' +sign_function_call() { + METHOD_NAME="$1" \ + ARGS_JSON="$2" \ + DEPOSIT_YOCTO="$3" \ + GAS_TGAS="$4" \ + node --input-type=module <<'EOF' + +const { + NETWORK_ID = 'testnet', + RPC_URL = 'https://rpc.testnet.fastnear.com', + TOKEN_CONTRACT_ID, + SENDER_ACCOUNT_ID, + SENDER_PUBLIC_KEY, + SENDER_PRIVATE_KEY, + METHOD_NAME, + ARGS_JSON, + DEPOSIT_YOCTO = '0', + GAS_TGAS = '100', +} = process.env; + +for (const name of [ + 'TOKEN_CONTRACT_ID', + 'SENDER_ACCOUNT_ID', + 'SENDER_PUBLIC_KEY', + 'SENDER_PRIVATE_KEY', + 'METHOD_NAME', + 'ARGS_JSON', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); +const signer = await InMemorySigner.fromKeyPair( + NETWORK_ID, + SENDER_ACCOUNT_ID, + keyPair +); + +const derivedPublicKey = keyPair.getPublicKey().toString(); +if (derivedPublicKey !== SENDER_PUBLIC_KEY) { + throw new Error( + `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: SENDER_ACCOUNT_ID, + public_key: SENDER_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const action = transactions.functionCall( + METHOD_NAME, + Buffer.from(ARGS_JSON), + BigInt(GAS_TGAS) * 10n ** 12n, + BigInt(DEPOSIT_YOCTO) +); + +const transaction = transactions.createTransaction( + SENDER_ACCOUNT_ID, + utils.PublicKey.fromString(SENDER_PUBLIC_KEY), + TOKEN_CONTRACT_ID, + BigInt(accessKey.nonce) + 1n, + [action], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + SENDER_ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +} ``` -**Зачем нужен следующий шаг?** - -Это чистый RPC-вопрос в этом workflow: «зарегистрирован ли уже получатель и какой минимальный депозит потребует контракт, если нет?» Подписанный write-path зависит уже от вашего wallet, CLI или backend-интеграции, поэтому в самый маленький core RPC-пример он не входит. - -## Чтения контракта и сырое состояние - -Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» - -### Прочитать счётчик прямо из состояния контракта, а потом подтвердить его через view-метод +4. При необходимости сначала зарегистрируйте storage для получателя. -Используйте этот сценарий, когда вы уже знаете точное семейство storage-ключей и хотите увидеть самый короткий контраст между raw state и публичным методом чтения контракта. +```bash +if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then + SIGNED_TX_BASE64="$( + sign_function_call \ + storage_deposit \ + "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id, + registration_only: true + }')" \ + "$MIN_STORAGE_YOCTO" \ + 100 + )" -Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`: + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash + }' +fi +``` -- `view_state` читает сырой ключ `STATE` напрямую -- `call_function get_num` спрашивает у контракта то же текущее число +5. После готовности storage переведите FT. ```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export CONTRACT_ID=counter.near-examples.testnet -export STATE_PREFIX_BASE64=U1RBVEU= +SIGNED_TX_BASE64="$( + sign_function_call \ + ft_transfer \ + "$(jq -nc \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ + receiver_id: $receiver_id, + amount: $amount, + memo: "FastNear RPC example" + }')" \ + 1 \ + 100 +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' ``` -1. Сначала прочитайте сырой ключ `STATE`. +6. Подтвердите FT-баланс получателя тем же view-методом контракта. ```bash +RECEIVER_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ - --arg account_id "$CONTRACT_ID" \ - --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { - request_type: "view_state", + request_type: "call_function", account_id: $account_id, - prefix_base64: $prefix_base64, + method_name: "ft_balance_of", + args_base64: $args_base64, finality: "final" } }')" \ - | tee /tmp/counter-view-state.json >/dev/null - -jq '{ - key: (.result.values[0].key | @base64d), + | jq '{ + receiver_balance: (.result.result | implode | fromjson) + }' +``` + +**Зачем нужен следующий шаг?** + +Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. + +## Чтения контракта и сырое состояние + +Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» + +### Как прочитать сырое состояние контракта напрямую? + +Используйте этот сценарий, когда у контракта нет нужного view-метода или когда вам нужно проверить саму схему хранения, а не просто довериться ответу метода. + +В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: сначала вы читаете storage напрямую, а потом подтверждаете, что публичный view-метод контракта даёт тот же ответ: + +- `view_state` читает сырой ключ `STATE` прямо из storage контракта +- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API + + Стратегия + Сначала прочитайте storage напрямую, а уже потом дайте контракту подтвердить тот же ответ через view-метод. + + 01RPC view_state читает сырой ключ STATE, не запуская код контракта. + 02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта. + 03RPC call_function get_num — это удобная перепроверка того, что прямое чтение storage и view-метод по-прежнему дают один и тот же ответ. + +Здесь важнее ментальная модель, чем сам счётчик: + +- `view_state` — это прямое чтение storage из trie +- `call_function` исполняет read-only-метод контракта +- оба способа могут ответить на один и тот же вопрос, но делают разную работу + +```mermaid +flowchart LR + S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] + R --> D["Декодировать base64 + Borsh"] + D --> N["Знаковое значение счётчика"] + C["RPC call_function get_num"] --> J["JSON-результат метода"] + N --> X["Сравнить"] + J --> X + X --> A["Одно и то же текущее значение"] +``` + +**Что вы делаете** + +- Читаете сырой ключ `STATE` из storage контракта. +- Декодируете возвращённые байты в текущее знаковое значение счётчика. +- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export CONTRACT_ID=counter.near-examples.testnet +export STATE_PREFIX_BASE64=U1RBVEU= +``` + +1. Сначала прочитайте сырое состояние контракта. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$CONTRACT_ID" \ + --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_state", + account_id: $account_id, + prefix_base64: $prefix_base64, + finality: "final" + } + }')" \ + | tee /tmp/counter-view-state.json >/dev/null + +jq '{ + block_height: .result.block_height, + key_base64: .result.values[0].key, value_base64: .result.values[0].value }' /tmp/counter-view-state.json + +jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json ``` -Здесь должен появиться `key: "STATE"`. Это и есть тот случай, когда `view_state` уместен: семейство ключей вам уже известно заранее. +Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. -2. Декодируйте сырые байты. +2. Декодируйте байты значения в знаковое число счётчика. ```bash RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" -python3 - "$RAW_VALUE_BASE64" <<'PY' +python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . raw = base64.b64decode(sys.argv[1]) -print(int.from_bytes(raw, "little", signed=True)) + +print(json.dumps({ + "value_base64": sys.argv[1], + "bytes": list(raw), + "hex": raw.hex(), + "signed_i8": int.from_bytes(raw, "little", signed=True), + "unsigned_u8": int.from_bytes(raw, "little", signed=False), +})) PY ``` -Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть гораздо менее дружелюбным: near-sdk-коллекции и Borsh-сериализованные структуры часто выводят storage-ключи из префиксов и внутренних схем ключей, поэтому `view_state` остаётся практичным только когда вы уже знаете точный layout, который хотите читать. Правило остаётся тем же: сначала байты, потом схема. +Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. -3. Теперь спросите контракт привычным способом и сравните. +Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. + +Переиспользуемый рецепт здесь короткий: + +- `view_state` возвращает сырые байты в base64 +- вы декодируете эти байты по известной схеме хранения контракта +- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема + +3. Теперь спросите контракт более привычным способом и сравните. ```bash curl -s "$RPC_URL" \ @@ -486,7 +1126,7 @@ jq '{ }' /tmp/counter-call-function.json ``` -4. Сравните оба ответа. +4. Сравните оба ответа напрямую. ```bash RAW_STATE_NUMBER="$( @@ -510,194 +1150,704 @@ jq -n \ }' ``` +Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: + +- `view_state` ответил на вопрос, прочитав storage напрямую +- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта + **Зачем нужен следующий шаг?** -Используйте `view_state`, когда вы уже знаете точное семейство storage-ключей и хотите raw bytes. Используйте `call_function`, когда вам нужен публичный метод чтения самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда уже стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). +Используйте `view_state`, когда настоящий вопрос относится к точному storage, отсутствующему view-методу или проверке известного семейства ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). + +## Трассировка чанков и шардов + +Начинайте отсюда, когда вопрос уже не просто «транзакция прошла или нет?», а «какой именно чанк на шарде исполнил каждый шаг работы?» + +### Проследить, как сгенерированная `Transfer`-receipt переходит из одного чанка на шарде в другой + +Используйте этот сценарий, когда вызов контракта был только началом истории. В этом зафиксированном mainnet-примере подписанная транзакция стартует на шарде `11`, а сгенерированная `Transfer`-receipt заканчивает путь уже на шарде `6`. Именно ради таких cross-shard handoff и имеет смысл смотреть на чанки. -### Какие ERC-20 токены из Rainbow Bridge существуют на NEAR и сколько одного такого токена сейчас в обращении? +Этот walkthrough привязан к: -Используйте этот сценарий, когда хотите найти Rainbow Bridge ERC-20 контракты и посмотреть живой объём одного токена на NEAR. Rainbow Bridge развёртывает по одному NEAR-контракту на каждый bridged ERC-20 токен, а `factory.bridge.near` их перечисляет. +- транзакции `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` от `7419369993.tg` к `game.hot.tg` с вызовом `l2_claim` +- исходному чанку `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` на шарде `11` в блоке `194623170` +- чанку первой receipt `FJWpAYzVXbZwqJUbGXELTnnBBkdvc6W8vWkwuUA3Zwz9` на шарде `11` в блоке `194623171` +- сгенерированной `Transfer`-receipt `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` +- конечному чанку `EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU` на шарде `6` в блоке `194623172` Стратегия - Одно чтение factory перечисляет token-контракты. Ещё два небольших view-вызова по одному токену показывают, что это за токен и сколько его сейчас на NEAR. + Сначала восстановите receipt-цепочку, потом напрямую посмотрите на сгенерированную receipt, а затем привяжите каждый шаг к тому чанку на шарде, который действительно нёс эту работу. - 01RPC call_function get_tokens_accounts по factory.bridge.near возвращает развёрнутые bridged token-контракты. - 02Следующий RPC call_function по одному bridged token-контракту возвращает метаданные токена: имя, тикер и число десятичных знаков. - 03Ещё один RPC call_function по тому же контракту возвращает текущее сырое значение объёма в обращении на NEAR. + 01RPC EXPERIMENTAL_tx_status быстро показывает граф receipts и в какие следующие блоки перешла работа. + 02RPC EXPERIMENTAL_receipt позволяет посмотреть на тело сгенерированной receipt напрямую, а не выводить его только из логов. + 03RPC chunk по блоку и шарду или по хешу чанка доказывает, какая именно единица исполнения на шарде нёсла каждый шаг. + +Оба experimental-метода здесь очень уместны: `EXPERIMENTAL_tx_status` быстро находит граф receipts, а `EXPERIMENTAL_receipt` показывает тело сгенерированной receipt ещё до того, как вы привяжете её обратно к чанкам. + +```mermaid +flowchart LR + A["Tx 8xrc...
блок 194623170
чанк Bfyd...
шард 11"] --> B["Receipt AFC2...
блок 194623171
чанк FJWp...
шард 11
логи ft_mint"] + B --> C["Сгенерированная receipt TtRn...
Transfer 1800930478788300000000 yoctoNEAR"] + C --> D["Чанк EPau...
блок 194623172
шард 6
receipt исполняется"] +``` **Что вы делаете** -- Спрашиваете у bridge factory обо всех bridged token-контрактах, которые она создала. -- Выбираете один bridged token-контракт и читаете его метаданные. -- Читаете total supply того же контракта и переводите его в человеческие единицы через `decimals`. +- Сначала восстанавливаете receipt-цепочку из транзакции. +- Напрямую смотрите на тело сгенерированной `Transfer`-receipt. +- Используете координаты блока и шарда там, где они уже известны. +- Используете хеш чанка там, где другой инструмент уже выдал точный конечный чанк. ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com -export FACTORY_ID=factory.bridge.near -export TOKENS_FILE=/tmp/rainbow-bridge-tokens.json +export TX_HASH=8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP +export SIGNER_ACCOUNT_ID=7419369993.tg +export ORIGIN_BLOCK_HEIGHT=194623170 +export ORIGIN_SHARD_ID=11 +export RECEIPT_BLOCK_HEIGHT=194623171 +export RECEIPT_SHARD_ID=11 +export GENERATED_RECEIPT_ID=TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4 +export DESTINATION_CHUNK_HASH=EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU +``` + +1. Начните с `EXPERIMENTAL_tx_status`, чтобы сначала увидеть граф receipts, а уже потом думать о чанках. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: [$tx_hash, $signer_account_id] + }')" \ + | tee /tmp/chunk-trace-status.json >/dev/null + +jq '{ + final_execution_status: .result.final_execution_status, + transaction_handoff: .result.transaction_outcome.outcome.status, + receipts: ( + .result.receipts_outcome + | map({ + receipt_id: .id, + executor_id: .outcome.executor_id, + block_hash, + status: .outcome.status + }) + ) +}' /tmp/chunk-trace-status.json ``` -1. Получите список bridged token-контрактов. +На что смотреть: + +- подписанная транзакция передаёт работу в receipt `AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y` +- позже в том же графе receipts исполняется `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` для `7419369993.tg` +- уже одного tx status достаточно, чтобы увидеть: настоящая работа продолжилась после исходной подписанной транзакции + +2. Посмотрите на сгенерированную receipt напрямую, чтобы доказать, что это действительно `Transfer`-receipt. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$FACTORY_ID" '{ + --data "$(jq -nc --arg receipt_id "$GENERATED_RECEIPT_ID" '{ jsonrpc: "2.0", id: "fastnear", - method: "query", + method: "EXPERIMENTAL_receipt", params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_tokens_accounts", - args_base64: "e30=", - finality: "final" + receipt_id: $receipt_id } }')" \ - | tee "$TOKENS_FILE" >/dev/null + | tee /tmp/chunk-trace-receipt.json >/dev/null -jq -r '.result.result | implode | fromjson | .[]' "$TOKENS_FILE" +jq '{ + predecessor_id: .result.predecessor_id, + receiver_id: .result.receiver_id, + signer_id: .result.receipt.Action.signer_id, + signer_public_key: .result.receipt.Action.signer_public_key, + actions: .result.receipt.Action.actions +}' /tmp/chunk-trace-receipt.json ``` -Каждая строка — это один bridged FT-контракт на NEAR в форме `.factory.bridge.near`. Например, bridged ERC-20 USDT с Ethereum-адреса `0xdAC17F958D2ee523a2206206994597C13D831ec7` появляется как `dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near`. +Именно здесь история по шардам становится конкретной: эта цепочка исполнения контракта сгенерировала `Transfer` action receipt от `system` к `7419369993.tg` с депозитом `1800930478788300000000`. -2. Прочитайте метаданные одного токен-контракта. +3. Используйте `chunk` по блоку и шарду, чтобы найти исходную подписанную транзакцию на шарде `11`. ```bash -export TOKEN_ID=dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --argjson block_id "$ORIGIN_BLOCK_HEIGHT" \ + --argjson shard_id "$ORIGIN_SHARD_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "chunk", + params: { + block_id: $block_id, + shard_id: $shard_id + } + }')" \ + | jq --arg tx_hash "$TX_HASH" '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created + }, + matching_transaction: ( + .result.transactions[] + | select(.hash == $tx_hash) + | { + hash, + signer_id, + receiver_id + } + ) + }' +``` + +Это самый чистый use case для `chunk` по блоку и шарду: координаты уже известны, а вам нужна точная единица исполнения на шарде, которая несла исходную подписанную транзакцию. + +4. Оставайтесь на том же маршруте и посмотрите, как первая receipt исполняется в следующем блоке на том же шарде. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --argjson block_id "$RECEIPT_BLOCK_HEIGHT" \ + --argjson shard_id "$RECEIPT_SHARD_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "chunk", + params: { + block_id: $block_id, + shard_id: $shard_id + } + }')" \ + | jq '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created, + tx_root: .result.header.tx_root, + gas_used: .result.header.gas_used + }, + tx_count: (.result.transactions | length), + receipt_count: (.result.receipts | length), + matching_receipt: ( + .result.receipts[] + | select(.receipt_id == "AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y") + | { + receipt_id, + predecessor_id, + receiver_id + } + ) + }' +``` + +Вот здесь chunks наконец становятся естественными: + +- у чанка `tx_root = 11111111111111111111111111111111` +- `tx_count` равен `0` +- но шард всё равно жжёт gas и исполняет receipt `AFC2...` +То есть этот шард реально сделал работу в этом блоке, хотя новая подписанная транзакция прямо в самом чанке не появилась. + +5. Переключайтесь на `chunk` по хешу, когда другой инструмент уже выдал точный конечный чанк. + +```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + --data "$(jq -nc --arg chunk_id "$DESTINATION_CHUNK_HASH" '{ jsonrpc: "2.0", id: "fastnear", - method: "query", + method: "chunk", params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_metadata", - args_base64: "e30=", - finality: "final" + chunk_id: $chunk_id } }')" \ - | tee /tmp/rainbow-bridge-token-metadata.json >/dev/null + | jq --arg receipt_id "$GENERATED_RECEIPT_ID" '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created, + tx_root: .result.header.tx_root, + gas_used: .result.header.gas_used + }, + tx_count: (.result.transactions | length), + receipt_count: (.result.receipts | length), + matching_receipt: ( + .result.receipts[] + | select(.receipt_id == $receipt_id) + | { + receipt_id, + predecessor_id, + receiver_id + } + ) + }' +``` + +Это и подтверждает cross-shard hop: -jq '.result.result | implode | fromjson | {name, symbol, decimals}' /tmp/rainbow-bridge-token-metadata.json +- сгенерированная `Transfer`-receipt исполняется в чанке `EPau...` +- этот чанк живёт на шарде `6`, а не на шарде `11` +- подписанная транзакция стартовала на одном шарде, а следующая receipt завершилась уже на другом + +**Зачем нужен следующий шаг?** + +Используйте [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда вы знаете координаты блока и шарда и хотите буквально спросить: «что этот шард исполнил в этом блоке?» Используйте [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. Используйте [EXPERIMENTAL_tx_status](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) и [EXPERIMENTAL_receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), когда настоящий вопрос относится к трассировке на уровне receipts. Если ещё нужны state changes и produced receipts, расширяйтесь в [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard). + +## Точные чтения NEAR Social и BOS + +Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. + +### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? + +Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». + + Стратегия + Спросите у social.near ровно о двух вещах, которые важны до подписи. + + 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. + 02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near. + 03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer. + +Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: + +- есть ли у целевого аккаунта storage на `social.near`? +- если есть, осталось ли там ещё место? +- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? + +**Официальные ссылки** + +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +**Что вы делаете** + +- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. +- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. +- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. +- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mike.near +export SIGNER_ACCOUNT_ID=mike.near ``` -3. Прочитайте текущий total supply на NEAR и переведите его в человеческие единицы. +1. Сначала проверьте сам аккаунт signer. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { - request_type: "call_function", + request_type: "view_account", account_id: $account_id, - method_name: "ft_total_supply", - args_base64: "e30=", finality: "final" } }')" \ - | tee /tmp/rainbow-bridge-token-supply.json >/dev/null + | tee /tmp/social-publish-signer.json >/dev/null + +jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + signer_account_id: $signer_account_id, + amount: .result.amount, + locked: .result.locked, + storage_usage: .result.storage_usage +}' /tmp/social-publish-signer.json +``` -RAW_SUPPLY="$( - jq -r '.result.result | implode | fromjson' /tmp/rainbow-bridge-token-supply.json -)" +Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. + +2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. -DECIMALS="$( - jq -r '.result.result | implode | fromjson | .decimals' /tmp/rainbow-bridge-token-metadata.json +```bash +SOCIAL_STORAGE_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' )" -HUMAN_SUPPLY="$( - python3 - "$RAW_SUPPLY" "$DECIMALS" <<'PY' -from decimal import Decimal +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_account_storage", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-account-storage.json >/dev/null -raw = Decimal(sys.argv[1]) -decimals = int(sys.argv[2]) -human = raw / (Decimal(10) ** decimals) -print(human) -PY +jq --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + storage: (.result.result | implode | fromjson), + storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) +}' /tmp/social-account-storage.json +``` + +Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. + +3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. + +```bash +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + jq -n --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + signer_matches_target: true, + permission_granted: true, + reason: "owner write" + }' +else + WRITE_PERMISSION_ARGS_BASE64="$( + jq -nc \ + --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ + --arg key "$ACCOUNT_ID" '{ + predecessor_id: $predecessor_id, + key: $key + }' | base64 | tr -d '\n' + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + signer_matches_target: false, + permission_granted: (.result.result | implode | fromjson) + }' +fi +``` + +4. Сведите проверку storage и разрешения в один читаемый итог. + +```bash +AVAILABLE_BYTES="$( + jq -r ' + .result.result + | if length == 0 then "0" + else (implode | fromjson | .available_bytes // 0 | tostring) + end + ' /tmp/social-account-storage.json )" +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + PERMISSION_GRANTED=true +else + PERMISSION_GRANTED="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq -r '.result.result | implode | fromjson' + )" +fi + jq -n \ - --arg token_id "$TOKEN_ID" \ - --arg raw_supply "$RAW_SUPPLY" \ - --argjson decimals "$DECIMALS" \ - --arg human_supply "$HUMAN_SUPPLY" '{ - token_id: $token_id, - raw_supply: $raw_supply, - decimals: $decimals, - human_supply: $human_supply + --arg account_id "$ACCOUNT_ID" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ + --argjson available_bytes "$AVAILABLE_BYTES" \ + --argjson permission_granted "$PERMISSION_GRANTED" '{ + account_id: $account_id, + signer_account_id: $signer_account_id, + storage_ready: ($available_bytes > 0), + permission_ready: $permission_granted, + ready_to_publish_now: (($available_bytes > 0) and $permission_granted) }' ``` -Результат `ft_total_supply` приходит в минимальных единицах токена. Используйте `decimals` из ответа предыдущего шага, чтобы перевести его в человекочитаемый объём в обращении. +Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. + +**Зачем нужен следующий шаг?** + +Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. -#### Необязательное расширение: показать первые несколько bridged token-ов с метаданными и объёмом в обращении +### Что прямо сейчас содержит `mob.near/widget/Profile`? -Используйте это расширение, когда нужен быстрый sample-инвентарь и вы всё ещё хотите оставаться в RPC. +Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». + + Стратегия + Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. + + 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. + 02RPC call_function get читает точный исходник widget/Profile. + 03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples. + +**Официальные ссылки** + +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +**Что вы делаете** + +- Спрашиваете у `social.near` каталог виджетов под `mob.near`. +- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. +- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. +- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). ```bash -export TOKEN_SAMPLE_COUNT=5 +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mob.near +export WIDGET_NAME=Profile +``` -python3 <<'PY' -from decimal import Decimal +1. Получите каталог виджетов и сохраните высоты блоков последней записи. -TOKENS_FILE = os.environ["TOKENS_FILE"] -LIMIT = int(os.environ.get("TOKEN_SAMPLE_COUNT", "5")) -RPC_URL = os.environ["RPC_URL"] +```bash +WIDGET_KEYS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} + }' | base64 | tr -d '\n' +)" -def decode_result(result): - return json.loads("".join(chr(b) for b in result)) +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "keys", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-keys.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].widget + | to_entries + | sort_by(.value * -1) + | map({ + widget_name: .key, + last_write_block: .value + }) + | .[0:20] +' /tmp/social-widget-keys.json +``` -with open(TOKENS_FILE) as fh: - token_ids = decode_result(json.load(fh)["result"]["result"])[:LIMIT] +2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. -def rpc_call(account_id, method_name): - payload = { - "jsonrpc": "2.0", - "id": "fastnear", - "method": "query", - "params": { - "request_type": "call_function", - "account_id": account_id, - "method_name": method_name, - "args_base64": "e30=", - "finality": "final", - }, - } - import subprocess - raw = subprocess.check_output([ - "curl", "-s", RPC_URL, - "-H", "content-type: application/json", - "--data", json.dumps(payload), - ], text=True) - return decode_result(json.loads(raw)["result"]["result"]) - -print(f"{'token_id':<56} {'symbol':<12} {'decimals':>8} {'raw_supply':>24} {'human_supply':>24} name") -for token_id in token_ids: - metadata = rpc_call(token_id, "ft_metadata") - raw_supply = rpc_call(token_id, "ft_total_supply") - human_supply = Decimal(raw_supply) / (Decimal(10) ** metadata["decimals"]) - print( - f"{token_id:<56} " - f"{metadata['symbol']:<12} " - f"{metadata['decimals']:>8} " - f"{raw_supply:>24} " - f"{str(human_supply):>24} " - f"{metadata['name']}" - ) -PY +```bash +WIDGET_GET_ARGS_BASE64="$( + jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-source.json >/dev/null + +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:25] + | join("\n") + ' /tmp/social-widget-source.json ``` +3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. + +```bash +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + ' /tmp/social-widget-keys.json \ + | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +``` + +На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. + **Зачем нужен следующий шаг?** -Оставайтесь в RPC, пока вопрос звучит как «какие bridged token-контракты существуют и сколько одного такого токена сейчас в обращении?» Factory — это источник истины для множества bridged token-ов, а каждый token-контракт сам отвечает за свои метаданные и объём в обращении через стандартные NEP-141 view-методы. Если следующий вопрос становится «кто держит этот токен?», переключайтесь на [V1 FT Top Holders](https://docs.fastnear.com/ru/api/v1/ft-top), а не пытайтесь обходить holders через RPC. +Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. + +## Частые задачи + +### Проверить точное состояние аккаунта или ключа доступа + +**Начните здесь** + +- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для точных полей аккаунта. +- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. + +**Следующая страница при необходимости** + +- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» + +**Остановитесь, когда** + +- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. + +**Переходите дальше, когда** + +- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. +- Пользователя интересует не текущее состояние, а недавняя история активности. + +### Трассировать исполнение на уровне шарда через чанки + +**Начните здесь** + +- Начните с примера выше, если настоящий вопрос звучит как «какой чанк или шард вообще исполнил эту receipt?» +- [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда координаты блока и шарда уже известны. +- [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. + +**Следующая страница при необходимости** + +- [Experimental Receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), если нужно само тело сгенерированной receipt. +- [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard), если chunk payload уже недостаточен и ещё нужны state changes или produced receipts. +- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если вопрос превращается в более широкое async- или callback-расследование. + +**Остановитесь, когда** + +- Уже можно назвать, какой именно чанк и какой шард несли ту работу, которая была важна. + +**Переходите дальше, когда** + +- Пользователю нужны уже не детали исполнения на уровне шарда, а читаемая история транзакции. Тогда переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Проверить один точный блок или снимок состояния протокола + +**Начните здесь** + +- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. +- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» +- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. + +**Следующая страница при необходимости** + +- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. +- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» + +**Остановитесь, когда** + +- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. + +**Переходите дальше, когда** + +- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Что этот контракт возвращает прямо сейчас? + +**Начните здесь** + +- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» +- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. +- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. +- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» + +**Следующая страница при необходимости** + +- [FastNear API](https://docs.fastnear.com/ru/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. +- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» + +**Остановитесь, когда** + +- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. + +**Переходите дальше, когда** + +- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. +- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» + +### Отправить транзакцию и подтвердить результат + +**Начните здесь** + +- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. +- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. +- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. +- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» + +**Остановитесь, когда** + +- У вас уже есть результат отправки и нужный финальный статус. + +**Переходите дальше, когда** + +- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. +- Нужен уже не единичный статус, а более широкий сценарий расследования. ## Частые ошибки diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index acda451..b8ab3b3 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -2,65 +2,93 @@ # Примеры RPC -Используйте эту страницу, когда нужен быстрый точный ответ через RPC. Начните с одного чтения, а к транзакциям и сырому состоянию переходите только если простого запроса уже недостаточно. +Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. -## Быстрый старт +## Отправка и отслеживание транзакции -Если вы только открыли эту страницу, начните с одного точного чтения аккаунта. +Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=near +### Отправить транзакцию и затем проследить её от хеша до финального исполнения -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - finality: "final", - account_id: $account_id - } - }')" \ - | jq '.result | { - amount, - locked, - code_hash, - storage_usage - }' -``` +Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. -Это самый маленький надёжный RPC-пример на странице: один запрос, один точный ответ, без дерева receipts. +Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: -## Отправка и отслеживание транзакции +- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- signer: `mike.near` +- receiver: `social.near` +- высота блока включения: `79574923` +- высота блока исполнения receipt для записи в SocialDB: `79574924` -### Двухчастный паттерн: отправить транзакцию или отследить уже известный tx hash до финального исполнения +Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. -Базовый паттерн: + Стратегия + Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. + + 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. + 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. + 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. + +**Что вы здесь решаете** + +- какой эндпоинт отправки брать первым +- что опрашивать после того, как у вас появился tx hash +- как `wait_until` связан с included-, optimistic- и final-гарантиями +- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` + +```mermaid +flowchart LR + S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] + A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] + T --> F["Транзакция полностью завершена"] + T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] + F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] +``` -- `broadcast_tx_async` для отправки -- `tx` с `wait_until: "FINAL"` для отслеживания -- `EXPERIMENTAL_tx_status` только если следующий вопрос уже про receipts +| Метод | Когда использовать | Что вернётся | Роль здесь | +| --- | --- | --- | --- | +| [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | +| [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | +| [`broadcast_tx_commit`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | +| [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | +| [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | -Этот walkthrough намеренно разбит на две части: +**Карта статусов и ожидания** -- отправить новую подписанную транзакцию и сохранить возвращённый хеш -- отследить один известный исторический tx hash с воспроизводимым выводом +Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. -Для части про отслеживание используется одна зафиксированная историческая транзакция, поэтому status-lookup идёт через архивный хост: +| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | +| --- | --- | --- | +| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | +| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | +| `INCLUDED_FINAL` | блок включения уже финален | `tx` | +| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | +| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | -- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- signer: `mike.near` -- receiver: `social.near` -- `https://archival-rpc.mainnet.fastnear.com` +Практическое различие очень простое: -1. Отправьте новую подписанную транзакцию и сохраните возвращённый хеш. +- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash +- используйте `tx` как обычный цикл опроса +- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу + +**Что вы делаете** + +- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. +- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. +- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. +- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. ```bash RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb +SIGNER_ACCOUNT_ID=mike.near +RECEIVER_ID=social.near +``` +1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. + +```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data '{ @@ -72,16 +100,40 @@ curl -s "$RPC_URL" \ | jq . ``` -Этот первый шаг нужен только для формы отправки. Именно возвращённый хеш вы потом будете отслеживать для своей живой транзакции. +В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. -2. Отслеживайте один известный tx hash, пока не получите самый простой финальный ответ. +2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. ```bash -RPC_URL=https://archival-rpc.mainnet.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "tx", + params: { + tx_hash: $tx_hash, + sender_account_id: $signer_account_id, + wait_until: "INCLUDED_FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + status: .result.status, + transaction_handoff: .result.transaction_outcome.outcome.status + }' ``` +Что здесь важно заметить: + +- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality +- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения +- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt + +3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. + ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ @@ -99,12 +151,18 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - transaction_status: .result.status, + status: .result.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -3. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда для этого известного tx уже нужен уровень receipts. +Что здесь важно заметить: + +- для исторической tx этот вызов тоже возвращается сразу +- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» +- для многих приложений именно здесь и стоит остановиться + +4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. ```bash curl -s "$RPC_URL" \ @@ -123,49 +181,82 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, + status: .result.status, transaction_handoff: .result.transaction_outcome.outcome.status, receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Если вы хотите, чтобы узел ждал за вас, используйте [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Но базовый паттерн на этой странице остаётся таким: отправка через `broadcast_tx_async`, затем отслеживание хеша через `tx`. +Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». + +5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + transaction_handoff: .transactions[0].transaction_outcome.outcome.status + }' +``` + +Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» + +**Рекомендуемый паттерн** + +- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. +- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. +- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. ## Механика аккаунтов и ключей Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. -### Может ли этот access key прямо сейчас вызвать этот контракт? +### Проверить и удалить старые function-call-ключи Near Social -Используйте этот сценарий, когда у вас уже есть аккаунт, один public key и целевой контракт, а вам нужен простой ответ да или нет до того, как вы начнёте что-то подписывать. +Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. Стратегия - Сначала отфильтруйте ключи аккаунта, затем прочитайте точный ключ и только потом классифицируйте его права. + Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. - 01RPC view_access_key_list сужает список до ключей, которые вообще могут относиться к целевому контракту. - 02RPC view_access_key даёт точный permission-object для того public key, которым вы реально можете подписывать. - 03jq превращает этот permission-object в full_access, function_call_match, receiver_mismatch или method_not_allowed. + 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. + 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. + 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. **Что вы делаете** -- Получаете access key аккаунта и сужаете список до нужного контракта. -- Точно проверяете тот ключ, которым собираетесь подписывать. -- Решаете, может ли он вызвать этот receiver и method, не выходя за пределы RPC. +- Через сам RPC получаете полный список access key аккаунта. +- Сужаете этот список до function-call-ключей, привязанных к `social.near`. +- Точно проверяете один выбранный ключ перед удалением. +- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -TARGET_CONTRACT_ID=crossword.puzzle.near -TARGET_METHOD_NAME=new_puzzle -TARGET_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_WANT_TO_CHECK' +Сразу важны два ограничения: + +- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. +- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». -# Пример живых значений, проверенных 19 апреля 2026 года: -# ACCOUNT_ID=mike.near -# TARGET_CONTRACT_ID=crossword.puzzle.near -# TARGET_METHOD_NAME=new_puzzle -# TARGET_PUBLIC_KEY='ed25519:otwaB1X88ocpmUdC1B5XaifucfDLmLKaonb26KqTj96' +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export SOCIAL_RECEIVER_ID=social.near +export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' +export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' +export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' ``` -1. Получите ключи аккаунта и сузьте их до целевого контракта. +1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. ```bash curl -s "$RPC_URL" \ @@ -180,28 +271,246 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/access-key-list.json >/dev/null - -jq --arg target_contract_id "$TARGET_CONTRACT_ID" '{ - candidate_keys: [ - .result.keys[] - | select( - .access_key.permission == "FullAccess" - or ( - (.access_key.permission | type) == "object" - and .access_key.permission.FunctionCall.receiver_id == $target_contract_id - ) - ) - | { - public_key, - nonce: .access_key.nonce, - permission: .access_key.permission + | tee /tmp/fastnear-access-keys.json >/dev/null + +jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + nonce: .access_key.nonce, + receiver_id: .access_key.permission.FunctionCall.receiver_id, + method_names: .access_key.permission.FunctionCall.method_names, + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } +' /tmp/fastnear-access-keys.json +``` + +Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. + +2. Ещё раз проверьте конкретный ключ перед удалением. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg public_key "$DELETE_PUBLIC_KEY" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key", + account_id: $account_id, + public_key: $public_key, + finality: "final" } - ] -}' /tmp/access-key-list.json + }')" \ + | jq '{nonce: .result.nonce, permission: .result.permission}' +``` + +3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. + +```bash +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + is_function_call: true, + limit: 10 + }')" \ + | jq '{ + account_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_success + } + ] + }' +``` + +Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. + +4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. + +Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. + +```bash +SIGNED_TX_BASE64="$( + node --input-type=module <<'EOF' + +const { + ACCOUNT_ID, + NETWORK_ID = 'mainnet', + RPC_URL = 'https://rpc.mainnet.fastnear.com', + DELETE_PUBLIC_KEY, + FULL_ACCESS_PUBLIC_KEY, + FULL_ACCESS_PRIVATE_KEY, +} = process.env; + +for (const name of [ + 'ACCOUNT_ID', + 'DELETE_PUBLIC_KEY', + 'FULL_ACCESS_PUBLIC_KEY', + 'FULL_ACCESS_PRIVATE_KEY', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); +const derivedPublicKey = keyPair.getPublicKey().toString(); + +if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { + throw new Error( + `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: ACCOUNT_ID, + public_key: FULL_ACCESS_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const transaction = transactions.createTransaction( + ACCOUNT_ID, + utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), + ACCOUNT_ID, + BigInt(accessKey.nonce) + 1n, + [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +)" +``` + +5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' +``` + +6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. + +```bash +if curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_access_key_list", + account_id: $account_id, + finality: "final" + } + }')" \ + | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' + .result.keys[] + | select(.public_key == $public_key) + ' >/dev/null; then + echo "Key is still present: $DELETE_PUBLIC_KEY" +else + echo "Key deleted: $DELETE_PUBLIC_KEY" +fi +``` + +**Зачем нужен следующий шаг?** + +Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. + +### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? + +Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. + + Стратегия + Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. + + 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. + 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. + 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. + +**Что вы делаете** + +- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. +- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. +- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. +- Подтягиваете кандидата по транзакциям и различаете три разных ключа: + - ключ, который был добавлен + - public key верхнеуровневого signer + - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` + +Сразу важны три детали про nonce: + +- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. +- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. +- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export TX_BASE_URL=https://tx.main.fastnear.com +export ACCOUNT_ID=YOUR_ACCOUNT_ID +export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' + +# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: +# export ACCOUNT_ID=mike.near +# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' ``` -2. Прочитайте точное состояние того ключа, который хотите оценить. +1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. ```bash curl -s "$RPC_URL" \ @@ -219,69 +528,172 @@ curl -s "$RPC_URL" \ finality: "final" } }')" \ - | tee /tmp/exact-access-key.json >/dev/null + | tee /tmp/key-origin-view.json >/dev/null -jq '{nonce: .result.nonce, permission: .result.permission}' /tmp/exact-access-key.json +CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" +ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" +SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" +SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" + +jq -n \ + --arg account_id "$ACCOUNT_ID" \ + --arg target_public_key "$TARGET_PUBLIC_KEY" \ + --argjson current_nonce "$CURRENT_NONCE" \ + --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ + --argjson search_from "$SEARCH_FROM" \ + --argjson search_to "$SEARCH_TO" \ + --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ + account_id: $account_id, + target_public_key: $target_public_key, + current_nonce: $current_nonce, + estimated_receipt_block: $estimated_receipt_block, + search_from_tx_block_height: $search_from, + search_to_tx_block_height: $search_to, + permission: ($permission | fromjson) + }' ``` -3. Превратите этот permission-object в ответ да или нет для этого контракта и метода. +Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. + +2. Ищите историю аккаунта только внутри этого диапазона блоков. ```bash -jq -n \ - --slurpfile key /tmp/exact-access-key.json \ - --arg target_contract_id "$TARGET_CONTRACT_ID" \ - --arg target_method_name "$TARGET_METHOD_NAME" ' - ($key[0].result.permission) as $permission - | if $permission == "FullAccess" then - { - can_call_now: true, - reason: "full_access" - } - elif $permission.FunctionCall.receiver_id != $target_contract_id then - { - can_call_now: false, - reason: "receiver_mismatch", - receiver_id: $permission.FunctionCall.receiver_id - } - elif ( - ($permission.FunctionCall.method_names | length) == 0 - or ($permission.FunctionCall.method_names | index($target_method_name)) - ) then - { - can_call_now: true, - reason: ( - if ($permission.FunctionCall.method_names | length) == 0 - then "function_call_any_method" - else "function_call_method_match" - end - ), - allowance: ($permission.FunctionCall.allowance // "unlimited") - } - else - { - can_call_now: false, - reason: "method_not_allowed", - allowed_methods: $permission.FunctionCall.method_names +curl -s "$TX_BASE_URL/v0/account" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --argjson from_tx_block_height "$SEARCH_FROM" \ + --argjson to_tx_block_height "$SEARCH_TO" '{ + account_id: $account_id, + is_real_signer: true, + from_tx_block_height: $from_tx_block_height, + to_tx_block_height: $to_tx_block_height, + desc: false, + limit: 50 + }')" \ + | tee /tmp/key-origin-candidates.json >/dev/null + +jq '{ + txs_count, + candidate_txs: [ + .account_txs[] + | { + transaction_hash, + tx_block_height, + is_signer, + is_real_signer, + is_predecessor, + is_receiver } - end' + ] +}' /tmp/key-origin-candidates.json +``` + +Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. + +3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. + +```bash +TX_HASHES_JSON="$( + jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json +)" + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ + | tee /tmp/key-origin-transactions.json >/dev/null + +jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "direct", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $tx.transaction.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }), + ($tx.transaction.actions[]? + | .Delegate? + | .delegate_action as $delegate + | $delegate.actions[]? + | .AddKey? + | select(.public_key == $target_public_key) + | { + authorization_mode: "delegated", + top_level_signer_id: $tx.transaction.signer_id, + top_level_signer_public_key: $tx.transaction.public_key, + authorizing_public_key: $delegate.public_key, + added_public_key: .public_key, + add_key_payload_nonce: .access_key.nonce, + permission: .access_key.permission + }) + ) + | { + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + tx_block_hash: $tx.execution_outcome.block_hash, + receiver_id: $tx.transaction.receiver_id + } + . +' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json +``` + +Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. + +Для примерного ключа `mike.near` выше совпадение оказывается делегированным: + +- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` +- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` +- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` +- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` + +4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. + +```bash +ADD_KEY_RECEIPT_ID="$( + jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' + .transactions[] + | .receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) + | .receipt.receipt_id + ' /tmp/key-origin-transactions.json | head -n 1 +)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + receipt_block_height: .receipt.block_height, + tx_block_height: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }' ``` -Для примерного ключа `mike.near` выше на 19 апреля 2026 года ответ получается `can_call_now: true`: это function-call-key для `crossword.puzzle.near`, а `method_names: ["new_puzzle"]` явно разрешает тот метод, который мы проверяем. +Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. **Зачем нужен следующий шаг?** -`view_access_key_list` — самый быстрый фильтр на уровне контракта. `view_access_key` — точная проверка полномочий для того public key, которым вы действительно хотите пользоваться. Если ответ `false`, вам нужен другой ключ или другая схема permissions, а не более глубокая историческая трассировка. +Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. -### Нужно ли этому получателю сначала зарегистрировать FT storage? +### Проверить регистрацию FT storage и затем перевести токены -Используйте этот сценарий, когда история звучит так: «я собираюсь отправить FT-токен и хочу получить простой ответ “нужен ли сначала `storage_deposit`?”». +Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». Стратегия - Сначала прочитайте storage-состояние получателя и остановитесь, как только станет понятно, может ли `ft_transfer` уже пройти. + Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. 02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит. - 03jq превращает эти два чтения в один ответ: «перевод уже может идти» или «сначала нужен `storage_deposit`». + 03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог. **Сеть** @@ -292,19 +704,24 @@ jq -n \ - [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) - [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) -В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Здесь важен именно read-only-ответ: нужен ли сначала `storage_deposit`, или путь перевода уже может продолжаться. +В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. **Что вы делаете** - Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. -- Получаете точный минимальный размер storage deposit на этом же контракте. -- Останавливаетесь, как только понимаете: `ft_transfer` уже может идти или сначала нужен `storage_deposit`. +- При необходимости получаете минимальный размер storage deposit. +- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. +- Подтверждаете баланс получателя тем же view-методом самого контракта. ```bash export NETWORK_ID=testnet export RPC_URL=https://rpc.testnet.fastnear.com export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet +export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' +export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' +export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' ``` 1. Проверьте, зарегистрирован ли получатель на FT-контракте. @@ -340,7 +757,7 @@ jq '{ }' /tmp/ft-storage-balance.json ``` -2. Получите минимальный storage deposit на этом же контракте. +2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. ```bash MIN_STORAGE_YOCTO="$( @@ -365,103 +782,326 @@ MIN_STORAGE_YOCTO="$( printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" ``` -3. Превратите эти два чтения в один ответ о готовности перевода. +3. Определите одну переиспользуемую функцию подписи для function-call к контракту. + +Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. ```bash -jq -n \ - --slurpfile balance /tmp/ft-storage-balance.json \ - --slurpfile bounds /tmp/ft-storage-bounds.json \ - --arg receiver_account_id "$RECEIVER_ACCOUNT_ID" ' - ( - $balance[0].result.result - | if length == 0 then null else (implode | fromjson) end - ) as $storage - | ( - $bounds[0].result.result - | implode - | fromjson - ) as $bounds - | { - receiver_account_id: $receiver_account_id, - receiver_registered: ($storage != null), - current_storage: $storage, - minimum_storage_deposit_yocto: $bounds.min, - next_step: ( - if $storage != null - then "получатель уже зарегистрирован; ft_transfer может продолжаться" - else "сначала отправьте storage_deposit, потом делайте ft_transfer" - end - ) - }' +sign_function_call() { + METHOD_NAME="$1" \ + ARGS_JSON="$2" \ + DEPOSIT_YOCTO="$3" \ + GAS_TGAS="$4" \ + node --input-type=module <<'EOF' + +const { + NETWORK_ID = 'testnet', + RPC_URL = 'https://rpc.testnet.fastnear.com', + TOKEN_CONTRACT_ID, + SENDER_ACCOUNT_ID, + SENDER_PUBLIC_KEY, + SENDER_PRIVATE_KEY, + METHOD_NAME, + ARGS_JSON, + DEPOSIT_YOCTO = '0', + GAS_TGAS = '100', +} = process.env; + +for (const name of [ + 'TOKEN_CONTRACT_ID', + 'SENDER_ACCOUNT_ID', + 'SENDER_PUBLIC_KEY', + 'SENDER_PRIVATE_KEY', + 'METHOD_NAME', + 'ARGS_JSON', +]) { + if (!process.env[name]) { + throw new Error(`Missing ${name}`); + } +} + +async function rpc(method, params) { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'fastnear', + method, + params, + }), + }); + const json = await response.json(); + if (json.error) { + throw new Error(JSON.stringify(json.error)); + } + return json.result; +} + +const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); +const signer = await InMemorySigner.fromKeyPair( + NETWORK_ID, + SENDER_ACCOUNT_ID, + keyPair +); + +const derivedPublicKey = keyPair.getPublicKey().toString(); +if (derivedPublicKey !== SENDER_PUBLIC_KEY) { + throw new Error( + `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` + ); +} + +const accessKey = await rpc('query', { + request_type: 'view_access_key', + account_id: SENDER_ACCOUNT_ID, + public_key: SENDER_PUBLIC_KEY, + finality: 'final', +}); + +const block = await rpc('block', { finality: 'final' }); + +const action = transactions.functionCall( + METHOD_NAME, + Buffer.from(ARGS_JSON), + BigInt(GAS_TGAS) * 10n ** 12n, + BigInt(DEPOSIT_YOCTO) +); + +const transaction = transactions.createTransaction( + SENDER_ACCOUNT_ID, + utils.PublicKey.fromString(SENDER_PUBLIC_KEY), + TOKEN_CONTRACT_ID, + BigInt(accessKey.nonce) + 1n, + [action], + utils.serialize.base_decode(block.header.hash) +); + +const [, signedTx] = await transactions.signTransaction( + transaction, + signer, + SENDER_ACCOUNT_ID, + NETWORK_ID +); + +process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); +EOF +} ``` -**Зачем нужен следующий шаг?** - -Это чистый RPC-вопрос в этом workflow: «зарегистрирован ли уже получатель и какой минимальный депозит потребует контракт, если нет?» Подписанный write-path зависит уже от вашего wallet, CLI или backend-интеграции, поэтому в самый маленький core RPC-пример он не входит. - -## Чтения контракта и сырое состояние - -Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» - -### Прочитать счётчик прямо из состояния контракта, а потом подтвердить его через view-метод +4. При необходимости сначала зарегистрируйте storage для получателя. -Используйте этот сценарий, когда вы уже знаете точное семейство storage-ключей и хотите увидеть самый короткий контраст между raw state и публичным методом чтения контракта. +```bash +if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then + SIGNED_TX_BASE64="$( + sign_function_call \ + storage_deposit \ + "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id, + registration_only: true + }')" \ + "$MIN_STORAGE_YOCTO" \ + 100 + )" -Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`: + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash + }' +fi +``` -- `view_state` читает сырой ключ `STATE` напрямую -- `call_function get_num` спрашивает у контракта то же текущее число +5. После готовности storage переведите FT. ```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export CONTRACT_ID=counter.near-examples.testnet -export STATE_PREFIX_BASE64=U1RBVEU= +SIGNED_TX_BASE64="$( + sign_function_call \ + ft_transfer \ + "$(jq -nc \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ + receiver_id: $receiver_id, + amount: $amount, + memo: "FastNear RPC example" + }')" \ + 1 \ + 100 +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "send_tx", + params: { + signed_tx_base64: $signed_tx_base64, + wait_until: "FINAL" + } + }')" \ + | jq '{ + final_execution_status: .result.final_execution_status, + transaction_hash: .result.transaction.hash, + status: .result.status + }' ``` -1. Сначала прочитайте сырой ключ `STATE`. +6. Подтвердите FT-баланс получателя тем же view-методом контракта. ```bash +RECEIVER_BALANCE_ARGS_BASE64="$( + jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' +)" + curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc \ - --arg account_id "$CONTRACT_ID" \ - --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ + --arg account_id "$TOKEN_CONTRACT_ID" \ + --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { - request_type: "view_state", + request_type: "call_function", account_id: $account_id, - prefix_base64: $prefix_base64, + method_name: "ft_balance_of", + args_base64: $args_base64, finality: "final" } }')" \ - | tee /tmp/counter-view-state.json >/dev/null - -jq '{ - key: (.result.values[0].key | @base64d), + | jq '{ + receiver_balance: (.result.result | implode | fromjson) + }' +``` + +**Зачем нужен следующий шаг?** + +Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. + +## Чтения контракта и сырое состояние + +Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» + +### Как прочитать сырое состояние контракта напрямую? + +Используйте этот сценарий, когда у контракта нет нужного view-метода или когда вам нужно проверить саму схему хранения, а не просто довериться ответу метода. + +В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: сначала вы читаете storage напрямую, а потом подтверждаете, что публичный view-метод контракта даёт тот же ответ: + +- `view_state` читает сырой ключ `STATE` прямо из storage контракта +- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API + + Стратегия + Сначала прочитайте storage напрямую, а уже потом дайте контракту подтвердить тот же ответ через view-метод. + + 01RPC view_state читает сырой ключ STATE, не запуская код контракта. + 02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта. + 03RPC call_function get_num — это удобная перепроверка того, что прямое чтение storage и view-метод по-прежнему дают один и тот же ответ. + +Здесь важнее ментальная модель, чем сам счётчик: + +- `view_state` — это прямое чтение storage из trie +- `call_function` исполняет read-only-метод контракта +- оба способа могут ответить на один и тот же вопрос, но делают разную работу + +```mermaid +flowchart LR + S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] + R --> D["Декодировать base64 + Borsh"] + D --> N["Знаковое значение счётчика"] + C["RPC call_function get_num"] --> J["JSON-результат метода"] + N --> X["Сравнить"] + J --> X + X --> A["Одно и то же текущее значение"] +``` + +**Что вы делаете** + +- Читаете сырой ключ `STATE` из storage контракта. +- Декодируете возвращённые байты в текущее знаковое значение счётчика. +- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. + +```bash +export NETWORK_ID=testnet +export RPC_URL=https://rpc.testnet.fastnear.com +export CONTRACT_ID=counter.near-examples.testnet +export STATE_PREFIX_BASE64=U1RBVEU= +``` + +1. Сначала прочитайте сырое состояние контракта. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$CONTRACT_ID" \ + --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "view_state", + account_id: $account_id, + prefix_base64: $prefix_base64, + finality: "final" + } + }')" \ + | tee /tmp/counter-view-state.json >/dev/null + +jq '{ + block_height: .result.block_height, + key_base64: .result.values[0].key, value_base64: .result.values[0].value }' /tmp/counter-view-state.json + +jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json ``` -Здесь должен появиться `key: "STATE"`. Это и есть тот случай, когда `view_state` уместен: семейство ключей вам уже известно заранее. +Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. -2. Декодируйте сырые байты. +2. Декодируйте байты значения в знаковое число счётчика. ```bash RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" -python3 - "$RAW_VALUE_BASE64" <<'PY' +python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . raw = base64.b64decode(sys.argv[1]) -print(int.from_bytes(raw, "little", signed=True)) + +print(json.dumps({ + "value_base64": sys.argv[1], + "bytes": list(raw), + "hex": raw.hex(), + "signed_i8": int.from_bytes(raw, "little", signed=True), + "unsigned_u8": int.from_bytes(raw, "little", signed=False), +})) PY ``` -Для этого контракта `STATE` — это однобайтовый знаковый счётчик, поэтому декодирование совсем простое. На других контрактах layout может быть гораздо менее дружелюбным: near-sdk-коллекции и Borsh-сериализованные структуры часто выводят storage-ключи из префиксов и внутренних схем ключей, поэтому `view_state` остаётся практичным только когда вы уже знаете точный layout, который хотите читать. Правило остаётся тем же: сначала байты, потом схема. +Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. -3. Теперь спросите контракт привычным способом и сравните. +Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. + +Переиспользуемый рецепт здесь короткий: + +- `view_state` возвращает сырые байты в base64 +- вы декодируете эти байты по известной схеме хранения контракта +- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема + +3. Теперь спросите контракт более привычным способом и сравните. ```bash curl -s "$RPC_URL" \ @@ -486,7 +1126,7 @@ jq '{ }' /tmp/counter-call-function.json ``` -4. Сравните оба ответа. +4. Сравните оба ответа напрямую. ```bash RAW_STATE_NUMBER="$( @@ -510,194 +1150,704 @@ jq -n \ }' ``` +Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: + +- `view_state` ответил на вопрос, прочитав storage напрямую +- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта + **Зачем нужен следующий шаг?** -Используйте `view_state`, когда вы уже знаете точное семейство storage-ключей и хотите raw bytes. Используйте `call_function`, когда вам нужен публичный метод чтения самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда уже стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). +Используйте `view_state`, когда настоящий вопрос относится к точному storage, отсутствующему view-методу или проверке известного семейства ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). + +## Трассировка чанков и шардов + +Начинайте отсюда, когда вопрос уже не просто «транзакция прошла или нет?», а «какой именно чанк на шарде исполнил каждый шаг работы?» + +### Проследить, как сгенерированная `Transfer`-receipt переходит из одного чанка на шарде в другой + +Используйте этот сценарий, когда вызов контракта был только началом истории. В этом зафиксированном mainnet-примере подписанная транзакция стартует на шарде `11`, а сгенерированная `Transfer`-receipt заканчивает путь уже на шарде `6`. Именно ради таких cross-shard handoff и имеет смысл смотреть на чанки. -### Какие ERC-20 токены из Rainbow Bridge существуют на NEAR и сколько одного такого токена сейчас в обращении? +Этот walkthrough привязан к: -Используйте этот сценарий, когда хотите найти Rainbow Bridge ERC-20 контракты и посмотреть живой объём одного токена на NEAR. Rainbow Bridge развёртывает по одному NEAR-контракту на каждый bridged ERC-20 токен, а `factory.bridge.near` их перечисляет. +- транзакции `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` от `7419369993.tg` к `game.hot.tg` с вызовом `l2_claim` +- исходному чанку `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` на шарде `11` в блоке `194623170` +- чанку первой receipt `FJWpAYzVXbZwqJUbGXELTnnBBkdvc6W8vWkwuUA3Zwz9` на шарде `11` в блоке `194623171` +- сгенерированной `Transfer`-receipt `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` +- конечному чанку `EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU` на шарде `6` в блоке `194623172` Стратегия - Одно чтение factory перечисляет token-контракты. Ещё два небольших view-вызова по одному токену показывают, что это за токен и сколько его сейчас на NEAR. + Сначала восстановите receipt-цепочку, потом напрямую посмотрите на сгенерированную receipt, а затем привяжите каждый шаг к тому чанку на шарде, который действительно нёс эту работу. - 01RPC call_function get_tokens_accounts по factory.bridge.near возвращает развёрнутые bridged token-контракты. - 02Следующий RPC call_function по одному bridged token-контракту возвращает метаданные токена: имя, тикер и число десятичных знаков. - 03Ещё один RPC call_function по тому же контракту возвращает текущее сырое значение объёма в обращении на NEAR. + 01RPC EXPERIMENTAL_tx_status быстро показывает граф receipts и в какие следующие блоки перешла работа. + 02RPC EXPERIMENTAL_receipt позволяет посмотреть на тело сгенерированной receipt напрямую, а не выводить его только из логов. + 03RPC chunk по блоку и шарду или по хешу чанка доказывает, какая именно единица исполнения на шарде нёсла каждый шаг. + +Оба experimental-метода здесь очень уместны: `EXPERIMENTAL_tx_status` быстро находит граф receipts, а `EXPERIMENTAL_receipt` показывает тело сгенерированной receipt ещё до того, как вы привяжете её обратно к чанкам. + +```mermaid +flowchart LR + A["Tx 8xrc...
блок 194623170
чанк Bfyd...
шард 11"] --> B["Receipt AFC2...
блок 194623171
чанк FJWp...
шард 11
логи ft_mint"] + B --> C["Сгенерированная receipt TtRn...
Transfer 1800930478788300000000 yoctoNEAR"] + C --> D["Чанк EPau...
блок 194623172
шард 6
receipt исполняется"] +``` **Что вы делаете** -- Спрашиваете у bridge factory обо всех bridged token-контрактах, которые она создала. -- Выбираете один bridged token-контракт и читаете его метаданные. -- Читаете total supply того же контракта и переводите его в человеческие единицы через `decimals`. +- Сначала восстанавливаете receipt-цепочку из транзакции. +- Напрямую смотрите на тело сгенерированной `Transfer`-receipt. +- Используете координаты блока и шарда там, где они уже известны. +- Используете хеш чанка там, где другой инструмент уже выдал точный конечный чанк. ```bash export NETWORK_ID=mainnet export RPC_URL=https://rpc.mainnet.fastnear.com -export FACTORY_ID=factory.bridge.near -export TOKENS_FILE=/tmp/rainbow-bridge-tokens.json +export TX_HASH=8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP +export SIGNER_ACCOUNT_ID=7419369993.tg +export ORIGIN_BLOCK_HEIGHT=194623170 +export ORIGIN_SHARD_ID=11 +export RECEIPT_BLOCK_HEIGHT=194623171 +export RECEIPT_SHARD_ID=11 +export GENERATED_RECEIPT_ID=TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4 +export DESTINATION_CHUNK_HASH=EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU +``` + +1. Начните с `EXPERIMENTAL_tx_status`, чтобы сначала увидеть граф receipts, а уже потом думать о чанках. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: [$tx_hash, $signer_account_id] + }')" \ + | tee /tmp/chunk-trace-status.json >/dev/null + +jq '{ + final_execution_status: .result.final_execution_status, + transaction_handoff: .result.transaction_outcome.outcome.status, + receipts: ( + .result.receipts_outcome + | map({ + receipt_id: .id, + executor_id: .outcome.executor_id, + block_hash, + status: .outcome.status + }) + ) +}' /tmp/chunk-trace-status.json ``` -1. Получите список bridged token-контрактов. +На что смотреть: + +- подписанная транзакция передаёт работу в receipt `AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y` +- позже в том же графе receipts исполняется `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` для `7419369993.tg` +- уже одного tx status достаточно, чтобы увидеть: настоящая работа продолжилась после исходной подписанной транзакции + +2. Посмотрите на сгенерированную receipt напрямую, чтобы доказать, что это действительно `Transfer`-receipt. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$FACTORY_ID" '{ + --data "$(jq -nc --arg receipt_id "$GENERATED_RECEIPT_ID" '{ jsonrpc: "2.0", id: "fastnear", - method: "query", + method: "EXPERIMENTAL_receipt", params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_tokens_accounts", - args_base64: "e30=", - finality: "final" + receipt_id: $receipt_id } }')" \ - | tee "$TOKENS_FILE" >/dev/null + | tee /tmp/chunk-trace-receipt.json >/dev/null -jq -r '.result.result | implode | fromjson | .[]' "$TOKENS_FILE" +jq '{ + predecessor_id: .result.predecessor_id, + receiver_id: .result.receiver_id, + signer_id: .result.receipt.Action.signer_id, + signer_public_key: .result.receipt.Action.signer_public_key, + actions: .result.receipt.Action.actions +}' /tmp/chunk-trace-receipt.json ``` -Каждая строка — это один bridged FT-контракт на NEAR в форме `.factory.bridge.near`. Например, bridged ERC-20 USDT с Ethereum-адреса `0xdAC17F958D2ee523a2206206994597C13D831ec7` появляется как `dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near`. +Именно здесь история по шардам становится конкретной: эта цепочка исполнения контракта сгенерировала `Transfer` action receipt от `system` к `7419369993.tg` с депозитом `1800930478788300000000`. -2. Прочитайте метаданные одного токен-контракта. +3. Используйте `chunk` по блоку и шарду, чтобы найти исходную подписанную транзакцию на шарде `11`. ```bash -export TOKEN_ID=dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --argjson block_id "$ORIGIN_BLOCK_HEIGHT" \ + --argjson shard_id "$ORIGIN_SHARD_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "chunk", + params: { + block_id: $block_id, + shard_id: $shard_id + } + }')" \ + | jq --arg tx_hash "$TX_HASH" '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created + }, + matching_transaction: ( + .result.transactions[] + | select(.hash == $tx_hash) + | { + hash, + signer_id, + receiver_id + } + ) + }' +``` + +Это самый чистый use case для `chunk` по блоку и шарду: координаты уже известны, а вам нужна точная единица исполнения на шарде, которая несла исходную подписанную транзакцию. + +4. Оставайтесь на том же маршруте и посмотрите, как первая receipt исполняется в следующем блоке на том же шарде. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --argjson block_id "$RECEIPT_BLOCK_HEIGHT" \ + --argjson shard_id "$RECEIPT_SHARD_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "chunk", + params: { + block_id: $block_id, + shard_id: $shard_id + } + }')" \ + | jq '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created, + tx_root: .result.header.tx_root, + gas_used: .result.header.gas_used + }, + tx_count: (.result.transactions | length), + receipt_count: (.result.receipts | length), + matching_receipt: ( + .result.receipts[] + | select(.receipt_id == "AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y") + | { + receipt_id, + predecessor_id, + receiver_id + } + ) + }' +``` + +Вот здесь chunks наконец становятся естественными: + +- у чанка `tx_root = 11111111111111111111111111111111` +- `tx_count` равен `0` +- но шард всё равно жжёт gas и исполняет receipt `AFC2...` +То есть этот шард реально сделал работу в этом блоке, хотя новая подписанная транзакция прямо в самом чанке не появилась. + +5. Переключайтесь на `chunk` по хешу, когда другой инструмент уже выдал точный конечный чанк. + +```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + --data "$(jq -nc --arg chunk_id "$DESTINATION_CHUNK_HASH" '{ jsonrpc: "2.0", id: "fastnear", - method: "query", + method: "chunk", params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_metadata", - args_base64: "e30=", - finality: "final" + chunk_id: $chunk_id } }')" \ - | tee /tmp/rainbow-bridge-token-metadata.json >/dev/null + | jq --arg receipt_id "$GENERATED_RECEIPT_ID" '{ + header: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_created: .result.header.height_created, + tx_root: .result.header.tx_root, + gas_used: .result.header.gas_used + }, + tx_count: (.result.transactions | length), + receipt_count: (.result.receipts | length), + matching_receipt: ( + .result.receipts[] + | select(.receipt_id == $receipt_id) + | { + receipt_id, + predecessor_id, + receiver_id + } + ) + }' +``` + +Это и подтверждает cross-shard hop: -jq '.result.result | implode | fromjson | {name, symbol, decimals}' /tmp/rainbow-bridge-token-metadata.json +- сгенерированная `Transfer`-receipt исполняется в чанке `EPau...` +- этот чанк живёт на шарде `6`, а не на шарде `11` +- подписанная транзакция стартовала на одном шарде, а следующая receipt завершилась уже на другом + +**Зачем нужен следующий шаг?** + +Используйте [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда вы знаете координаты блока и шарда и хотите буквально спросить: «что этот шард исполнил в этом блоке?» Используйте [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. Используйте [EXPERIMENTAL_tx_status](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) и [EXPERIMENTAL_receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), когда настоящий вопрос относится к трассировке на уровне receipts. Если ещё нужны state changes и produced receipts, расширяйтесь в [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard). + +## Точные чтения NEAR Social и BOS + +Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. + +### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? + +Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». + + Стратегия + Спросите у social.near ровно о двух вещах, которые важны до подписи. + + 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. + 02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near. + 03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer. + +Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: + +- есть ли у целевого аккаунта storage на `social.near`? +- если есть, осталось ли там ещё место? +- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? + +**Официальные ссылки** + +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +**Что вы делаете** + +- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. +- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. +- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. +- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». + +```bash +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mike.near +export SIGNER_ACCOUNT_ID=mike.near ``` -3. Прочитайте текущий total supply на NEAR и переведите его в человеческие единицы. +1. Сначала проверьте сам аккаунт signer. ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_ID" '{ + --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "query", params: { - request_type: "call_function", + request_type: "view_account", account_id: $account_id, - method_name: "ft_total_supply", - args_base64: "e30=", finality: "final" } }')" \ - | tee /tmp/rainbow-bridge-token-supply.json >/dev/null + | tee /tmp/social-publish-signer.json >/dev/null + +jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ + signer_account_id: $signer_account_id, + amount: .result.amount, + locked: .result.locked, + storage_usage: .result.storage_usage +}' /tmp/social-publish-signer.json +``` -RAW_SUPPLY="$( - jq -r '.result.result | implode | fromjson' /tmp/rainbow-bridge-token-supply.json -)" +Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. + +2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. -DECIMALS="$( - jq -r '.result.result | implode | fromjson | .decimals' /tmp/rainbow-bridge-token-metadata.json +```bash +SOCIAL_STORAGE_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id + }' | base64 | tr -d '\n' )" -HUMAN_SUPPLY="$( - python3 - "$RAW_SUPPLY" "$DECIMALS" <<'PY' -from decimal import Decimal +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get_account_storage", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-account-storage.json >/dev/null -raw = Decimal(sys.argv[1]) -decimals = int(sys.argv[2]) -human = raw / (Decimal(10) ** decimals) -print(human) -PY +jq --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + storage: (.result.result | implode | fromjson), + storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) +}' /tmp/social-account-storage.json +``` + +Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. + +3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. + +```bash +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + jq -n --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + signer_matches_target: true, + permission_granted: true, + reason: "owner write" + }' +else + WRITE_PERMISSION_ARGS_BASE64="$( + jq -nc \ + --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ + --arg key "$ACCOUNT_ID" '{ + predecessor_id: $predecessor_id, + key: $key + }' | base64 | tr -d '\n' + )" + + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '{ + signer_matches_target: false, + permission_granted: (.result.result | implode | fromjson) + }' +fi +``` + +4. Сведите проверку storage и разрешения в один читаемый итог. + +```bash +AVAILABLE_BYTES="$( + jq -r ' + .result.result + | if length == 0 then "0" + else (implode | fromjson | .available_bytes // 0 | tostring) + end + ' /tmp/social-account-storage.json )" +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + PERMISSION_GRANTED=true +else + PERMISSION_GRANTED="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "is_write_permission_granted", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq -r '.result.result | implode | fromjson' + )" +fi + jq -n \ - --arg token_id "$TOKEN_ID" \ - --arg raw_supply "$RAW_SUPPLY" \ - --argjson decimals "$DECIMALS" \ - --arg human_supply "$HUMAN_SUPPLY" '{ - token_id: $token_id, - raw_supply: $raw_supply, - decimals: $decimals, - human_supply: $human_supply + --arg account_id "$ACCOUNT_ID" \ + --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ + --argjson available_bytes "$AVAILABLE_BYTES" \ + --argjson permission_granted "$PERMISSION_GRANTED" '{ + account_id: $account_id, + signer_account_id: $signer_account_id, + storage_ready: ($available_bytes > 0), + permission_ready: $permission_granted, + ready_to_publish_now: (($available_bytes > 0) and $permission_granted) }' ``` -Результат `ft_total_supply` приходит в минимальных единицах токена. Используйте `decimals` из ответа предыдущего шага, чтобы перевести его в человекочитаемый объём в обращении. +Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. + +**Зачем нужен следующий шаг?** + +Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. -#### Необязательное расширение: показать первые несколько bridged token-ов с метаданными и объёмом в обращении +### Что прямо сейчас содержит `mob.near/widget/Profile`? -Используйте это расширение, когда нужен быстрый sample-инвентарь и вы всё ещё хотите оставаться в RPC. +Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». + + Стратегия + Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. + + 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. + 02RPC call_function get читает точный исходник widget/Profile. + 03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples. + +**Официальные ссылки** + +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) + +**Что вы делаете** + +- Спрашиваете у `social.near` каталог виджетов под `mob.near`. +- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. +- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. +- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). ```bash -export TOKEN_SAMPLE_COUNT=5 +export NETWORK_ID=mainnet +export RPC_URL=https://rpc.mainnet.fastnear.com +export SOCIAL_CONTRACT_ID=social.near +export ACCOUNT_ID=mob.near +export WIDGET_NAME=Profile +``` -python3 <<'PY' -from decimal import Decimal +1. Получите каталог виджетов и сохраните высоты блоков последней записи. -TOKENS_FILE = os.environ["TOKENS_FILE"] -LIMIT = int(os.environ.get("TOKEN_SAMPLE_COUNT", "5")) -RPC_URL = os.environ["RPC_URL"] +```bash +WIDGET_KEYS_ARGS_BASE64="$( + jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} + }' | base64 | tr -d '\n' +)" -def decode_result(result): - return json.loads("".join(chr(b) for b in result)) +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "keys", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-keys.json >/dev/null + +jq --arg account_id "$ACCOUNT_ID" ' + .result.result + | implode + | fromjson + | .[$account_id].widget + | to_entries + | sort_by(.value * -1) + | map({ + widget_name: .key, + last_write_block: .value + }) + | .[0:20] +' /tmp/social-widget-keys.json +``` -with open(TOKENS_FILE) as fh: - token_ids = decode_result(json.load(fh)["result"]["result"])[:LIMIT] +2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. -def rpc_call(account_id, method_name): - payload = { - "jsonrpc": "2.0", - "id": "fastnear", - "method": "query", - "params": { - "request_type": "call_function", - "account_id": account_id, - "method_name": method_name, - "args_base64": "e30=", - "finality": "final", - }, - } - import subprocess - raw = subprocess.check_output([ - "curl", "-s", RPC_URL, - "-H", "content-type: application/json", - "--data", json.dumps(payload), - ], text=True) - return decode_result(json.loads(raw)["result"]["result"]) - -print(f"{'token_id':<56} {'symbol':<12} {'decimals':>8} {'raw_supply':>24} {'human_supply':>24} name") -for token_id in token_ids: - metadata = rpc_call(token_id, "ft_metadata") - raw_supply = rpc_call(token_id, "ft_total_supply") - human_supply = Decimal(raw_supply) / (Decimal(10) ** metadata["decimals"]) - print( - f"{token_id:<56} " - f"{metadata['symbol']:<12} " - f"{metadata['decimals']:>8} " - f"{raw_supply:>24} " - f"{str(human_supply):>24} " - f"{metadata['name']}" - ) -PY +```bash +WIDGET_GET_ARGS_BASE64="$( + jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget_name)] + }' | base64 | tr -d '\n' +)" + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$SOCIAL_CONTRACT_ID" \ + --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | tee /tmp/social-widget-source.json >/dev/null + +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + | split("\n")[0:25] + | join("\n") + ' /tmp/social-widget-source.json ``` +3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. + +```bash +jq -r \ + --arg account_id "$ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" ' + .result.result + | implode + | fromjson + | .[$account_id].widget[$widget_name] + ' /tmp/social-widget-keys.json \ + | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +``` + +На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. + **Зачем нужен следующий шаг?** -Оставайтесь в RPC, пока вопрос звучит как «какие bridged token-контракты существуют и сколько одного такого токена сейчас в обращении?» Factory — это источник истины для множества bridged token-ов, а каждый token-контракт сам отвечает за свои метаданные и объём в обращении через стандартные NEP-141 view-методы. Если следующий вопрос становится «кто держит этот токен?», переключайтесь на [V1 FT Top Holders](https://docs.fastnear.com/ru/api/v1/ft-top), а не пытайтесь обходить holders через RPC. +Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. + +## Частые задачи + +### Проверить точное состояние аккаунта или ключа доступа + +**Начните здесь** + +- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для точных полей аккаунта. +- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. + +**Следующая страница при необходимости** + +- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» + +**Остановитесь, когда** + +- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. + +**Переходите дальше, когда** + +- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. +- Пользователя интересует не текущее состояние, а недавняя история активности. + +### Трассировать исполнение на уровне шарда через чанки + +**Начните здесь** + +- Начните с примера выше, если настоящий вопрос звучит как «какой чанк или шард вообще исполнил эту receipt?» +- [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда координаты блока и шарда уже известны. +- [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. + +**Следующая страница при необходимости** + +- [Experimental Receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), если нужно само тело сгенерированной receipt. +- [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard), если chunk payload уже недостаточен и ещё нужны state changes или produced receipts. +- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если вопрос превращается в более широкое async- или callback-расследование. + +**Остановитесь, когда** + +- Уже можно назвать, какой именно чанк и какой шард несли ту работу, которая была важна. + +**Переходите дальше, когда** + +- Пользователю нужны уже не детали исполнения на уровне шарда, а читаемая история транзакции. Тогда переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Проверить один точный блок или снимок состояния протокола + +**Начните здесь** + +- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. +- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» +- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. + +**Следующая страница при необходимости** + +- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. +- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» + +**Остановитесь, когда** + +- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. + +**Переходите дальше, когда** + +- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Что этот контракт возвращает прямо сейчас? + +**Начните здесь** + +- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» +- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. +- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. +- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» + +**Следующая страница при необходимости** + +- [FastNear API](https://docs.fastnear.com/ru/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. +- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» + +**Остановитесь, когда** + +- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. + +**Переходите дальше, когда** + +- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. +- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» + +### Отправить транзакцию и подтвердить результат + +**Начните здесь** + +- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. +- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. +- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. +- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. + +**Следующая страница при необходимости** + +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. +- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. +- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» + +**Остановитесь, когда** + +- У вас уже есть результат отправки и нужный финальный статус. + +**Переходите дальше, когда** + +- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. +- Нужен уже не единичный статус, а более широкий сценарий расследования. ## Частые ошибки From 55a65488cccf835e597cfcc6ed0cf733ea140377 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sun, 19 Apr 2026 14:11:02 -0700 Subject: [PATCH 23/35] docs: polish examples across core surfaces --- docs/api/examples.md | 520 +++-- docs/fastdata/kv/examples.md | 273 +-- docs/fastdata/kv/index.md | 41 +- docs/neardata/examples.md | 414 ++-- docs/neardata/index.md | 18 +- docs/transfers/examples.md | 298 +-- .../current/api/examples.md | 520 +++-- .../current/fastdata/kv/examples.md | 273 +-- .../current/fastdata/kv/index.md | 41 +- .../current/neardata/examples.md | 414 ++-- .../current/neardata/index.md | 18 +- .../current/transfers/examples.md | 298 +-- static/ru/api/examples.md | 514 +++-- static/ru/api/examples/index.md | 514 +++-- static/ru/fastdata/kv.md | 41 +- static/ru/fastdata/kv/examples.md | 272 +-- static/ru/fastdata/kv/examples/index.md | 272 +-- static/ru/fastdata/kv/index.md | 41 +- static/ru/guides/llms.txt | 12 +- static/ru/llms-full.txt | 1963 ++++++----------- static/ru/llms.txt | 12 +- static/ru/neardata.md | 16 +- static/ru/neardata/examples.md | 399 ++-- static/ru/neardata/examples/index.md | 399 ++-- static/ru/neardata/index.md | 16 +- static/ru/transfers/examples.md | 297 +-- static/ru/transfers/examples/index.md | 297 +-- 27 files changed, 3578 insertions(+), 4615 deletions(-) diff --git a/docs/api/examples.md b/docs/api/examples.md index 84cdcaf..6ea74e7 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -2,43 +2,16 @@ sidebar_label: Examples slug: /api/examples title: API Examples -description: Plain-language workflows for using FastNear API docs for account lookups, asset inventory, and direct staking checks. +description: Plain-language workflows for using FastNear API docs for account lookups, holdings checks, NFT gating, and staking classification. displayed_sidebar: fastnearApiSidebar page_actions: - markdown --- -## Quick start - -Start with one identity lookup and one broad account read. - -```bash -API_BASE_URL=https://api.fastnear.com -PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' - -ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" - -ACCOUNT_ID="$( - curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ - | jq -r '.account_ids[0]' -)" - -echo "$ACCOUNT_ID" - -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' -``` - -This is the shortest path to “which account is this key?” and “what does that wallet look like right now?” - ## Worked walkthroughs +Read this page as a short ladder: first resolve who the account is, then classify the wallet shape, then use one richer provenance flow when you want to turn a live BOS artifact into a minted record. + ### Resolve a public key, then fetch the account snapshot Use this when you have a public key first and the next user-facing question is “which account is this?” followed immediately by “what does that account look like right now?” @@ -46,7 +19,7 @@ Use this when you have a public key first and the next user-facing question is
Strategy -

Resolve identity first, then either inspect one account immediately or fan out across the returned list when the key maps to more than one account.

+

Resolve identity first, then reuse the same account ID for one readable wallet snapshot.

01GET /v1/public_key gives the candidate account_id values for the key.

@@ -58,8 +31,8 @@ Use this when you have a public key first and the next user-facing question is **What you're doing** - Resolve the public key to one or more account IDs. -- Count how many account IDs came back before you commit to one. -- Reuse one account ID immediately, or loop through the full list when the key maps to multiple accounts. +- Extract the first matching account ID with `jq`. +- Reuse that value in the full account snapshot endpoint. ```bash API_BASE_URL=https://api.fastnear.com @@ -75,56 +48,35 @@ ACCOUNT_ID="$( | jq -r '.account_ids[0]' )" -ACCOUNT_COUNT="$( - jq -r '.account_ids | length' /tmp/fastnear-public-key.json -)" +jq '{account_ids}' /tmp/fastnear-public-key.json -jq '{ - account_ids, - account_count: (.account_ids | length) -}' /tmp/fastnear-public-key.json - -if [ "$ACCOUNT_COUNT" -eq 1 ]; then - curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' -else - jq -r '.account_ids[]' /tmp/fastnear-public-key.json \ - | while read -r candidate_account_id; do - curl -s "$API_BASE_URL/v1/account/$candidate_account_id/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' - done -fi +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' ``` **Why this next step?** -The public-key lookup tells you which account or accounts you are dealing with. The full account snapshot is the natural next read when you want balances, NFTs, staking, and pools in one response. If the key maps to multiple accounts instead of one, this is the point where you either inspect each returned `account_id` or move to [V1 Public Key Lookup All](/api/v1/public-key-all) for the broader historical view. +The public-key lookup tells you which account you are dealing with. The full account snapshot is the natural next read when you want balances, NFTs, staking, and pools in one response. If the key maps to multiple accounts instead of one, move to [V1 Public Key Lookup All](/api/v1/public-key-all) or loop through each returned `account_id`. -### Does this account have direct staking right now? +### Does this wallet show direct staking, liquid staking tokens, or both? -Use this when the user story is simple: “tell me whether this account has visible direct staking pools right now, and show me which pools they are.” +Use this when the user story is “show me whether this wallet currently shows direct staking pool positions, liquid staking tokens, or both.”
Strategy -

Read the staking endpoint once, then turn the visible pool list into a yes/no answer.

+

Compare staking positions and FT balances before you interpret the wallet.

-

01GET /v1/account/.../staking returns the account’s visible direct staking positions.

-

02jq turns that response into has_direct_staking_now, pool_count, and pool_ids.

-

03If pools is empty, the answer from this surface is simply “no visible direct staking right now.”

+

01GET /v1/account/.../staking finds direct pool exposure.

+

02GET /v1/account/.../ft finds liquid staking tokens that sit beside or instead of direct pools.

+

03jq turns those two indexed reads into direct_only, liquid_only, or mixed.

@@ -135,213 +87,349 @@ Use this when the user story is simple: “tell me whether this account has visi **Official references** - [Validator staking](https://docs.near.org/concepts/basics/staking) +- [Using liquid staking](https://docs.near.org/primitives/liquid-staking) + +This example is intentionally observational. It classifies what FastNear can see from staking positions and FT balances today. It does not prove every possible synthetic or off-platform staking exposure. **What you're doing** - Read indexed direct staking positions from the account staking endpoint. -- Print a small yes/no summary plus the visible pool IDs. -- Stop there unless the next question becomes pool-specific unstake or withdraw timing. +- Read indexed FT balances from the account FT endpoint. +- Classify the account into `direct_only`, `liquid_only`, `mixed`, or `no_visible_staking_position`. +- Print the direct pool list and the liquid staking token list that informed the classification. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' ``` 1. Fetch the direct staking view. ```bash curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json >/dev/null - -jq '{ - account_id, - has_direct_staking_now: ((.pools // []) | length > 0), - pool_count: ((.pools // []) | length), - pool_ids: ((.pools // []) | map(.pool_id)) -}' /tmp/account-staking.json + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' ``` -At the time of writing, `mike.near` returned visible direct staking pools here. If `pool_ids` comes back empty for your target account, this endpoint is answering “no visible direct staking right now.” - -**Why this next step?** - -This keeps the question narrow and operational. If the answer is `true`, remember what that means on chain: the account usually delegated into a staking-pool contract such as `polkachu.poolv1.near` by sending a `FunctionCall` like `deposit_and_stake` with attached deposit. The pool contract later performs the actual `Stake` action on its own account. If the answer is `false`, do not infer liquid staking from this example alone; liquid staking positions usually show up first as FT holdings in specific LST contracts, so the right follow-up is the FT holdings example below. Also note the scope boundary here: this endpoint does not currently surface pending-unstake or withdraw-ready amounts, so it is not the place to answer epoch-delay timing questions. - -#### Optional follow-up: What did this contract call for delegation do? - -Use this when the staking endpoint already showed a pool like `polkachu.poolv1.near` and you want to see the transaction shape behind one real delegation. - -This pinned mainnet tx is useful because it shows the full pattern clearly: - -- transaction hash: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` -- top-level receiver: `polkachu.poolv1.near` -- top-level method: `deposit_and_stake` -- attached deposit: `34650000000000000000000000` (≈34.65 NEAR) - -The important chain shape is: - -- the delegator sends a `FunctionCall deposit_and_stake` into the pool contract -- the pool contract records the deposit and staking shares -- the pool later emits a self-receipt with a real `Stake` action +2. Fetch fungible token balances so you can detect liquid staking positions. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/staking-delegation-tx.json >/dev/null +3. Classify the account from those two indexed views. -jq '{ - top_level_call: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - attached_deposit: .transactions[0].transaction.actions[0].FunctionCall.deposit - }, - pool_side_effects: [ - .transactions[0].receipts[] - | select(.receipt.receiver_id == "polkachu.poolv1.near") - | { - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - actions: ( - .receipt.receipt.Action.actions - | map(if type == "string" then . else keys[0] end) - ), - first_logs: (.execution_outcome.outcome.logs[:3]) - } - ] -}' /tmp/staking-delegation-tx.json +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' ``` -The answer you want is simple: the delegator did not sign a raw `Stake` action directly. They called the staking-pool contract with `deposit_and_stake` and attached deposit, and the pool contract later executed the `Stake` action on its own account. +**Why this next step?** + +If the classification is `direct_only`, the next operational question is usually about unstake and withdraw timing. If it is `liquid_only`, the next question is usually about redeeming or swapping the liquid token. If it is `mixed`, you should treat those as two separate exit paths rather than assuming one flow covers both. -### What FT balances and NFT collections does this account show right now? +### Archive a BOS widget version as a provenance NFT -Use this when a wallet view, support tool, or agent already has an `account_id` and needs a fast indexed holdings summary: FT balances plus the NFT collections this account currently shows holdings from. +Use this when the user story is “this BOS widget is a real on-chain artifact. Mint an NFT that records exactly which version I archived.”
Strategy -

Read FT balances first, read NFT collections second, then combine them into one compact indexed inventory.

+

Read the exact widget first, then mint only after the provenance fields are deterministic.

-

01GET /v1/account/.../ft gives the wallet’s indexed FT balances.

-

02GET /v1/account/.../nft gives the NFT collections the wallet currently shows holdings from.

-

03jq turns those two indexed reads into one wallet-friendly inventory.

+

01GET /v1/account/.../nft checks whether the receiver already holds archive NFTs from this collection.

+

02RPC call_function get on social.near reads the exact widget source and its SocialDB write block.

+

03Hash the source, mint nft_mint on testnet, then verify the provenance fields through nft_tokens_for_owner.

-**What you're doing** +**Networks** -- Read the account’s fungible token balances. -- Read the account’s NFT collection-level holdings. -- Print one short indexed inventory that a wallet or support flow could reuse. +- mainnet for reading the widget from `social.near` +- testnet for safely minting the provenance NFT on `nft.examples.testnet` -This example does not answer native balance, staking, pools, exact NFT token IDs, or metadata. +**Official references** -The FT endpoint here is balance-first. It does not include display metadata such as token `symbol` or `decimals`; when you need to format a balance for UI, call the token contract’s `ft_metadata` read method over RPC. +- [Pre-deployed NFT contract](https://docs.near.org/tutorials/nfts/js/predeployed-contract) +- [NEP-171 NFT standard](https://docs.near.org/primitives/nft/standard) +- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) -The NFT endpoint here is collection-level. Treat it as “which NFT contracts does this account currently hold from?” rather than a full per-token crawl. +**What you're doing** -```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID +- Check whether the receiver already holds NFTs from the archive collection. +- Read one exact BOS widget from `social.near`, including its widget-level SocialDB block. +- Hash the widget source and turn that into provenance metadata. +- Mint a testnet NFT whose metadata records the author, widget path, SocialDB block, and source hash. +- Verify that the minted token still carries those provenance fields. + +Pinned source widget: -# Sample live value observed on April 19, 2026: -# ACCOUNT_ID=mike.near +- author account: `mob.near` +- widget path: `mob.near/widget/Profile` +- widget-level SocialDB block: `86494825` + +```bash +API_BASE_URL=https://test.api.fastnear.com +MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com +TESTNET_RPC_URL=https://rpc.testnet.fastnear.com +AUTHOR_ACCOUNT_ID=mob.near +WIDGET_NAME=Profile +DESTINATION_COLLECTION_ID=nft.examples.testnet +RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Read fungible token balances for the account. +1. Use FastNear API to see whether the receiver already holds NFTs from the archive collection. ```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null +curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ + | tee /tmp/provenance-account-nfts.json >/dev/null -jq '{ - account_id, - ft_contracts: ( - .tokens - | map(select((.balance // "0") != "0") | { +jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ + existing_archive_tokens: [ + .tokens[]? + | select(.contract_id == $destination_collection_id) + | { contract_id, - balance, + token_id, last_update_block_height - }) - | .[:10] - ) -}' /tmp/account-ft.json + } + ] +}' /tmp/provenance-account-nfts.json ``` -2. Read NFT collection holdings for the same account. +2. Read the exact widget body and widget-level SocialDB block from mainnet. ```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ - | tee /tmp/account-nft.json >/dev/null +WIDGET_ARGS_BASE64="$( + jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($author_account_id + "/widget/" + $widget_name)], + options: {with_block_height: true} + }' | base64 | tr -d '\n' +)" -jq '{ - account_id, - nft_collections: ( - (.tokens // []) - | map({ - contract_id, - last_update_block_height - }) - | unique_by(.contract_id) - | .[:10] +curl -s "$MAINNET_RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/bos-widget.json >/dev/null + +jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + widget_path: ($author_account_id + "/widget/" + $widget_name), + socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], + source_preview: ( + .[$author_account_id].widget[$widget_name][""] + | split("\n")[0:8] ) -}' /tmp/account-nft.json +}' /tmp/bos-widget.json ``` -3. Turn those two reads into one compact inventory. +3. Hash the widget source and build deterministic provenance metadata. ```bash -jq -n \ - --slurpfile ft /tmp/account-ft.json \ - --slurpfile nft /tmp/account-nft.json ' - ($ft[0].tokens // []) as $ft_tokens - | ($nft[0].tokens // []) as $nft_tokens - | { - account_id: ($ft[0].account_id // $nft[0].account_id), - ft_contract_count: ( - $ft_tokens - | map(select((.balance // "0") != "0")) - | length - ), - nft_collection_count: ( - $nft_tokens - | map(.contract_id) - | unique - | length - ), - ft_contracts: ( - $ft_tokens - | map(select((.balance // "0") != "0") | { - contract_id, - balance, - last_update_block_height - }) - | .[:10] - ), - nft_collections: ( - $nft_tokens - | map({ - contract_id, - last_update_block_height - }) - | unique_by(.contract_id) - | .[:10] - ) +jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][""] +' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx + +WIDGET_BLOCK_HEIGHT="$( + jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][":block"] + ' /tmp/bos-widget.json +)" + +SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" +SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" +TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" + +PROVENANCE_METADATA_JSON="$( + jq -nc \ + --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" \ + --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ + --arg block_height "$WIDGET_BLOCK_HEIGHT" \ + --arg source_sha256 "$SOURCE_SHA256" '{ + title: ("BOS widget archive: " + $widget_path), + description: ("Archived from social.near on mainnet at block " + $block_height), + copies: 1, + extra: ({ + author_account_id: $author_account_id, + widget_name: $widget_name, + widget_path: $widget_path, + source_contract_id: "social.near", + source_network: "mainnet", + socialdb_block_height: ($block_height | tonumber), + source_sha256: $source_sha256 + } | @json) }' +)" + +printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' +``` + +4. Mint the provenance NFT on testnet. + +```bash +near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ + --arg token_id "$TOKEN_ID" \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --argjson metadata "$PROVENANCE_METADATA_JSON" '{ + token_id: $token_id, + receiver_id: $receiver_id, + metadata: $metadata + }')" \ + --accountId "$SIGNER_ACCOUNT_ID" \ + --deposit 0.1 \ + --networkId testnet ``` -For `mike.near` on April 19, 2026, these reads returned dozens of FT contracts and NFT collections. That is enough for the common wallet question: “which FT balances and NFT collections does this account currently show?” +5. Verify that the minted NFT carries the provenance fields you expect. + +Poll a few times instead of assuming failure if the token does not appear immediately after the mint transaction returns. + +```bash +NFT_TOKEN_ARGS_BASE64="$( + jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ + | base64 | tr -d '\n' +)" + +for attempt in 1 2 3 4 5; do + curl -s "$TESTNET_RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$DESTINATION_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_token", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/bos-widget-provenance-token.json >/dev/null + + if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then + break + fi + + sleep 1 +done + +jq '{ + token_id, + owner_id, + title: .metadata.title, + provenance: (.metadata.extra | fromjson) +}' /tmp/bos-widget-provenance-token.json +``` **Why this next step?** -Use [`GET /v1/account/{account_id}/full`](/api/v1/account-full) when the next question also needs staking, pools, or native account state. Use contract-specific reads only when the question becomes “which exact NFT token IDs or metadata do I own?” +FastNear API gives you the quick receiver-side check. Mainnet RPC gives you the exact widget body and SocialDB block. Testnet minting turns that into a durable NFT record. If you later want to prove which historical transaction wrote the widget, hand off to the NEAR Social proof investigations on [Transactions API examples](/tx/examples). + +## Common jobs + +### What does this account actually hold right now? + +**Start here** + +- [V1 Full Account View](/api/v1/account-full) when you want the fastest readable answer to “what is in this account right now?” + +**Next page if needed** + +- [V1 Account FT](/api/v1/account-ft), [V1 Account NFT](/api/v1/account-nft), or [V1 Account Staking](/api/v1/account-staking) if the broad summary is useful but you now want to stay on just one asset family. +- [Transactions API account history](/tx/account) if the next question becomes “how did this account get here?” instead of “what does it hold?” + +**Stop when** + +- The summary already answers the holdings question in one response. + +**Switch when** + +- The user asks for exact account state, access-key semantics, or protocol-native fields. Move to [RPC Reference](/rpc). +- The user asks for activity or execution history rather than current holdings. Move to [Transactions API](/tx). + +### Resolve a public key to one or more accounts + +**Start here** + +- [V1 Public Key Lookup](/api/v1/public-key) when you want the primary account match. +- [V1 Public Key Lookup All](/api/v1/public-key-all) when you need the full set of associated accounts. + +**Next page if needed** + +- [V1 Full Account View](/api/v1/account-full) after resolution if the user immediately wants balances or holdings for the returned accounts. + +**Stop when** + +- You have identified the account or accounts that belong to the key. + +**Switch when** + +- The user starts asking about exact access-key permissions, nonces, or current key state. Move to [View Access Key](/rpc/account/view-access-key) or [View Access Key List](/rpc/account/view-access-key-list). +- The user wants recent activity for the resolved accounts rather than just identity resolution. Move to [Transactions API](/tx). + +### Does this account hold FTs, NFTs, or staking positions? + +**Start here** + +- [V1 Account FT](/api/v1/account-ft) when the question is just about fungible-token balances. +- [V1 Account NFT](/api/v1/account-nft) when the question is specifically about NFT holdings. +- [V1 Account Staking](/api/v1/account-staking) when the question is really about staking positions, not the whole wallet picture. + +**Next page if needed** + +- [V1 Full Account View](/api/v1/account-full) if the user later wants the whole account picture after starting from one asset family. +- [Transactions API account history](/tx/account) if the user stops asking “what does this account hold?” and starts asking how it got there. + +**Stop when** + +- The asset-specific endpoint already answers the holdings question without making you rebuild the whole account picture. + +**Switch when** + +- The indexed view is not enough and the user needs the exact on-chain answer. Move to [RPC Reference](/rpc). +- The question becomes historical or execution-oriented instead of “what does this account hold now?” Move to [Transactions API](/tx). ## Common mistakes diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index f4da6a6..1b1daec 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -2,110 +2,86 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: KV FastData Examples -description: Plain-language workflows for reading exact FastData rows, checking exact-key history, and optionally tracing one indexed setting back to its originating transaction. +description: Plain-language workflows for checking exact storage keys, following indexed write history, and confirming current state with RPC. displayed_sidebar: kvFastDataSidebar page_actions: - markdown --- -## Quick start - -If you already know the exact FastData keys you care about, read them directly. - -```bash -KV_BASE_URL=https://kv.test.fastnear.com -CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=kv.gork-agent.testnet - -curl -s "$KV_BASE_URL/v0/multi" \ - -H 'content-type: application/json' \ - --data '{ - "keys": [ - "kv.gork-agent.testnet/kv.gork-agent.testnet/key", - "kv.gork-agent.testnet/kv.gork-agent.testnet/value" - ] - }' \ - | jq '{ - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] - }' -``` - -This is the shortest useful FastData read on the page: one request, two exact rows. - ## Worked investigation -### Read one indexed setting and inspect its history +### Inspect one predecessor’s indexed writes, then narrow to the key that changed -Use this investigation when you already know the contract and predecessor, and the question is: “what is the current indexed setting value, and did it change before?” +Use this investigation when you know the predecessor first and the real question is “what did this predecessor write, which row is interesting, and what happened to that key afterward?”
Strategy -

Read the exact setting rows first, widen to predecessor metadata only when provenance matters, and use Transactions API only for the final proof.

+

Start from predecessor scope, narrow to one exact key only after it earns your attention, then use RPC only for the final exact check.

-

01multi or get-latest-key reads the exact indexed setting rows.

-

02get-history-key shows whether the indexed setting changed again later.

-

03Only if provenance matters, latest-by-predecessor with metadata plus POST /v0/transactions proves which write created those indexed rows.

+

01all-by-predecessor gives the latest indexed rows for one predecessor across the contracts it touched.

+

02get-history-key or history-by-predecessor explains how the interesting row changed over time.

+

03RPC view_state is the optional exact read when you need canonical current state, not just indexed history.

**Goal** -- Read one stable indexed setting from a minimal public testnet contract and confirm the exact-key history for one row. +- Explain what one predecessor wrote, which exact key became the focus, how that key changed, and whether you even need a canonical `view_state` check at the end. | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | -| Exact setting read | KV FastData [`multi`](/fastdata/kv/multi) | Read the known `key` and `value` rows in one request | This is the narrowest useful read when the exact indexed setting rows are already known | -| Exact row read | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Re-read one exact row by path | Useful when the question is about one row, not the whole setting pair | -| Exact keyed history | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) | Check the change history for the exact `value` row | Shows whether that indexed setting changed across multiple writes | -| Optional provenance bridge | KV FastData [`latest-by-predecessor`](/fastdata/kv/latest-by-predecessor) | Recover `tx_hash` and `receipt_id` for the indexed rows only when provenance matters | This is the optional bridge from indexed rows back to one write | -| Optional transaction hydration | Transactions API [`POST /v0/transactions`](/tx/transactions) | Hydrate the recovered `tx_hash` and decode the original args only when you need that proof | Final optional proof that one write created the indexed setting rows | +| Latest indexed rows by scope | KV FastData [`all-by-predecessor`](/fastdata/kv/all-by-predecessor) | Start with the predecessor’s current indexed writes across the contracts it touched | Answers the scope-first question before you pretend the exact key is already known | +| Indexed key history | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) or [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Pull the history for the exact key or keep the predecessor-wide write story broader for one more step | Shows whether the interesting row is stable, recent, or part of a larger write pattern | +| Exact state check | RPC [`view_state`](/rpc/contract/view-state) | Confirm the current on-chain state once the indexed pattern is clear | Separates indexed storage history from the exact state the chain would return now | **What a useful answer should include** -- the exact `current_account_id`, `predecessor_id`, and indexed setting rows investigated -- the latest indexed rows and the exact-key history for one of them -- the `tx_hash` or `receipt_id` that created those rows, only if provenance matters -- whether the question is still about indexed FastData rows or has widened into canonical contract state - -### Verified read-only testnet shell walkthrough +- which predecessor scope you started from +- which exact key became the real focus +- how that key changed in history +- whether a final `view_state` check is still necessary -Use this when you want a fully read-only example against the stable sample data in `kv.gork-agent.testnet`. +### Predecessor-scope shell walkthrough -This minimal contract behaves like a tiny settings store: one write emits two indexed rows, `key` and `value`. The sample setting currently reads as `test=hello`, which is simple enough to teach the FastData shape without pretending it is a richer application object. -This sample contract indexes its own writes, so `CURRENT_ACCOUNT_ID` and `PREDECESSOR_ID` are intentionally the same in this walkthrough. +Use this when one predecessor is already known and you want to move cleanly from “what did this predecessor write?” to “how did this exact key get here?” **What you're doing** -- Read the exact indexed setting rows together. -- Re-read the same rows individually so the exact-key route shape is clear. -- Pull the exact-key history for the setting `value` row. -- Stop there unless provenance matters. +- Read the latest indexed rows for one predecessor across the contracts it touched. +- Lift one interesting `current_account_id` plus exact `key` with `jq`. +- Reuse those exact values in the documented exact-key history route. +- Only after that decide whether you still need `view_state` for canonical current state. ```bash -KV_BASE_URL=https://kv.test.fastnear.com -TX_BASE_URL=https://tx.test.fastnear.com -CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=kv.gork-agent.testnet +KV_BASE_URL=https://kv.main.fastnear.com +PREDECESSOR_ID=james.near -curl -s "$KV_BASE_URL/v0/multi" \ +curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ -H 'content-type: application/json' \ - --data '{ - "keys": [ - "kv.gork-agent.testnet/kv.gork-agent.testnet/key", - "kv.gork-agent.testnet/kv.gork-agent.testnet/value" - ] - }' \ + --data '{"include_metadata":true,"limit":10}' \ + | tee /tmp/kv-predecessor.json >/dev/null + +jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ] +}' /tmp/kv-predecessor.json + +CURRENT_ACCOUNT_ID="$(jq -r '.entries[0].current_account_id' /tmp/kv-predecessor.json)" +EXACT_KEY="$(jq -r '.entries[0].key' /tmp/kv-predecessor.json)" +ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" + +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ | jq '{ entries: [ .entries[] @@ -118,113 +94,100 @@ curl -s "$KV_BASE_URL/v0/multi" \ } ] }' +``` -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ - | jq '{ - latest_key_row: ( - .entries[0] - | { - block_height, - key, - value - } - ) - }' +**Why this next step?** -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ - | jq '{ - latest_value_row: ( - .entries[0] - | { - block_height, - key, - value - } - ) - }' +The first lookup answers the scope-first question: what does this predecessor write right now? Narrowing from that feed to one exact key answers the more specific question: how did this row get here? If the write pattern is still broader than one key, stay on [History by Predecessor](/fastdata/kv/history-by-predecessor) a little longer before you switch to exact-key history or RPC. -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ - | jq '{ - page_token, - entries: [ - .entries[] - | { - block_height, - key, - value - } - ] - }' -``` +## Common jobs -That is the whole main read path: exact rows, exact latest reads, and exact-key history for the same indexed setting. +### Start from one predecessor’s writes -### Optional provenance extension +**Start here** -Only use this follow-up when the next question is “which write created these rows?” +- [All by Predecessor](/fastdata/kv/all-by-predecessor) when you know who wrote the rows but not yet which exact key matters most. -```bash +**Next page if needed** -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ - -H 'content-type: application/json' \ - --data '{"include_metadata": true, "limit": 10}' \ - | tee /tmp/kv-predecessor-latest.json >/dev/null +- [GET History by Exact Key](/fastdata/kv/get-history-key) if one row becomes the real focus. +- [History by Predecessor](/fastdata/kv/history-by-predecessor) if the broader predecessor write pattern is still the real question. -jq '{ - entries: [ - .entries[] - | { - block_height, - key, - value, - tx_hash, - receipt_id - } - ] -}' /tmp/kv-predecessor-latest.json +**Stop when** -INDEXED_TX_HASH="$( - jq -r ' - first(.entries[] | select(.key == "value") | .tx_hash) - ' /tmp/kv-predecessor-latest.json -)" +- You can explain what this predecessor wrote and whether one row deserves deeper history. -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$INDEXED_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - args: ( - .transactions[0].transaction.actions[0].FunctionCall.args - | @base64d - | fromjson - ), - receipt_ids: .transactions[0].execution_outcome.outcome.receipt_ids - }' -``` +**Switch when** -**Why this next step?** +- The user needs canonical current chain state rather than indexed storage history. Move to [View State](/rpc/contract/view-state). + +### Turn one exact key into a change history + +**Start here** + +- [GET History by Exact Key](/fastdata/kv/get-history-key) for path-based history lookup. +- [History by Key](/fastdata/kv/history-by-key) when the fully qualified key route is the better fit. + +**Next page if needed** + +- Revisit [GET Latest by Exact Key](/fastdata/kv/get-latest-key) if you want the current indexed value alongside the history. + +**Stop when** + +- You can explain how the key changed over time. + +**Switch when** + +- The user asks whether the latest indexed value matches the chain right now. + +### Trace writes from one predecessor + +**Start here** + +- [All by Predecessor](/fastdata/kv/all-by-predecessor) for latest rows across contracts touched by one predecessor. +- [History by Predecessor](/fastdata/kv/history-by-predecessor) when you need the write history over time. + +**Next page if needed** + +- Narrow to an exact key if one row becomes the real focus. + +**Stop when** + +- You can answer what this predecessor changed and where. + +**Switch when** + +- The user stops asking about indexed writes and starts asking about the current chain state. + +### Batch-check several known keys + +**Start here** + +- [Multi Lookup](/fastdata/kv/multi) when you already know a fixed set of fully qualified keys. + +**Next page if needed** + +- Move one interesting key to [GET History by Exact Key](/fastdata/kv/get-history-key) if the batch result raises a historical question. + +**Stop when** -This sample contract emits two indexed rows from one write: `key=test` and `value=hello`. Treat that as one indexed setting. The exact-key routes prove those rows directly. The predecessor-scoped lookup with metadata is the optional bridge back to provenance, because it gives you the `tx_hash` and `receipt_id` that created those rows. Hydrating that transaction proves those indexed rows came from one `__fastdata_kv` call with decoded args `{ "key": "test", "value": "hello" }`. +- The batch response already answers which of the keys matter. -That is also the important boundary for this surface: KV FastData answers questions about indexed FastData rows. If the question changes to canonical contract state, move to the contract's own read method or [View State](/rpc/contract/view-state) only when you independently know the storage layout you want. +**Switch when** +- You no longer have a fixed key list and need to inspect the contract or predecessor more broadly. ## Common mistakes -- Starting with broad predecessor scans when the exact FastData rows are already known. -- Treating [History by Key](/fastdata/kv/history-by-key) as if it were the same as [GET History by Exact Key](/fastdata/kv/get-history-key). The first is global by key string; the second stays inside one contract and predecessor. -- Using KV FastData when the real question is about balances, holdings, or account summaries. -- Confusing indexed FastData rows with canonical on-chain contract state. -- Assuming every FastData investigation needs a fresh write before any read is useful. +- Starting with broad account or predecessor scans when an exact key is already known. +- Using KV FastData when the user really wants balances or holdings. +- Confusing indexed history with exact current chain state. +- Reusing pagination tokens or changing filters mid-scan. ## Related guides - [KV FastData API](/fastdata/kv) -- [Transactions API](/tx) - [RPC Reference](/rpc) +- [FastNear API](/api) - [Choosing the Right Surface](/agents/choosing-surfaces) - [Agent Playbooks](/agents/playbooks) diff --git a/docs/fastdata/kv/index.md b/docs/fastdata/kv/index.md index 72465f6..9d99bdf 100644 --- a/docs/fastdata/kv/index.md +++ b/docs/fastdata/kv/index.md @@ -10,7 +10,7 @@ page_actions: # KV FastData API -KV FastData API is the indexed key-value family. Use it when you already know the contract, account, predecessor, or key scope you want to inspect and you want indexed rows without building your own FastData indexing layer. +KV FastData API is the indexed key-value family. Use it when you already know the contract, account, predecessor, or key scope you want to inspect and you want indexed rows without building your own storage indexing layer. ## Base URLs @@ -22,12 +22,41 @@ https://kv.main.fastnear.com https://kv.test.fastnear.com ``` +## Quick start + +If you already know one exact key, start with the latest indexed row and stop as soon as it answers the question. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' +``` + +This is the narrowest useful KV read: one exact key, one latest indexed row. If the next question becomes “how did this key change over time?”, move to [GET History by Exact Key](/fastdata/kv/get-history-key) or the fuller [KV FastData Examples](/fastdata/kv/examples). + ## Use this API when - you want latest indexed state for one key or a known key family - you want historical key changes by account, key, or predecessor - you want batch lookups for known exact keys -- you are debugging indexed FastData rows emitted by a contract or predecessor +- you are debugging contract storage in indexed form ## Do not start here when @@ -53,18 +82,18 @@ Use [FastNear API](/api) for higher-level account views, [NEAR Data API](/nearda ## Need a workflow? -Use [KV FastData Examples](/fastdata/kv/examples) for plain-language flows like exact-key lookups, exact-key history, predecessor-scoped inspection, and transaction bridging. +Use [KV FastData Examples](/fastdata/kv/examples) for plain-language flows like exact-key lookups, key-history investigation, predecessor-scoped inspection, and canonical RPC follow-up. ## Default workflow 1. Pick the narrowest scope that matches the user's question. 2. Stay within KV FastData first when the question is still about indexed key-value data. 3. Use the latest endpoints for current indexed views and the history endpoints only when the user needs change-over-time answers. -4. Stop once the indexed rows already answer the FastData question. +4. Stop once the indexed rows already answer the storage question. ## Auth and availability -- Public indexed FastData reads often work without a key. +- Public indexed storage reads often work without a key. - If you standardize on one FastNear API key across FastNear surfaces, reuse the same header or query-param shape here too. - Add `?network=testnet` to switch the page to the testnet backend where supported. - List responses omit `page_token` when there are no more results. @@ -75,7 +104,7 @@ Use [KV FastData Examples](/fastdata/kv/examples) for plain-language flows like - the user needs canonical contract-state semantics - the indexed storage view is the wrong abstraction for the question -When that happens, widen to [View State](/rpc/contract/view-state) in [RPC Reference](/rpc) or to the contract's own read method. Do not assume one FastData key maps directly to one raw `view_state` key. +When that happens, widen to [View State](/rpc/contract/view-state) in [RPC Reference](/rpc). ## Troubleshooting diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index d502c74..22948ab 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -2,102 +2,72 @@ sidebar_label: Examples slug: /neardata/examples title: NEAR Data Examples -description: Plain-language workflows for checking contract touches, comparing optimistic and final heads, and walking forward block by block. +description: Plain-language workflows for recent contract-touch monitoring, optimistic confirmation, and shard-local change inspection. displayed_sidebar: nearDataApiSidebar page_actions: - markdown --- -## Quick start +NEAR Data is strongest when the real question is about recent chain activity: did a contract show up in the newest block family, did an optimistic signal survive finality, and which shard actually carried the change? -Start with one recent finalized block and ask for the smallest possible touch summary first. - -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=intents.near - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | jq --arg target "$TARGET_ACCOUNT_ID" '{ - height: .block.header.height, - hash: .block.header.hash, - direct_tx_count: ([.shards[].chunk.transactions[]? - | select(.transaction.receiver_id == $target)] | length), - incoming_receipt_count: ([.shards[].chunk.receipts[]? - | select(.receiver_id == $target)] | length), - outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? - | select( - (.receipt.receiver_id // "") == $target - or (.execution_outcome.outcome.executor_id // "") == $target - )] | length), - state_change_count: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target)] | length), - state_change_types: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target) - | .type] | unique | sort) - } | . + { - touched: ( - (.direct_tx_count > 0) - or (.incoming_receipt_count > 0) - or (.outcome_hit_count > 0) - or (.state_change_count > 0) - ) - }' -``` - -This is the smallest useful NEAR Data summary for an app team: one finalized block, one yes-or-no answer, and a few counts before you widen. `intents.near` is pinned here so the first run is likely to return a real touched block before you swap in your own contract. - -NEAR blocks are sharded, so the filter walks `.shards[]` before it inspects transactions, receipts, outcomes, or state changes. `chunk.receipts` means work that landed in this block; `receipt_execution_outcomes` means work that executed in this block, even if it was scheduled earlier. - -## Worked investigation +## Worked investigations ### Did my contract get touched in the latest finalized block? -Use this investigation when you want a concrete yes/no answer before you widen into Transactions API or RPC. +Use this when your app, bot, or support tool needs one fast answer about a live contract. We will check `intents.near`, but the same summary works for any contract account.
Strategy -

Answer the contract-touch question first, then keep only one tx hash or receipt id for the next step.

+

Let NEAR Data answer the monitoring question first, then keep one tx hash or receipt ID for the next surface only if you need it.

-

01last-block-final gives you one stable block height without guessing.

-

02block is the main read: it already contains the transactions, incoming receipts, receipt execution outcomes, and state changes you need to answer “touched or not?”.

-

03Only if the answer is “yes” do you widen: keep one exact tx hash or receipt id from the same cached block, then hand that identifier to [Transactions API](/tx) or [RPC Reference](/rpc).

+

01last-block-final resolves the newest finalized height.

+

02block gives one recent hydrated block document with shard payloads already attached.

+

03Summarize direct txs, incoming receipts, execution outcomes, and state_changes for your contract. Treat state_changes as the strongest signal that the contract was actually changed.

-**Goal** - -- Decide whether one target contract was touched in the latest finalized block, and keep only the compact counts plus one exact identifier worth investigating next. - -**What a useful answer should include** - -- finalized height and hash -- touched or not touched -- counts for direct txs, incoming receipts, outcome hits, and state changes -- a compact `state_change_types` list -- one sample tx hash or receipt id when present - -### Final block to contract-touch answer shell walkthrough - -Use this when the target account is already known and you want one recent finalized answer, not a long polling loop. - -**What you're doing** - -- Get the latest finalized block redirect target. -- Fetch the full block document once. -- Build one small touch summary for one `TARGET_ACCOUNT_ID`. -- Return a yes/no answer plus the smallest useful counts, state-change types, and sample identifiers. +This can honestly return `touched: false` in a quiet block. That is still a useful answer: nothing in the newest finalized block currently needs deeper tracing. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=intents.near +TARGET_CONTRACT=intents.near + +contract_touch_summary() { + jq -r --arg contract "$1" ' + [ .shards[] | { + shard_id, + direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length), + sample_tx_hash: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash] | .[0]), + sample_receipt_id: ( + [ .chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id ] + + [ .receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract) | .execution_outcome.id ] + + [ .state_changes[]? | select(.change.account_id? == $contract) | (.cause.receipt_hash? // empty) ] + | .[0] + ) + } + | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) + ] as $rows + | { + height: .block.header.height, + hash: .block.header.hash, + contract: $contract, + touched: (($rows | length) > 0), + shards: ($rows | map(.shard_id)), + evidence: { + direct_txs: (($rows | map(.direct_txs) | add) // 0), + incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), + execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), + state_changes: (($rows | map(.state_changes) | add) // 0) + }, + sample_tx_hash: ([ $rows[] | .sample_tx_hash | select(.) ] | .[0]), + sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) + }' +} FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -105,197 +75,163 @@ FINAL_LOCATION="$( | tr -d '\r' )" -printf 'Final redirect target: %s\n' "$FINAL_LOCATION" +printf 'Latest finalized block: %s\n' "$FINAL_LOCATION" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-block.json >/dev/null - -jq --arg target "$TARGET_ACCOUNT_ID" ' - ( - [ - .shards[] - | .chunk.transactions[]? - | select(.transaction.receiver_id == $target) - | .transaction.hash - ] - ) as $txs - | ( - [ - .shards[] - | .chunk.receipts[]? - | select(.receiver_id == $target) - | .receipt_id - ] - ) as $receipts - | ( - [ - .shards[] - | .receipt_execution_outcomes[]? - | select( - (.receipt.receiver_id // "") == $target - or (.execution_outcome.outcome.executor_id // "") == $target - ) - | .tx_hash - | select(. != null) - ] - | unique - ) as $outcomes - | ( - [ - .shards[] - | .state_changes[]? - | select((.change.account_id // "") == $target) - | .type - ] - ) as $state_changes - | ( - $state_changes - | unique - | sort - ) as $state_change_types - | { - height: .block.header.height, - hash: .block.header.hash, - touched: ( - ($txs | length) > 0 - or ($receipts | length) > 0 - or ($outcomes | length) > 0 - or ($state_changes | length) > 0 - ), - direct_tx_count: ($txs | length), - incoming_receipt_count: ($receipts | length), - outcome_hit_count: ($outcomes | length), - state_change_count: ($state_changes | length), - state_change_types: $state_change_types, - sample_direct_tx: ($txs[0] // null), - sample_incoming_receipt: ($receipts[0] // null), - sample_outcome_tx_hash: ($outcomes[0] // null) - } -' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json + | tee /tmp/neardata-final-block.json \ + | contract_touch_summary "$TARGET_CONTRACT" ``` -If you need richer detail later, keep reusing `/tmp/neardata-block.json`. The point of this first pass is to answer “touched or not?” before you widen into longer arrays or deeper traces. - -Common `state_change_types` include `account_update`, `access_key_update`, `data_update`, and the corresponding `*_deletion` variants. That is often enough to tell whether you are looking at storage writes, key churn, or broader account-level changes before you leave NEAR Data. - -#### Optional follow-up: Which tx hash or receipt id should I inspect next? +Read the answer like this: -Keep the same cached block and summary, then lift one exact identifier for the next surface. +- `touched: false` means the newest finalized block did not mention or mutate the contract in any of the monitored ways. +- `sample_tx_hash` means you already have a good `/tx` anchor for the next question. +- `sample_receipt_id` without a tx hash usually means the contract showed up through receipt-driven execution, so NEAR Data already saved you the cheaper monitoring step. -```bash -FOLLOW_UP_KIND="$( - jq -r ' - if .sample_direct_tx != null then "tx_hash" - elif .sample_incoming_receipt != null then "receipt_id" - elif .sample_outcome_tx_hash != null then "tx_hash" - else "none" - end - ' /tmp/neardata-touch-summary.json -)" +### Did I see activity optimistically, and did it survive finality? -FOLLOW_UP_VALUE="$( - jq -r ' - .sample_direct_tx - // .sample_incoming_receipt - // .sample_outcome_tx_hash - // empty - ' /tmp/neardata-touch-summary.json -)" - -printf 'Next identifier kind: %s\n' "$FOLLOW_UP_KIND" -printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" -``` +Use this when you want an early signal for a live contract, but the stable answer still needs finalized confirmation. -If the identifier is a `tx_hash`, hand it to [Transactions API](/tx) or RPC `tx` status. If it is a `receipt_id`, hand it to [Transactions API: Receipt by ID](/tx/receipt). Only after that should you decide whether shard-level reopening is still necessary. - -**Why this next step?** - -This keeps the question as small as possible: first answer “was my contract touched?”, then widen only if one exact tx hash or receipt id justifies a deeper trace. NEAR Data is the discovery layer here, not just a block monitor. - -### How far ahead is optimistic right now? +
+
+ Strategy +

Use the same contract-touch vocabulary on both surfaces so the comparison stays honest.

+
+
+

01last-block-optimistic resolves the newest optimistic height.

+

02block-optimistic shows the early signal for the same contract.

+

03block at the same height either confirms the same observation or proves finality has not caught up yet.

+
+
-Use this when you need to choose between low-latency reads and settled reads before you start polling. +If finality has already caught up, the optimistic and finalized summaries may match immediately. That is still useful: it tells you the early signal already survived into stable history. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -OPTIMISTIC_LOCATION="$( +TARGET_CONTRACT=intents.near + +contract_touch_summary() { + jq -r --arg contract "$1" ' + [ .shards[] | { + shard_id, + direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) + } + | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) + ] as $rows + | { + height: .block.header.height, + hash: .block.header.hash, + contract: $contract, + touched: (($rows | length) > 0), + shards: ($rows | map(.shard_id)), + evidence: { + direct_txs: (($rows | map(.direct_txs) | add) // 0), + incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), + execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), + state_changes: (($rows | map(.state_changes) | add) // 0) + } + }' +} + +OPT_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' \ | tr -d '\r' )" -jq -n \ - --arg final_location "$FINAL_LOCATION" \ - --arg optimistic_location "$OPTIMISTIC_LOCATION" '{ - final_location: $final_location, - optimistic_location: $optimistic_location, - final_height: ($final_location | split("/") | last | tonumber), - optimistic_height: ($optimistic_location | split("/") | last | tonumber) - } | . + { - optimistic_minus_final: (.optimistic_height - .final_height) - }' +OPT_HEIGHT="${OPT_LOCATION##*/}" + +printf 'Latest optimistic block: %s\n' "$OPT_LOCATION" + +curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" \ + | tee /tmp/neardata-optimistic-block.json \ + | contract_touch_summary "$TARGET_CONTRACT" + +curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT" \ + | tee /tmp/neardata-final-same-height.json >/dev/null + +if jq -e 'type == "null"' /tmp/neardata-final-same-height.json >/dev/null; then + printf 'Finalized block %s is not available yet; finality has not caught up.\n' "$OPT_HEIGHT" +else + printf 'Finalized block %s is already available; compare the stable answer below.\n' "$OPT_HEIGHT" + contract_touch_summary "$TARGET_CONTRACT" < /tmp/neardata-final-same-height.json +fi ``` -Use `last_block/optimistic` when the app values speed more than settled finality, for example reactive status views or early alerting. Use `last_block/final` when the answer feeds accounting, reconciliation, or any workflow that should not rewind. +That is the practical split: -### How do I walk forward block by block? +- optimistic is the early signal that your monitoring loop can react to quickly; +- finalized is the stable answer you can show to users or use for durable automation. -Use this when the job is “start at height N, fetch, process, increment, repeat.” For a deterministic bootstrap floor, read [`first-block`](/neardata/first-block) once before you begin. +### Which shard actually changed my contract in this block? -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz +Use this when a recent block already showed contract activity and you now want the shard-local proof of where the change actually landed. -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" +
+
+ Strategy +

Use the full block to find the winning shard, then let block-shard prove the actual mutation.

+
+
+

01Scan the finalized block’s shard list for state_changes on your contract.

+

02Open only the shard that actually changed the contract.

+

03Keep the matching state changes and receipt execution outcomes as your shard-local proof.

+
+
-FINAL_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | awk -F/ '{print $4}')" -NEXT_HEIGHT=$((FINAL_HEIGHT + 1)) - -while true; do - HTTP_CODE="$( - curl -s -o /tmp/neardata-next-block.json -w '%{http_code}' \ - "$NEARDATA_BASE_URL/v0/block/$NEXT_HEIGHT" - )" - - if [ "$HTTP_CODE" = "200" ]; then - jq '{height: .block.header.height, hash: .block.header.hash}' \ - /tmp/neardata-next-block.json - NEXT_HEIGHT=$((NEXT_HEIGHT + 1)) - elif [ "$HTTP_CODE" = "404" ]; then - sleep 2 - else - printf 'Unexpected status: %s\n' "$HTTP_CODE" >&2 - break - fi -done -``` +At the time of writing, recent finalized block `194727131` gave a clean live `intents.near` example: the contract first appeared as an incoming receipt on shard `8`, then actually executed and changed state on shard `7`. -That is the canonical polling shape for finalized data: fetch by height, process one block, advance, and treat `404` as “not finalized yet, back off and try again.” If you need the same loop at optimistic speed, switch to `/v0/block_opt/` and accept optimistic semantics instead of final ones. +If you need a fresher block, reuse the same summary from the first example over a few nearby finalized heights and then plug the winning height into the same `block-shard` call. +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near +EXAMPLE_HEIGHT=194727131 + +curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT" \ + | tee /tmp/neardata-block-194727131.json \ + | jq --arg contract "$TARGET_CONTRACT" '[ + .shards[] | { + shard_id, + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) + } + | select(.incoming_receipts + .execution_outcomes + .state_changes > 0) + ]' + +curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT/shard/7" \ + | jq --arg contract "$TARGET_CONTRACT" '{ + shard_id, + chunk_hash: .chunk.header.chunk_hash, + matching_state_changes: [ + .state_changes[] + | select(.change.account_id? == $contract) + | {type, cause, account_id: .change.account_id} + ][0:2], + matching_execution_outcomes: [ + .receipt_execution_outcomes[] + | select(.execution_outcome.outcome.executor_id == $contract) + | { + receipt_id: .execution_outcome.id, + executor_id: .execution_outcome.outcome.executor_id, + status: .execution_outcome.outcome.status, + predecessor_id: .receipt.predecessor_id + } + ][0:2] + }' +``` -## Common mistakes +That is the practical rule: -- Treating NEAR Data like a push stream instead of a polling or point-read API. -- Starting with RPC before checking whether one finalized block already answers the contract-touch question. -- Looking only for direct transactions and forgetting that contracts are often touched through receipts or state changes. -- Using optimistic data for settled accounting or reconciliation. -- Assuming one hard-coded shard id should be checked before you inspect the block family itself. -- Widening to Transactions API or RPC before extracting one exact tx hash or receipt id from NEAR Data. +- use `block` when the first question is “which shard mattered?”; +- use `block-shard` when the real question becomes “show me the actual state-changing shard payload.” -## Related guides +## When to widen -- [NEAR Data API](/neardata) -- [Transactions API](/tx) -- [RPC Reference](/rpc) -- [Choosing the Right Surface](/agents/choosing-surfaces) -- [Agent Playbooks](/agents/playbooks) +- Use [Transactions API](/tx) once you have a `tx_hash` and want the human-readable transaction story. +- Use [RPC Reference](/rpc) when the next question is about exact protocol-native receipt or block semantics. +- Use [Block Headers](/neardata/block-headers) when you only need head progression or finality lag, not contract-touch inspection. diff --git a/docs/neardata/index.md b/docs/neardata/index.md index 968a2a0..3ae361a 100644 --- a/docs/neardata/index.md +++ b/docs/neardata/index.md @@ -1,6 +1,6 @@ --- title: NEAR Data API -description: Cached and archived block-family reads for optimistic, finalized, and redirect-style block access patterns. +description: Recent block and shard reads for contract-touch monitoring, optimistic confirmation, and shard-local inspection. sidebar_position: 1 displayed_sidebar: nearDataApiSidebar slug: /neardata @@ -10,7 +10,7 @@ page_actions: # NEAR Data API -NEAR Data API is the near-realtime and block-family surface. Use it when you want fresh block slices, redirect helpers, or recent finalized and optimistic block reads without presenting it as a streaming product. +NEAR Data API is the recent block and shard surface. Use it when you want fresh block slices, contract-touch monitoring, helper redirects, or optimistic-versus-finalized confirmation without turning the product into a streaming API. ## Base URLs @@ -25,8 +25,9 @@ https://testnet.neardata.xyz ## Best fit - Polling for recent finalized or optimistic blocks. -- Block-family helpers and redirect flows. -- Lightweight freshness checks and monitoring paths. +- Detecting whether a live contract showed up or changed state in a recent block. +- Comparing optimistic signals with finalized confirmation. +- Inspecting one recent shard after you already know which block matters. ## When not to use it @@ -41,13 +42,14 @@ https://testnet.neardata.xyz ## Common starting points -- [Optimistic block](/neardata/block-optimistic) for freshest block polling. -- [Final block by height](/neardata/block) and [Block headers](/neardata/block-headers) for finalized block-family queries. -- [Last final block redirect](/neardata/last-block-final) and [Last optimistic block redirect](/neardata/last-block-optimistic) when you want helper redirects. +- [Last final block redirect](/neardata/last-block-final) and [Last optimistic block redirect](/neardata/last-block-optimistic) when you want the newest recent block quickly. +- [Final block by height](/neardata/block) for one recent hydrated block document with shard payloads attached. +- [Block Shard](/neardata/block-shard) when a recent block already identified the shard you need to inspect more closely. +- [Block Headers](/neardata/block-headers) when head progression matters more than the wider block payload. ## Need a workflow? -Use [NEAR Data API Examples](/neardata/examples) for plain-language flows like optimistic polling, finalized confirmation, redirect helpers, and escalation into canonical RPC inspection. +Use [NEAR Data API Examples](/neardata/examples) for compact workflows like contract-touch detection, optimistic-versus-finalized comparison, and shard-local change inspection. ## Troubleshooting diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index 76a6e28..06868f3 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -2,259 +2,165 @@ sidebar_label: Examples slug: /transfers/examples title: Transfers Examples -description: Plain-language workflows for filtering transfers, reading humanized amounts and running balances, and pivoting into receipt or transaction context. +description: Plain-language workflows for finding transfers, paginating with resume_token, and pivoting into transaction history. displayed_sidebar: transfersApiSidebar page_actions: - markdown --- -## Quick start - -Start with one filtered incoming query and surface the fields that make Transfers API worth using. - -```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=intents.near -ASSET_ID=native:near -MIN_AMOUNT_YOCTO=1000000000000000000000000 - -curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - ignore_system: true, - min_amount: $min_amount, - desc: true, - limit: 5 - }')" \ - | jq '{ - resume_token, - transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - asset_id, - amount, - human_amount: ( - if .human_amount == null then null - else (.human_amount * 1000 | round / 1000) - end - ), - usd_amount: ( - if .usd_amount == null then null - else (.usd_amount * 100 | round / 100) - end - ), - block_timestamp, - method_name, - transfer_type, - start_of_block_balance, - end_of_block_balance, - other_account_id, - block_height - } - ] - }' -``` - -This is the shortest way to answer “which 1+ NEAR transfers hit this account, what were they worth, and which row should I inspect next?” `usd_amount` can be `null` when pricing is not available for that row. - ## Worked walkthrough -### Which incoming transfers of 1+ NEAR hit this account, and which row should I inspect? +### Filter and page a transfer feed for one account -Use this when the user story is “I need one narrow transfer search first, I want the row fields that already look like wallet or analytics data, and only after that will I decide whether one row needs deeper follow-up.” +Use this when the user story is “show me the meaningful transfer feed for this account, let me keep paging it cleanly, and only chase one row if that row needs the execution story.”
Strategy -

Use Transfers API for the filtered movement answer first, then widen only when one row still needs chain context.

+

Build the account feed first, then lift one receipt only if one row needs more story.

-

01POST /v0/transfers does the filtering work first: receiver-side rows, one asset, system transfers hidden, and a minimum amount threshold.

-

02Print the distinctive row fields first: human_amount, usd_amount, method_name, transfer_type, and the running balances.

-

03If you need more rows, reuse the opaque resume_token with the exact same filters.

-

04Only then choose one row and decide whether you want its receipt_id as an execution anchor or its transaction_id as a readable story anchor.

+

01POST /v0/transfers gives you the first page of one filtered account feed.

+

02jq lifts the returned rows plus resume_token so you can keep paging the same feed.

+

03POST /v0/receipt is only the optional follow-up when one row needs the execution story behind it.

+**Network** + +- mainnet only today + **What you're doing** -- Query a filtered incoming transfer window for one active mainnet account. -- Print the row fields that Transfers API already normalizes for you. -- Reuse the same filters with `resume_token` if you need another page. -- Lift either `receipt_id` or `transaction_id` only when one row still needs a deeper story. +- Fetch the first page of one filtered transfer feed for a single account. +- Use the feed parameters themselves as the main teaching surface: `account_id`, `direction`, `asset_id`, `min_amount`, `desc`, and `limit`. +- Inspect the returned rows plus `resume_token` before you decide whether any row deserves deeper execution history. +- Only if one row does deserve that deeper story, reuse its `receipt_id` in Transactions API. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=intents.near +ACCOUNT_ID=YOUR_ACCOUNT_ID ASSET_ID=native:near -MIN_AMOUNT_YOCTO=1000000000000000000000000 -TRANSFER_INDEX=0 +MIN_AMOUNT=1000000000000000000000000 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" '{ + --arg min_amount "$MIN_AMOUNT" '{ account_id: $account_id, direction: "receiver", asset_id: $asset_id, - ignore_system: true, min_amount: $min_amount, desc: true, - limit: 5 + limit: 10 }')" \ - | tee /tmp/transfers-window.json >/dev/null + | tee /tmp/transfers-feed.json >/dev/null jq '{ resume_token, transfers: [ - .transfers - | to_entries[] + .transfers[] | { - transfer_index: .key, - transaction_id: .value.transaction_id, - receipt_id: .value.receipt_id, - asset_id: .value.asset_id, - amount: .value.amount, - human_amount: ( - if .value.human_amount == null then null - else (.value.human_amount * 1000 | round / 1000) - end - ), - usd_amount: ( - if .value.usd_amount == null then null - else (.value.usd_amount * 100 | round / 100) - end - ), - block_timestamp: .value.block_timestamp, - method_name: .value.method_name, - transfer_type: .value.transfer_type, - start_of_block_balance: .value.start_of_block_balance, - end_of_block_balance: .value.end_of_block_balance, - other_account_id: .value.other_account_id, - block_height: .value.block_height + transaction_id, + receipt_id, + asset_id, + amount, + human_amount, + usd_amount, + other_account_id, + block_height } ] -}' /tmp/transfers-window.json - -RESUME_TOKEN="$( - jq -r '.resume_token // empty' /tmp/transfers-window.json -)" - -if [ -n "$RESUME_TOKEN" ]; then - curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" \ - --arg resume_token "$RESUME_TOKEN" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - ignore_system: true, - min_amount: $min_amount, - desc: true, - limit: 5, - resume_token: $resume_token - }')" \ - | jq '{ - next_page_resume_token: .resume_token, - next_transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - human_amount: ( - if .human_amount == null then null - else (.human_amount * 1000 | round / 1000) - end - ), - transfer_type, - other_account_id, - block_height - } - ] - }' -fi - -TRANSACTION_ID="$( - jq -r --argjson transfer_index "$TRANSFER_INDEX" \ - '.transfers[$transfer_index].transaction_id // empty' \ - /tmp/transfers-window.json -)" - -RECEIPT_ID="$( - jq -r --argjson transfer_index "$TRANSFER_INDEX" \ - '.transfers[$transfer_index].receipt_id // empty' \ - /tmp/transfers-window.json -)" - -printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" -printf 'Chosen transaction id: %s\n' "$TRANSACTION_ID" -printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" +}' /tmp/transfers-feed.json ``` -That answers the first question: which filtered rows match, what were they worth, and which transfer row should you inspect next? - -#### Optional follow-up: Receipt anchor or transaction story? - -Use `receipt_id` when you want the execution anchor for the row itself. Use `transaction_id` when you want the readable story of what the signer submitted. +Optional: if one feed row still needs its execution anchor, lift that row’s `receipt_id` and pivot once into Transactions API. ```bash -if [ -n "$RECEIPT_ID" ]; then - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' -fi - -if [ -n "$TRANSACTION_ID" ]; then - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TRANSACTION_ID" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ) - }' -fi +RECEIPT_ID="$(jq -r '.transfers[0].receipt_id' /tmp/transfers-feed.json)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' ``` **Why this next step?** -This is where Transfers API earns its keep. The first query already answers the movement question in wallet- or analytics-friendly terms: filtered rows, humanized amounts, transfer type, method clue, and running balances. If you still need another page, reuse the same `resume_token` with the same filters. If you need chain context, follow `receipt_id` for the execution anchor or `transaction_id` for the readable transaction story. +The transfer query answers the first question directly: what does this account’s filtered feed look like right now, and how do you keep paging it without losing your place? Only after the feed tells you which row matters should you switch to `receipt_id` and chase execution history in `/tx`. + +## Common jobs + +### Filter one account’s transfer feed + +**Start here** + +- [Query Transfers](/transfers/query) with the account plus the narrowest stable feed filters: direction, asset, amount, and order. + +**Next page if needed** + +- Keep refining the same feed with asset or amount filters if the first page still contains unrelated rows. + +**Stop when** + +- You can explain what the filtered feed contains and how to keep paging it. + +**Switch when** + +- One specific row now needs its execution story or receipt trail. Move to [Transactions API](/tx). + +### Keep paging through a transfer feed without losing your place + +**Start here** + +- [Query Transfers](/transfers/query) for the first page of recent events, using the tightest stable filters you can. + +**Next page if needed** + +- Reuse the exact returned `resume_token` to fetch the next page with the same filters. +- Keep the filters unchanged while you paginate, or you are no longer looking at the same feed. + +**Stop when** + +- You have enough pages to answer the requested feed, support review, or compliance check. + +**Switch when** + +- The user asks for transaction metadata beyond transfer events. +- The feed needs balances or holdings, not just movement. Move to [FastNear API](/api). + +### Escalate from transfer-only history to full transaction investigation + +**Start here** + +- [Query Transfers](/transfers/query) to identify the specific transfer events that matter. + +**Next page if needed** + +- [Transactions API account history](/tx/account) if the user wants the surrounding execution story for the same account. +- [Transactions by Hash](/tx/transactions) when you already know which transaction to inspect next. + +**Stop when** + +- You have identified the right transfer event and the right next API to open. + +**Switch when** +- The user explicitly needs receipt-level detail or exact RPC confirmation. Move to [Transactions API](/tx) first, then [RPC Reference](/rpc) if needed. ## Common mistakes - Using Transfers API when the user really wants balances, holdings, or account summaries. -- Treating transfer history as full execution history instead of a filtered movement view. +- Treating transfer history as full execution history. - Reusing a `resume_token` with different filters. -- Ignoring `method_name`, `transfer_type`, or running balances even though they are often the reason to use this API over raw transaction history. -- Starting here for testnet questions; this API is mainnet-only today. ## Related guides diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index 3e269c6..5f05f92 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -2,43 +2,16 @@ sidebar_label: Examples slug: /api/examples title: "Примеры API" -description: "Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и проверки прямого стейкинга." +description: "Пошаговые сценарии использования FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга." displayed_sidebar: fastnearApiSidebar page_actions: - markdown --- -## Быстрый старт - -Начните с одного поиска по публичному ключу и одного широкого чтения аккаунта. - -```bash -API_BASE_URL=https://api.fastnear.com -PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' - -ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" - -ACCOUNT_ID="$( - curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ - | jq -r '.account_ids[0]' -)" - -echo "$ACCOUNT_ID" - -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' -``` - -Это самый короткий путь к вопросам «какой это аккаунт?» и «что сейчас видно по этому кошельку?» - ## Готовые сценарии +Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. + ### Определить аккаунт по публичному ключу, а затем получить сводку по нему Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» @@ -46,7 +19,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \
Стратегия -

Сначала определите личность, а затем либо сразу проверьте один аккаунт, либо пройдитесь по всему списку, если ключ сопоставляется с несколькими аккаунтами.

+

Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку.

01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа.

@@ -58,8 +31,8 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Что вы делаете** - Ищете по публичному ключу один или несколько `account_id`. -- Сначала считаете, сколько `account_id` вернулось, прежде чем выбирать один. -- Сразу переиспользуете один аккаунт или проходите по всему списку, если ключ сопоставляется с несколькими аккаунтами. +- Извлекаете первый найденный `account_id` через `jq`. +- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. ```bash API_BASE_URL=https://api.fastnear.com @@ -75,56 +48,35 @@ ACCOUNT_ID="$( | jq -r '.account_ids[0]' )" -ACCOUNT_COUNT="$( - jq -r '.account_ids | length' /tmp/fastnear-public-key.json -)" +jq '{account_ids}' /tmp/fastnear-public-key.json -jq '{ - account_ids, - account_count: (.account_ids | length) -}' /tmp/fastnear-public-key.json - -if [ "$ACCOUNT_COUNT" -eq 1 ]; then - curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' -else - jq -r '.account_ids[]' /tmp/fastnear-public-key.json \ - | while read -r candidate_account_id; do - curl -s "$API_BASE_URL/v1/account/$candidate_account_id/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' - done -fi +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' ``` **Зачем нужен следующий шаг?** -Поиск по публичному ключу говорит, с каким аккаунтом или аккаунтами вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, именно здесь стоит либо пройтись по каждому найденному `account_id`, либо перейти к [V1 Public Key Lookup All](/api/v1/public-key-all) для более широкого исторического ответа. +Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. -### Есть ли у этого аккаунта прямой стейкинг прямо сейчас? +### Показывает ли этот кошелёк прямой стейкинг, ликвидные стейкинг-токены или и то и другое? -Используйте этот сценарий, когда история проста: «скажи, есть ли у аккаунта видимые прямые staking pool прямо сейчас, и покажи, какие именно это пулы». +Используйте этот сценарий, когда история звучит так: «покажи, видно ли по этому кошельку прямые позиции в staking pool, ликвидные стейкинг-токены или и то и другое».
Стратегия -

Один раз прочитайте staking-эндпоинт и превратите видимый список пулов в ответ “да / нет”.

+

Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк.

-

01GET /v1/account/.../staking возвращает видимые прямые staking-позиции аккаунта.

-

02jq превращает ответ в has_direct_staking_now, pool_count и pool_ids.

-

03Если массив pools пуст, ответ этой поверхности просто звучит как «прямой стейкинг сейчас не виден».

+

01GET /v1/account/.../staking находит прямую экспозицию через пулы.

+

02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них.

+

03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed.

@@ -135,213 +87,349 @@ fi **Официальные ссылки** - [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) +- [Liquid staking](https://docs.near.org/primitives/liquid-staking) + +Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. **Что вы делаете** - Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Печатаете короткий итог “да / нет” и список видимых `pool_id`. -- На этом останавливаетесь, если только следующий вопрос уже не касается `unstake` или `withdraw` в конкретном пуле. +- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. +- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. +- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' ``` 1. Получите представление по прямому стейкингу. ```bash curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json >/dev/null - -jq '{ - account_id, - has_direct_staking_now: ((.pools // []) | length > 0), - pool_count: ((.pools // []) | length), - pool_ids: ((.pools // []) | map(.pool_id)) -}' /tmp/account-staking.json + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' ``` -На момент написания для `mike.near` здесь возвращались видимые прямые staking-пулы. Если для вашего аккаунта `pool_ids` пуст, этот эндпоинт отвечает: «прямой стейкинг сейчас не виден». - -**Зачем нужен следующий шаг?** - -Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: liquid staking-позиции обычно сначала видны как FT-holdings в конкретных LST-контрактах, поэтому правильный follow-up здесь — FT-пример ниже. И ещё одна граница этой поверхности: этот эндпоинт сейчас не показывает pending-unstake или withdraw-ready amount, так что по нему не стоит отвечать на вопросы о задержках по эпохам. - -#### Необязательное продолжение: Что сделал этот контрактный вызов для делегирования? - -Используйте это продолжение, когда staking-эндпоинт уже показал пул вроде `polkachu.poolv1.near`, и теперь вы хотите увидеть форму одной реальной делегационной транзакции. - -Этот зафиксированный mainnet tx хорош тем, что очень ясно показывает весь паттерн: - -- хеш транзакции: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` -- top-level receiver: `polkachu.poolv1.near` -- top-level метод: `deposit_and_stake` -- attached deposit: `34650000000000000000000000` (≈34.65 NEAR) - -Важная форма chain-истории здесь такая: - -- делегатор отправляет `FunctionCall deposit_and_stake` в pool-контракт -- pool-контракт учитывает депозит и staking shares -- затем pool выпускает self-receipt с настоящим `Stake` action +2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/staking-delegation-tx.json >/dev/null +3. Классифицируйте аккаунт на основе этих двух индексированных представлений. -jq '{ - top_level_call: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - attached_deposit: .transactions[0].transaction.actions[0].FunctionCall.deposit - }, - pool_side_effects: [ - .transactions[0].receipts[] - | select(.receipt.receiver_id == "polkachu.poolv1.near") - | { - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - actions: ( - .receipt.receipt.Action.actions - | map(if type == "string" then . else keys[0] end) - ), - first_logs: (.execution_outcome.outcome.logs[:3]) - } - ] -}' /tmp/staking-delegation-tx.json +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' ``` -Простой вывод здесь такой: делегатор не подписывал сырой `Stake` action напрямую. Он вызвал staking-pool-контракт через `deposit_and_stake` и приложил депозит, а затем уже pool-контракт сам выполнил `Stake` action на своём аккаунте. +**Зачем нужен следующий шаг?** + +Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. -### Какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает? +### Заархивировать версию BOS-виджета как provenance NFT -Используйте этот сценарий, когда у wallet-экрана, support-инструмента или агента уже есть `account_id` и нужен быстрый индексированный обзор holdings: FT-балансы плюс NFT-коллекции, из которых этот аккаунт сейчас что-то показывает. +Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал».
Стратегия -

Сначала прочитайте FT-балансы, затем NFT-коллекции и только потом соберите их в один компактный индексированный инвентарь.

+

Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы.

-

01GET /v1/account/.../ft даёт индексированные FT-балансы кошелька.

-

02GET /v1/account/.../nft даёт NFT-коллекции, из которых этот кошелёк сейчас показывает holdings.

-

03jq превращает эти два индексированных чтения в один wallet-friendly инвентарь.

+

01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции.

+

02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB.

+

03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner.

-**Что вы делаете** +**Сети** -- Читаете FT-балансы аккаунта. -- Читаете NFT-holdings аккаунта на уровне коллекций. -- Печатаете один короткий индексированный инвентарь, который можно переиспользовать в wallet- или support-сценарии. +- mainnet для чтения виджета из `social.near` +- testnet для безопасного mint provenance NFT в `nft.examples.testnet` -Этот пример не отвечает на вопросы про нативный баланс, стейкинг, пулы, точные NFT token ID или метаданные. +**Официальные ссылки** -FT-эндпоинт здесь решает задачу балансов. Он не включает display-метаданные вроде `symbol` или `decimals`; когда нужно форматировать баланс для UI, вызовите у токен-контракта read-метод `ft_metadata` через RPC. +- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) +- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -NFT-эндпоинт здесь работает на уровне коллекций. Воспринимайте его как ответ на вопрос «из каких NFT-контрактов этот аккаунт сейчас что-то держит?», а не как полный per-token crawl. +**Что вы делаете** -```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID +- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. +- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. +- Хешируете исходник виджета и превращаете его в provenance-метаданные. +- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. +- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. + +Зафиксированный исходный виджет: -# Пример живого значения, проверенного 19 апреля 2026 года: -# ACCOUNT_ID=mike.near +- аккаунт автора: `mob.near` +- путь виджета: `mob.near/widget/Profile` +- SocialDB-блок уровня виджета: `86494825` + +```bash +API_BASE_URL=https://test.api.fastnear.com +MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com +TESTNET_RPC_URL=https://rpc.testnet.fastnear.com +AUTHOR_ACCOUNT_ID=mob.near +WIDGET_NAME=Profile +DESTINATION_COLLECTION_ID=nft.examples.testnet +RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Прочитайте FT-балансы аккаунта. +1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. ```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null +curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ + | tee /tmp/provenance-account-nfts.json >/dev/null -jq '{ - account_id, - ft_contracts: ( - .tokens - | map(select((.balance // "0") != "0") | { +jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ + existing_archive_tokens: [ + .tokens[]? + | select(.contract_id == $destination_collection_id) + | { contract_id, - balance, + token_id, last_update_block_height - }) - | .[:10] - ) -}' /tmp/account-ft.json + } + ] +}' /tmp/provenance-account-nfts.json ``` -2. Прочитайте NFT-коллекции для того же аккаунта. +2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. ```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ - | tee /tmp/account-nft.json >/dev/null +WIDGET_ARGS_BASE64="$( + jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($author_account_id + "/widget/" + $widget_name)], + options: {with_block_height: true} + }' | base64 | tr -d '\n' +)" -jq '{ - account_id, - nft_collections: ( - (.tokens // []) - | map({ - contract_id, - last_update_block_height - }) - | unique_by(.contract_id) - | .[:10] +curl -s "$MAINNET_RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/bos-widget.json >/dev/null + +jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + widget_path: ($author_account_id + "/widget/" + $widget_name), + socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], + source_preview: ( + .[$author_account_id].widget[$widget_name][""] + | split("\n")[0:8] ) -}' /tmp/account-nft.json +}' /tmp/bos-widget.json ``` -3. Соберите из этих двух чтений один компактный инвентарь. +3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. ```bash -jq -n \ - --slurpfile ft /tmp/account-ft.json \ - --slurpfile nft /tmp/account-nft.json ' - ($ft[0].tokens // []) as $ft_tokens - | ($nft[0].tokens // []) as $nft_tokens - | { - account_id: ($ft[0].account_id // $nft[0].account_id), - ft_contract_count: ( - $ft_tokens - | map(select((.balance // "0") != "0")) - | length - ), - nft_collection_count: ( - $nft_tokens - | map(.contract_id) - | unique - | length - ), - ft_contracts: ( - $ft_tokens - | map(select((.balance // "0") != "0") | { - contract_id, - balance, - last_update_block_height - }) - | .[:10] - ), - nft_collections: ( - $nft_tokens - | map({ - contract_id, - last_update_block_height - }) - | unique_by(.contract_id) - | .[:10] - ) +jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][""] +' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx + +WIDGET_BLOCK_HEIGHT="$( + jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][":block"] + ' /tmp/bos-widget.json +)" + +SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" +SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" +TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" + +PROVENANCE_METADATA_JSON="$( + jq -nc \ + --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" \ + --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ + --arg block_height "$WIDGET_BLOCK_HEIGHT" \ + --arg source_sha256 "$SOURCE_SHA256" '{ + title: ("BOS widget archive: " + $widget_path), + description: ("Archived from social.near on mainnet at block " + $block_height), + copies: 1, + extra: ({ + author_account_id: $author_account_id, + widget_name: $widget_name, + widget_path: $widget_path, + source_contract_id: "social.near", + source_network: "mainnet", + socialdb_block_height: ($block_height | tonumber), + source_sha256: $source_sha256 + } | @json) }' +)" + +printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' +``` + +4. Выпустите provenance NFT в testnet. + +```bash +near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ + --arg token_id "$TOKEN_ID" \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --argjson metadata "$PROVENANCE_METADATA_JSON" '{ + token_id: $token_id, + receiver_id: $receiver_id, + metadata: $metadata + }')" \ + --accountId "$SIGNER_ACCOUNT_ID" \ + --deposit 0.1 \ + --networkId testnet ``` -Для `mike.near` на 19 апреля 2026 года эти чтения вернули десятки FT-контрактов и NFT-коллекций. Этого достаточно для частого wallet-вопроса: «какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает?» +5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. + +Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. + +```bash +NFT_TOKEN_ARGS_BASE64="$( + jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ + | base64 | tr -d '\n' +)" + +for attempt in 1 2 3 4 5; do + curl -s "$TESTNET_RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$DESTINATION_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_token", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/bos-widget-provenance-token.json >/dev/null + + if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then + break + fi + + sleep 1 +done + +jq '{ + token_id, + owner_id, + title: .metadata.title, + provenance: (.metadata.extra | fromjson) +}' /tmp/bos-widget-provenance-token.json +``` **Зачем нужен следующий шаг?** -Переходите к [`GET /v1/account/{account_id}/full`](/api/v1/account-full), когда следующий вопрос уже требует ещё и стейкинг, пулы или нативное состояние аккаунта. Переходите к contract-specific чтениям только тогда, когда вопрос меняется на «какие именно идентификаторы NFT-токенов и метаданные мне принадлежат?» +FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](/tx/examples). + +## Частые задачи + +### Что этот аккаунт вообще держит прямо сейчас? + +**Начните здесь** + +- [V1 Full Account View](/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» + +**Следующая страница при необходимости** + +- [V1 Account FT](/api/v1/account-ft), [V1 Account NFT](/api/v1/account-nft) или [V1 Account Staking](/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. +- [Transactions API account history](/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» + +**Остановитесь, когда** + +- Сводка уже отвечает на вопрос по активам в одной выдаче. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](/rpc). +- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](/tx). + +### Определить аккаунты по публичному ключу + +**Начните здесь** + +- [V1 Public Key Lookup](/api/v1/public-key), когда нужен основной аккаунт для ключа. +- [V1 Public Key Lookup All](/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. + +**Следующая страница при необходимости** + +- [V1 Full Account View](/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. + +**Остановитесь, когда** + +- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](/rpc/account/view-access-key) или [View Access Key List](/rpc/account/view-access-key-list). +- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](/tx). + +### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? + +**Начните здесь** + +- [V1 Account FT](/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. +- [V1 Account NFT](/api/v1/account-nft), когда вопрос конкретно про владение NFT. +- [V1 Account Staking](/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. + +**Следующая страница при необходимости** + +- [V1 Full Account View](/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. +- [Transactions API account history](/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» + +**Остановитесь, когда** + +- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. + +**Переходите дальше, когда** + +- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](/rpc). +- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](/tx). ## Частые ошибки diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index 538c5a1..9431498 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -2,110 +2,86 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: "Примеры KV FastData" -description: "Пошаговые сценарии для чтения точных строк FastData, проверки истории точного ключа и необязательной привязки индексированной настройки к исходной транзакции." +description: "Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC." displayed_sidebar: kvFastDataSidebar page_actions: - markdown --- -## Быстрый старт - -Если точные FastData-ключи уже известны, читайте их напрямую. - -```bash -KV_BASE_URL=https://kv.test.fastnear.com -CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=kv.gork-agent.testnet - -curl -s "$KV_BASE_URL/v0/multi" \ - -H 'content-type: application/json' \ - --data '{ - "keys": [ - "kv.gork-agent.testnet/kv.gork-agent.testnet/key", - "kv.gork-agent.testnet/kv.gork-agent.testnet/value" - ] - }' \ - | jq '{ - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] - }' -``` - -Это самое короткое полезное чтение FastData на странице: один запрос и сразу две точные строки. - ## Готовое расследование -### Прочитать одну индексированную настройку и посмотреть её историю +### Посмотреть индексированные записи одного `predecessor_id`, а затем сузиться до ключа, который изменился -Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки и менялось ли оно раньше?» +Используйте это расследование, когда сначала известен предшественник, а настоящий вопрос звучит так: «что вообще записал этот `predecessor_id`, какая строка здесь действительно интересна и что потом происходило с этим ключом?»
Стратегия -

Сначала читайте точные строки настройки, расширяйтесь до метаданных предшественника только если нужна provenance-цепочка, и переходите к Transactions API только для финального доказательства.

+

Начните с области по `predecessor_id`, переходите к точному ключу только после того, как он заслужил внимание, а RPC оставляйте на самый конец.

-

01multi или get-latest-key читают точные индексированные строки настройки.

-

02get-history-key показывает, менялось ли это индексированное значение позже.

-

03Только если важна provenance-цепочка, latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки.

+

01all-by-predecessor даёт последние индексированные строки для одного `predecessor_id` по затронутым контрактам.

+

02get-history-key или history-by-predecessor объясняют, как менялась интересующая строка во времени.

+

03RPC view_state — это уже необязательное точное чтение, когда нужен именно канонический current state, а не индексированная история.

**Цель** -- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта и подтвердить историю точного ключа для одной строки. +- Объяснить, что записал этот `predecessor_id`, какой точный ключ стал настоящим фокусом, как он менялся и нужен ли вам вообще финальный `view_state`. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Чтение точной настройки | KV FastData [`multi`](/fastdata/kv/multi) | Читаем известные строки `key` и `value` одним запросом | Это самое узкое полезное чтение, когда точные индексированные строки настройки уже известны | -| Чтение точной строки | KV FastData [`get-latest-key`](/fastdata/kv/get-latest-key) | Повторно читаем одну точную строку по path-маршруту | Полезно, когда вопрос только про одну строку, а не про всю пару настройки | -| История точного ключа | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) | Проверяем историю точной строки `value` | Показывает, менялось ли именно это индексированное значение в нескольких записях | -| Необязательный мост к provenance | KV FastData [`latest-by-predecessor`](/fastdata/kv/latest-by-predecessor) | Восстанавливаем `tx_hash` и `receipt_id` для индексированных строк только если provenance действительно важна | Это необязательный мост от индексированных строк обратно к одной записи | -| Необязательная гидратация транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем исходные args только когда нужно это доказательство | Финальное необязательное доказательство того, что обе строки создал один вызов | +| Последние индексированные строки по области | KV FastData [`all-by-predecessor`](/fastdata/kv/all-by-predecessor) | Сначала получаем текущие индексированные записи одного `predecessor_id` по затронутым контрактам | Отвечает на вопрос по области раньше, чем вы начинаете притворяться, будто точный ключ уже известен | +| История индексированного ключа | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) или [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Забираем историю точного ключа или ещё на один шаг оставляем историю шире, на уровне предшественника | Показывает, стабильно ли интересующее значение, насколько оно недавнее и не входит ли в более широкий паттерн записей | +| Точная проверка состояния | RPC [`view_state`](/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | **Что должен включать полезный ответ** -- какие именно `current_account_id`, `predecessor_id` и индексированные строки настройки исследовались -- как выглядят последние индексированные строки и история точного ключа для одной из них -- какой `tx_hash` или `receipt_id` создал эти строки, только если важна provenance-цепочка -- остаётся ли вопрос про индексированные строки FastData или уже перешёл к каноническому состоянию контракта - -### Проверенный read-only testnet shell-сценарий +- с какой области по `predecessor_id` вы начали +- какой точный ключ стал настоящим фокусом +- как этот ключ менялся в истории +- нужен ли вообще финальный `view_state` -Используйте этот сценарий, когда нужен полностью read-only пример на стабильных sample-данных в `kv.gork-agent.testnet`. +### Shell-сценарий по области предшественника -Этот минимальный контракт ведёт себя как крошечное хранилище настроек: одна запись эмитирует две индексированные строки, `key` и `value`. Сейчас sample-настройка выглядит как `test=hello`, и этого достаточно, чтобы показать shape FastData без притворства, будто перед нами более богатый прикладной объект. -Этот sample-контракт индексирует собственные записи, поэтому в этом walkthrough `CURRENT_ACCOUNT_ID` и `PREDECESSOR_ID` намеренно совпадают. +Используйте этот сценарий, когда сначала известен один `predecessor_id` и нужно аккуратно перейти от вопроса «что он вообще записал?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» **Что вы делаете** -- Читаете точные индексированные строки настройки вместе. -- Повторно читаете те же строки по отдельности, чтобы был понятен shape exact-key маршрута. -- Забираете историю точного ключа для строки `value` этой настройки. -- Останавливаетесь на этом, если provenance дальше не нужна. +- Читаете последние индексированные строки для одного `predecessor_id` по затронутым контрактам. +- Поднимаете интересующие `current_account_id` и точный `key` через `jq`. +- Переиспользуете эти значения в документированном маршруте истории по точному ключу. +- Только после этого решаете, нужен ли вам `view_state` для канонического current state. ```bash -KV_BASE_URL=https://kv.test.fastnear.com -TX_BASE_URL=https://tx.test.fastnear.com -CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=kv.gork-agent.testnet +KV_BASE_URL=https://kv.main.fastnear.com +PREDECESSOR_ID=james.near -curl -s "$KV_BASE_URL/v0/multi" \ +curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ -H 'content-type: application/json' \ - --data '{ - "keys": [ - "kv.gork-agent.testnet/kv.gork-agent.testnet/key", - "kv.gork-agent.testnet/kv.gork-agent.testnet/value" - ] - }' \ + --data '{"include_metadata":true,"limit":10}' \ + | tee /tmp/kv-predecessor.json >/dev/null + +jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ] +}' /tmp/kv-predecessor.json + +CURRENT_ACCOUNT_ID="$(jq -r '.entries[0].current_account_id' /tmp/kv-predecessor.json)" +EXACT_KEY="$(jq -r '.entries[0].key' /tmp/kv-predecessor.json)" +ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" + +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ | jq '{ entries: [ .entries[] @@ -118,113 +94,100 @@ curl -s "$KV_BASE_URL/v0/multi" \ } ] }' +``` -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ - | jq '{ - latest_key_row: ( - .entries[0] - | { - block_height, - key, - value - } - ) - }' +**Зачем нужен следующий шаг?** -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ - | jq '{ - latest_value_row: ( - .entries[0] - | { - block_height, - key, - value - } - ) - }' +Первый запрос отвечает на вопрос по области: «что этот `predecessor_id` сейчас пишет?». Сужение из этой ленты до одного точного ключа отвечает на более точный вопрос: «как именно эта строка дошла до такого состояния?». Если картина всё ещё шире одного ключа, ещё немного побудьте на [History by Predecessor](/fastdata/kv/history-by-predecessor), а уже потом переходите к точной истории ключа или RPC. -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ - | jq '{ - page_token, - entries: [ - .entries[] - | { - block_height, - key, - value - } - ] - }' -``` +## Частые задачи -На этом основной read-path заканчивается: точные строки, их exact latest-чтение и история точного ключа для той же индексированной настройки. +### Начать с записей одного `predecessor_id` -### Необязательное расширение до provenance +**Начните здесь** -Переходите сюда только тогда, когда следующий вопрос уже звучит как «какая запись создала эти строки?» +- [Всё по `predecessor_id`](/fastdata/kv/all-by-predecessor), когда вы знаете, кто писал строки, но ещё не знаете, какой точный ключ важнее всего. -```bash +**Следующая страница при необходимости** -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ - -H 'content-type: application/json' \ - --data '{"include_metadata": true, "limit": 10}' \ - | tee /tmp/kv-predecessor-latest.json >/dev/null +- [История по точному ключу](/fastdata/kv/get-history-key), если одна строка становится настоящим фокусом. +- [История по `predecessor_id`](/fastdata/kv/history-by-predecessor), если более широкая картина записей всё ещё важнее точного ключа. -jq '{ - entries: [ - .entries[] - | { - block_height, - key, - value, - tx_hash, - receipt_id - } - ] -}' /tmp/kv-predecessor-latest.json +**Остановитесь, когда** -INDEXED_TX_HASH="$( - jq -r ' - first(.entries[] | select(.key == "value") | .tx_hash) - ' /tmp/kv-predecessor-latest.json -)" +- Уже можно объяснить, что писал этот `predecessor_id`, и заслуживает ли одна строка более глубокой истории. -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$INDEXED_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - args: ( - .transactions[0].transaction.actions[0].FunctionCall.args - | @base64d - | fromjson - ), - receipt_ids: .transactions[0].execution_outcome.outcome.receipt_ids - }' -``` +**Переходите дальше, когда** -**Зачем нужен следующий шаг?** +- Пользователю нужно каноническое текущее состояние в цепочке, а не индексированная история записей. Переходите к [View State](/rpc/contract/view-state). + +### Превратить один точный ключ в историю изменений + +**Начните здесь** + +- [История по точному ключу](/fastdata/kv/get-history-key) для поиска истории по пути. +- [History by Key](/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. + +**Следующая страница при необходимости** + +- Возвращайтесь к [Последнему по точному ключу](/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. + +**Остановитесь, когда** + +- Уже можно объяснить, как ключ менялся со временем. + +**Переходите дальше, когда** + +- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. + +### Проследить записи от одного `predecessor_id` + +**Начните здесь** + +- [Всё по `predecessor_id`](/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. +- [История по `predecessor_id`](/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. + +**Следующая страница при необходимости** + +- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. + +**Остановитесь, когда** + +- Уже можно ответить, что именно этот предшественник изменил и где. + +**Переходите дальше, когда** + +- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. + +### Пакетно проверить несколько известных ключей + +**Начните здесь** + +- [Пакетный поиск по ключам](/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. + +**Следующая страница при необходимости** + +- Переведите один интересный ключ в [Историю по точному ключу](/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. + +**Остановитесь, когда** -Этот sample-контракт эмитирует две индексированные строки из одной записи: `key=test` и `value=hello`. Рассматривайте их как одну индексированную настройку. Exact-key маршруты напрямую доказывают эти строки. Lookup по предшественнику с метаданными — это необязательный мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, которые создали эти строки. Гидратация транзакции доказывает, что эти индексированные строки произошли из одного вызова `__fastdata_kv` с декодированными args `{ "key": "test", "value": "hello" }`. +- Пакетный ответ уже показывает, какие ключи действительно важны. -Именно здесь проходит важная граница этой поверхности: KV FastData отвечает на вопросы про индексированные строки FastData. Если вопрос меняется на каноническое состояние контракта, переходите к собственному read-методу контракта или к [View State](/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную layout-структуру хранилища. +**Переходите дальше, когда** +- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. ## Частые ошибки -- Начинать с широких выборок по предшественнику, когда точные строки FastData уже известны. -- Считать [History by Key](/fastdata/kv/history-by-key) тем же самым, что и [GET History by Exact Key](/fastdata/kv/get-history-key). Первый маршрут глобальный по строке ключа, второй остаётся внутри одного контракта и predecessor. -- Использовать KV FastData, когда настоящий вопрос про балансы, holdings или account summaries. -- Путать индексированные строки FastData с каноническим on-chain-состоянием контракта. -- Предполагать, что для каждого FastData-расследования сначала обязательно нужна новая запись. +- Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. +- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. +- Путать индексированную историю с точным текущим состоянием в цепочке. +- Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. ## Полезные связанные страницы - [KV FastData API](/fastdata/kv) -- [Transactions API](/tx) - [RPC Reference](/rpc) +- [FastNear API](/api) - [Choosing the Right Surface](/agents/choosing-surfaces) - [Agent Playbooks](/agents/playbooks) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md index 90dfaf4..00eb8ae 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md @@ -10,7 +10,7 @@ page_actions: # KV FastData API -KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации FastData. +KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации хранилища. ## Базовые URL @@ -22,12 +22,41 @@ https://kv.main.fastnear.com https://kv.test.fastnear.com ``` +## Быстрый старт + +Если вы уже знаете один точный ключ, начните с последней индексированной строки и остановитесь, как только она ответит на вопрос. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' +``` + +Это самый узкий полезный KV-запрос: один точный ключ и одна последняя индексированная строка. Если следующий вопрос уже звучит как «как этот ключ менялся со временем?», переходите к [истории по точному ключу](/fastdata/kv/get-history-key) или к более подробным [примерам KV FastData](/fastdata/kv/examples). + ## Используйте этот API, когда - нужно последнее индексированное состояние по одному ключу или известному семейству ключей - нужна история изменений по аккаунту, ключу или `predecessor_id` - нужны пакетные поиски по уже известным точным ключам -- идёт отладка индексированных строк FastData, эмитированных контрактом или предшественником +- идёт отладка хранилища контракта в индексированном виде ## Не стартуйте здесь, когда @@ -53,18 +82,18 @@ https://kv.test.fastnear.com ## Нужен сценарий? -Используйте [примеры KV FastData](/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории точного ключа, анализа по `predecessor_id` и привязки к транзакции. +Используйте [примеры KV FastData](/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. ## Рабочий цикл по умолчанию 1. Выберите самую узкую область, подходящую под вопрос пользователя. 2. Оставайтесь внутри KV FastData, пока вопрос остаётся про индексированные данные «ключ–значение». 3. Используйте эндпоинты «последнего значения» для текущих индексированных представлений, а исторические эндпоинты — только когда пользователю нужны ответы с изменением во времени. -4. Остановитесь, как только индексированных строк уже достаточно для ответа на FastData-вопрос. +4. Остановитесь, как только индексированных строк уже достаточно для ответа на вопрос о хранилище. ## Аутентификация и доступность -- Публичные индексированные чтения FastData часто работают и без ключа. +- Публичные индексированные чтения хранилища часто работают и без ключа. - Если вы стандартизируете один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. - Добавьте `?network=testnet`, чтобы переключить страницу на testnet-бэкенд там, где это поддерживается. - В ответах со списками поле `page_token` отсутствует, когда новых результатов больше нет. @@ -75,7 +104,7 @@ https://kv.test.fastnear.com - пользователю нужна каноническая семантика состояния контракта - индексированное представление хранилища — неподходящая абстракция для вопроса -Тогда расширяйтесь на [Просмотр состояния контракта](/rpc/contract/view-state) в [Справочнике RPC](/rpc) или на собственный read-метод контракта. Не предполагайте, что один ключ FastData напрямую соответствует одному raw-ключу из `view_state`. +Тогда расширяйтесь на [Просмотр состояния контракта](/rpc/contract/view-state) в [Справочнике RPC](/rpc). ## Устранение неполадок diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index e6ff8ba..62f8083 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -2,102 +2,72 @@ sidebar_label: Examples slug: /neardata/examples title: "Примеры NEAR Data" -description: "Пошаговые сценарии для проверки contract touch, сравнения optimistic и final head, а также прохода по блокам вперёд." +description: "Пошаговые сценарии для мониторинга недавней активности контракта, подтверждения optimistic-наблюдений и проверки изменений на уровне shard." displayed_sidebar: nearDataApiSidebar page_actions: - markdown --- -## Быстрый старт +NEAR Data особенно хороша там, где вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. -Начните с одного недавнего финализированного блока и сначала запросите самую маленькую возможную touch-сводку. - -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=intents.near - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | jq --arg target "$TARGET_ACCOUNT_ID" '{ - height: .block.header.height, - hash: .block.header.hash, - direct_tx_count: ([.shards[].chunk.transactions[]? - | select(.transaction.receiver_id == $target)] | length), - incoming_receipt_count: ([.shards[].chunk.receipts[]? - | select(.receiver_id == $target)] | length), - outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? - | select( - (.receipt.receiver_id // "") == $target - or (.execution_outcome.outcome.executor_id // "") == $target - )] | length), - state_change_count: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target)] | length), - state_change_types: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target) - | .type] | unique | sort) - } | . + { - touched: ( - (.direct_tx_count > 0) - or (.incoming_receipt_count > 0) - or (.outcome_hit_count > 0) - or (.state_change_count > 0) - ) - }' -``` - -Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. Здесь закреплён `intents.near`, чтобы первый запуск с высокой вероятностью сразу вернул реальный touched-блок, а уже потом вы сможете подставить свой контракт. - -Блоки NEAR шардингованы, поэтому фильтр проходит по `.shards[]`, прежде чем смотреть транзакции, receipts, outcomes или изменения состояния. `chunk.receipts` означает работу, которая приземлилась в этом блоке; `receipt_execution_outcomes` означает работу, которая исполнилась в этом блоке, даже если была запланирована раньше. - -## Готовое расследование +## Готовые расследования ### Был ли мой контракт затронут в последнем финализированном блоке? -Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC. +Используйте это, когда приложению, боту или инструменту поддержки нужен один быстрый ответ о живом контракте. Мы будем проверять `intents.near`, но та же сводка работает для любого аккаунта контракта.
Стратегия -

Сначала ответьте на вопрос о contract touch, а затем оставьте только один tx hash или receipt id для следующего шага.

+

Сначала дайте NEAR Data ответить на задачу мониторинга, а уже потом сохраняйте tx hash или receipt ID для следующей поверхности, если это вообще понадобится.

-

01last-block-final даёт одну стабильную высоту блока без угадывания.

-

02block — это главный read: он уже содержит транзакции, входящие receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?»

-

03Только если ответ «да», расширяйтесь дальше: сохраните один точный tx hash или receipt id из того же сохранённого блока, а затем передайте этот идентификатор в [Transactions API](/tx) или [RPC Reference](/rpc).

+

01last-block-final находит самую новую финализированную высоту.

+

02block даёт один недавний гидратированный документ блока с уже присоединёнными данными по shard.

+

03Суммируйте прямые транзакции, входящие receipts, результаты выполнения и state_changes для нужного контракта. Считайте state_changes самым сильным сигналом того, что контракт действительно изменился.

-**Цель** - -- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только компактные счётчики плюс один точный идентификатор для следующего шага. - -**Что должен включать полезный ответ** - -- финализированную высоту и хеш -- ответ “затронут / не затронут” -- счётчики прямых транзакций, входящих receipts, outcome-hit и state changes -- компактный список `state_change_types` -- один sample tx hash или receipt id, когда он есть - -### Shell-сценарий от финализированного блока к ответу по contract touch - -Используйте этот сценарий, когда целевой аккаунт уже известен и нужен один свежий финализированный ответ, а не длинный polling-цикл. - -**Что вы делаете** - -- Получаете redirect target для последнего финализированного блока. -- Один раз загружаете полный документ блока. -- Собираете один компактный ответ по одному `TARGET_ACCOUNT_ID`. -- Получаете ответ “да / нет” плюс минимально полезные счётчики, типы изменений состояния и sample-идентификаторы. +Такой сценарий вполне честно может вернуть `touched: false`, если блок тихий. Это тоже полезный ответ: в самом новом финализированном блоке сейчас нет ничего, что требовало бы более глубокого разбора. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=intents.near +TARGET_CONTRACT=intents.near + +contract_touch_summary() { + jq -r --arg contract "$1" ' + [ .shards[] | { + shard_id, + direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length), + sample_tx_hash: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash] | .[0]), + sample_receipt_id: ( + [ .chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id ] + + [ .receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract) | .execution_outcome.id ] + + [ .state_changes[]? | select(.change.account_id? == $contract) | (.cause.receipt_hash? // empty) ] + | .[0] + ) + } + | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) + ] as $rows + | { + height: .block.header.height, + hash: .block.header.hash, + contract: $contract, + touched: (($rows | length) > 0), + shards: ($rows | map(.shard_id)), + evidence: { + direct_txs: (($rows | map(.direct_txs) | add) // 0), + incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), + execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), + state_changes: (($rows | map(.state_changes) | add) // 0) + }, + sample_tx_hash: ([ $rows[] | .sample_tx_hash | select(.) ] | .[0]), + sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) + }' +} FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -105,197 +75,163 @@ FINAL_LOCATION="$( | tr -d '\r' )" -printf 'Final redirect target: %s\n' "$FINAL_LOCATION" +printf 'Latest finalized block: %s\n' "$FINAL_LOCATION" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-block.json >/dev/null - -jq --arg target "$TARGET_ACCOUNT_ID" ' - ( - [ - .shards[] - | .chunk.transactions[]? - | select(.transaction.receiver_id == $target) - | .transaction.hash - ] - ) as $txs - | ( - [ - .shards[] - | .chunk.receipts[]? - | select(.receiver_id == $target) - | .receipt_id - ] - ) as $receipts - | ( - [ - .shards[] - | .receipt_execution_outcomes[]? - | select( - (.receipt.receiver_id // "") == $target - or (.execution_outcome.outcome.executor_id // "") == $target - ) - | .tx_hash - | select(. != null) - ] - | unique - ) as $outcomes - | ( - [ - .shards[] - | .state_changes[]? - | select((.change.account_id // "") == $target) - | .type - ] - ) as $state_changes - | ( - $state_changes - | unique - | sort - ) as $state_change_types - | { - height: .block.header.height, - hash: .block.header.hash, - touched: ( - ($txs | length) > 0 - or ($receipts | length) > 0 - or ($outcomes | length) > 0 - or ($state_changes | length) > 0 - ), - direct_tx_count: ($txs | length), - incoming_receipt_count: ($receipts | length), - outcome_hit_count: ($outcomes | length), - state_change_count: ($state_changes | length), - state_change_types: $state_change_types, - sample_direct_tx: ($txs[0] // null), - sample_incoming_receipt: ($receipts[0] // null), - sample_outcome_tx_hash: ($outcomes[0] // null) - } -' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json + | tee /tmp/neardata-final-block.json \ + | contract_touch_summary "$TARGET_CONTRACT" ``` -Если позже понадобятся более богатые списки, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. - -Типичные `state_change_types` — это `account_update`, `access_key_update`, `data_update` и соответствующие варианты `*_deletion`. Этого часто достаточно, чтобы ещё внутри NEAR Data понять, смотрите ли вы на запись в storage, churn ключей или более широкое изменение аккаунта. - -#### Необязательное продолжение: Какой tx hash или receipt id разбирать дальше? +Читать ответ стоит так: -Используйте ту же сохранённую сводку и поднимите один точный идентификатор для следующей поверхности. +- `touched: false` означает, что самый новый финализированный блок не упомянул и не изменил контракт ни одним из отслеживаемых способов. +- `sample_tx_hash` означает, что у вас уже есть хороший якорь для следующего шага на `/tx`. +- `sample_receipt_id` без tx hash обычно означает, что контракт появился в цепочке через receipts, и NEAR Data уже сэкономила вам более дешёвый этап мониторинга. -```bash -FOLLOW_UP_KIND="$( - jq -r ' - if .sample_direct_tx != null then "tx_hash" - elif .sample_incoming_receipt != null then "receipt_id" - elif .sample_outcome_tx_hash != null then "tx_hash" - else "none" - end - ' /tmp/neardata-touch-summary.json -)" +### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? -FOLLOW_UP_VALUE="$( - jq -r ' - .sample_direct_tx - // .sample_incoming_receipt - // .sample_outcome_tx_hash - // empty - ' /tmp/neardata-touch-summary.json -)" - -printf 'Next identifier kind: %s\n' "$FOLLOW_UP_KIND" -printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" -``` +Используйте это, когда нужен ранний сигнал по живому контракту, но стабильный ответ всё равно должен пройти через финализированное подтверждение. -Если идентификатор — это `tx_hash`, передайте его в [Transactions API](/tx) или RPC `tx` status. Если это `receipt_id`, передайте его в [Transactions API: Receipt by ID](/tx/receipt). И только после этого решайте, нужен ли вам вообще shard-level follow-up. - -**Зачем нужен следующий шаг?** - -Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один точный tx hash или receipt id уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. - -### Насколько optimistic head опережает final прямо сейчас? +
+
+ Стратегия +

Используйте один и тот же словарь contract-touch на обеих поверхностях, чтобы сравнение было честным.

+
+
+

01last-block-optimistic находит самую новую optimistic-высоту.

+

02block-optimistic показывает ранний сигнал для того же контракта.

+

03block на той же высоте либо подтверждает то же наблюдение, либо показывает, что finality ещё не догнала.

+
+
-Используйте это, когда нужно выбрать между low-latency read и settled read ещё до запуска polling. +Если finality уже догнала, optimistic- и finalized-сводки могут совпасть сразу. Это тоже полезно: ранний сигнал уже попал в стабильную историю. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -OPTIMISTIC_LOCATION="$( +TARGET_CONTRACT=intents.near + +contract_touch_summary() { + jq -r --arg contract "$1" ' + [ .shards[] | { + shard_id, + direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) + } + | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) + ] as $rows + | { + height: .block.header.height, + hash: .block.header.hash, + contract: $contract, + touched: (($rows | length) > 0), + shards: ($rows | map(.shard_id)), + evidence: { + direct_txs: (($rows | map(.direct_txs) | add) // 0), + incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), + execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), + state_changes: (($rows | map(.state_changes) | add) // 0) + } + }' +} + +OPT_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' \ | tr -d '\r' )" -jq -n \ - --arg final_location "$FINAL_LOCATION" \ - --arg optimistic_location "$OPTIMISTIC_LOCATION" '{ - final_location: $final_location, - optimistic_location: $optimistic_location, - final_height: ($final_location | split("/") | last | tonumber), - optimistic_height: ($optimistic_location | split("/") | last | tonumber) - } | . + { - optimistic_minus_final: (.optimistic_height - .final_height) - }' +OPT_HEIGHT="${OPT_LOCATION##*/}" + +printf 'Latest optimistic block: %s\n' "$OPT_LOCATION" + +curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" \ + | tee /tmp/neardata-optimistic-block.json \ + | contract_touch_summary "$TARGET_CONTRACT" + +curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT" \ + | tee /tmp/neardata-final-same-height.json >/dev/null + +if jq -e 'type == "null"' /tmp/neardata-final-same-height.json >/dev/null; then + printf 'Finalized block %s is not available yet; finality has not caught up.\n' "$OPT_HEIGHT" +else + printf 'Finalized block %s is already available; compare the stable answer below.\n' "$OPT_HEIGHT" + contract_touch_summary "$TARGET_CONTRACT" < /tmp/neardata-final-same-height.json +fi ``` -Используйте `last_block/optimistic`, когда приложению важнее скорость, чем settled finality, например для реактивных status-view или ранних алертов. Используйте `last_block/final`, когда ответ пойдёт в accounting, reconciliation или любой workflow, который не должен откатываться назад. +Практический вывод такой: -### Как идти вперёд блок за блоком? +- optimistic — это ранний сигнал, на который цикл мониторинга может быстро отреагировать; +- finalized — это стабильный ответ, который уже можно показывать пользователям или использовать в устойчивой автоматизации. -Используйте этот шаблон, когда задача звучит как «начать с высоты N, получить блок, обработать его, увеличить высоту и повторить». Для детерминированной стартовой точки один раз прочитайте [`first-block`](/neardata/first-block), а затем идите вперёд. +### Какой shard действительно изменил мой контракт в этом блоке? -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz +Используйте это, когда недавний блок уже показал активность контракта, и теперь нужно доказательство на уровне shard того, где именно изменение реально приземлилось. -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" +
+
+ Стратегия +

Сначала используйте весь блок, чтобы найти нужный shard, а затем дайте block-shard доказать само изменение.

+
+
+

01Просканируйте список shard внутри финализированного блока и найдите state_changes по вашему контракту.

+

02Откройте только тот shard, который действительно изменил контракт.

+

03Сохраните совпадающие state_changes и нужные результаты исполнения как доказательство на уровне shard.

+
+
-FINAL_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | awk -F/ '{print $4}')" -NEXT_HEIGHT=$((FINAL_HEIGHT + 1)) - -while true; do - HTTP_CODE="$( - curl -s -o /tmp/neardata-next-block.json -w '%{http_code}' \ - "$NEARDATA_BASE_URL/v0/block/$NEXT_HEIGHT" - )" - - if [ "$HTTP_CODE" = "200" ]; then - jq '{height: .block.header.height, hash: .block.header.hash}' \ - /tmp/neardata-next-block.json - NEXT_HEIGHT=$((NEXT_HEIGHT + 1)) - elif [ "$HTTP_CODE" = "404" ]; then - sleep 2 - else - printf 'Unexpected status: %s\n' "$HTTP_CODE" >&2 - break - fi -done -``` +На момент написания недавний финализированный блок `194727131` дал чистый живой пример для `intents.near`: контракт сначала появился как входящий receipt на shard `8`, а затем действительно выполнился и изменил состояние на shard `7`. -Это каноническая форма polling для финализированных данных: получить блок по высоте, обработать один блок, перейти к следующему и трактовать `404` как «ещё не финализирован, подождите и повторите». Если нужен тот же цикл на optimistic-скорости, переключитесь на `/v0/block_opt/` и примите optimistic semantics вместо final. +Если для вашей задачи нужен более свежий блок, переиспользуйте ту же сводку из первого примера на нескольких соседних финализированных высотах, а затем подставьте найденную высоту в тот же вызов `block-shard`. +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near +EXAMPLE_HEIGHT=194727131 + +curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT" \ + | tee /tmp/neardata-block-194727131.json \ + | jq --arg contract "$TARGET_CONTRACT" '[ + .shards[] | { + shard_id, + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) + } + | select(.incoming_receipts + .execution_outcomes + .state_changes > 0) + ]' + +curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT/shard/7" \ + | jq --arg contract "$TARGET_CONTRACT" '{ + shard_id, + chunk_hash: .chunk.header.chunk_hash, + matching_state_changes: [ + .state_changes[] + | select(.change.account_id? == $contract) + | {type, cause, account_id: .change.account_id} + ][0:2], + matching_execution_outcomes: [ + .receipt_execution_outcomes[] + | select(.execution_outcome.outcome.executor_id == $contract) + | { + receipt_id: .execution_outcome.id, + executor_id: .execution_outcome.outcome.executor_id, + status: .execution_outcome.outcome.status, + predecessor_id: .receipt.predecessor_id + } + ][0:2] + }' +``` -## Частые ошибки +Практическое правило здесь простое: -- Воспринимать NEAR Data как push-стрим, а не как polling- или point-read API. -- Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. -- Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. -- Использовать optimistic-данные для settled accounting или reconciliation. -- Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. -- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data один точный tx hash или receipt id. +- используйте `block`, когда первый вопрос звучит как «какой shard вообще важен?»; +- используйте `block-shard`, когда настоящий вопрос уже стал таким: «покажи мне сам payload shard, который изменил состояние». -## Полезные связанные страницы +## Когда пора расширять поверхность -- [NEAR Data API](/neardata) -- [Transactions API](/tx) -- [RPC Reference](/rpc) -- [Choosing the Right Surface](/agents/choosing-surfaces) -- [Agent Playbooks](/agents/playbooks) +- Используйте [Transactions API](/tx), когда у вас уже есть `tx_hash` и нужен человекочитаемый рассказ о транзакции. +- Используйте [Справочник RPC](/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. +- Используйте [Block Headers](/neardata/block-headers), когда нужна только динамика head/finality, а не проверка contract-touch. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md index 7e709e4..2de79b0 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md @@ -1,6 +1,6 @@ --- title: "NEAR Data API" -description: "Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением." +description: "Недавние чтения по блокам и шардам для мониторинга активности контракта, подтверждения оптимистичных наблюдений и проверки изменений на уровне шарда." sidebar_position: 1 displayed_sidebar: nearDataApiSidebar slug: /neardata @@ -10,7 +10,7 @@ page_actions: # NEAR Data API -NEAR Data API — это поверхность для чтения данных почти в реальном времени, а также по семействам блоков. Используйте её, когда нужны свежие срезы блоков, вспомогательные маршруты с перенаправлением или недавние финализированные и оптимистичные чтения, но без позиционирования продукта как потокового сервиса. +NEAR Data API — это поверхность для недавних блоков и шардов. Используйте её, когда нужны свежие срезы блоков, мониторинг активности контракта, вспомогательные маршруты с перенаправлением или сравнение оптимистичных и финализированных чтений без превращения продукта в потоковый API. ## Базовые URL @@ -25,8 +25,9 @@ https://testnet.neardata.xyz ## Лучше всего подходит для - опроса недавних финализированных и оптимистичных блоков; -- вспомогательных маршрутов по блокам и сценариев с перенаправлением; -- лёгких проверок свежести данных и мониторинга. +- обнаружения того, появился ли живой контракт в недавнем блоке и изменил ли он состояние; +- сравнения оптимистичного сигнала с финализированным подтверждением; +- проверки одного недавнего шарда, когда уже известно, какой блок важен. ## Когда его не стоит использовать @@ -41,13 +42,14 @@ https://testnet.neardata.xyz ## С чего обычно начинают -- [Оптимистичный блок](/neardata/block-optimistic) — для самого свежего опроса блоков. -- [Финализированный блок по высоте](/neardata/block) и [Заголовки блока](/neardata/block-headers) — для запросов по финализированным блокам. -- [Перенаправление на последний финализированный блок](/neardata/last-block-final) и [Перенаправление на последний оптимистичный блок](/neardata/last-block-optimistic) — когда нужны вспомогательные маршруты с перенаправлением. +- [Перенаправление на последний финализированный блок](/neardata/last-block-final) и [Перенаправление на последний оптимистичный блок](/neardata/last-block-optimistic) — когда нужно быстро узнать самый новый недавний блок. +- [Финализированный блок по высоте](/neardata/block) — когда нужен один недавний гидратированный блок с уже приложенными данными по шардам. +- [Шард блока](/neardata/block-shard) — когда недавний блок уже показал нужный шард и его надо разобрать глубже. +- [Заголовки блока](/neardata/block-headers) — когда важнее движение головы цепочки и финальности, а не широкий блоковый документ. ## Нужен сценарий? -Используйте [примеры NEAR Data API](/neardata/examples) для простых пошаговых сценариев: опроса оптимистичных блоков, подтверждения финализированных блоков, работы с перенаправлениями и перехода к каноническому RPC. +Используйте [примеры NEAR Data API](/neardata/examples) для компактных сценариев: обнаружения активности контракта, сравнения оптимистичных и финализированных наблюдений, а также проверки изменений на уровне шарда. ## Устранение неполадок diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md index c1f339d..bb8cf1e 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -2,259 +2,165 @@ sidebar_label: Examples slug: /transfers/examples title: "Примеры Transfers API" -description: "Пошаговые сценарии для фильтрации переводов, чтения humanized amount и running balances, а также перехода к receipt- или transaction-контексту." +description: "Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций." displayed_sidebar: transfersApiSidebar page_actions: - markdown --- -## Быстрый старт - -Начните с одного отфильтрованного входящего запроса и сразу выведите поля, ради которых вообще стоит использовать Transfers API. - -```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=intents.near -ASSET_ID=native:near -MIN_AMOUNT_YOCTO=1000000000000000000000000 - -curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - ignore_system: true, - min_amount: $min_amount, - desc: true, - limit: 5 - }')" \ - | jq '{ - resume_token, - transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - asset_id, - amount, - human_amount: ( - if .human_amount == null then null - else (.human_amount * 1000 | round / 1000) - end - ), - usd_amount: ( - if .usd_amount == null then null - else (.usd_amount * 100 | round / 100) - end - ), - block_timestamp, - method_name, - transfer_type, - start_of_block_balance, - end_of_block_balance, - other_account_id, - block_height - } - ] - }' -``` - -Это самый короткий путь к вопросу «какие переводы от 1+ NEAR пришли на этот аккаунт, чего они стоили и какую строку стоит разбирать дальше?» `usd_amount` может быть `null`, если для этой строки нет ценового покрытия. - ## Готовый сценарий -### Какие входящие переводы от 1+ NEAR попали на этот аккаунт и какую строку стоит разобрать? +### Отфильтровать и листать ленту переводов одного аккаунта -Используйте этот сценарий, когда история звучит так: «сначала мне нужен один узкий поиск переводов, я хочу поля, которые уже похожи на wallet- или analytics-данные, и только после этого решу, нужна ли одной строке более глубокая расшифровка». +Используйте этот сценарий, когда история звучит так: «покажи мне осмысленную ленту переводов этого аккаунта, дай мне спокойно листать её дальше, и только потом, если нужно, помоги догнать одну строку до истории исполнения».
Стратегия -

Сначала используйте Transfers API ради отфильтрованного ответа о движении средств, а расширяйтесь только если одной строке всё ещё нужен chain-контекст.

+

Сначала соберите саму ленту аккаунта, а `receipt` поднимайте только тогда, когда одна строка действительно требует истории исполнения.

-

01POST /v0/transfers сначала делает всю фильтрацию: входящая сторона, один asset, скрытие system-переводов и порог по минимальной сумме.

-

02Сначала выведите отличительные поля строки: human_amount, usd_amount, method_name, transfer_type и running balances.

-

03Если нужны ещё строки, переиспользуйте непрозрачный resume_token с точно теми же фильтрами.

-

04И только потом выбирайте одну строку и решайте, нужен ли вам её receipt_id как execution-anchor или её transaction_id как якорь для читаемой истории.

+

01POST /v0/transfers даёт первую страницу отфильтрованной ленты одного аккаунта.

+

02jq поднимает сами строки плюс resume_token, чтобы вы могли продолжать листать ту же ленту.

+

03POST /v0/receipt — это уже необязательный следующий шаг, если одной строке нужна её история исполнения.

+**Сеть** + +- только mainnet + **Что вы делаете** -- Запрашиваете отфильтрованное окно входящих переводов для одного активного mainnet-аккаунта. -- Сначала печатаете поля строки, которые Transfers API уже нормализует за вас. -- Переиспользуете тот же `resume_token`, если вам нужна следующая страница. -- Поднимаете либо `receipt_id`, либо `transaction_id` только тогда, когда одной строке всё ещё нужна более глубокая история. +- Забираете первую страницу одной отфильтрованной ленты переводов для выбранного аккаунта. +- Используете сами параметры ленты как главный учебный материал: `account_id`, `direction`, `asset_id`, `min_amount`, `desc` и `limit`. +- Сначала смотрите на строки ответа и `resume_token`, а не прыгаете сразу в историю исполнения. +- Только если какая-то строка действительно требует дополнительной истории, переиспользуете её `receipt_id` в Transactions API. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=intents.near +ACCOUNT_ID=YOUR_ACCOUNT_ID ASSET_ID=native:near -MIN_AMOUNT_YOCTO=1000000000000000000000000 -TRANSFER_INDEX=0 +MIN_AMOUNT=1000000000000000000000000 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" '{ + --arg min_amount "$MIN_AMOUNT" '{ account_id: $account_id, direction: "receiver", asset_id: $asset_id, - ignore_system: true, min_amount: $min_amount, desc: true, - limit: 5 + limit: 10 }')" \ - | tee /tmp/transfers-window.json >/dev/null + | tee /tmp/transfers-feed.json >/dev/null jq '{ resume_token, transfers: [ - .transfers - | to_entries[] + .transfers[] | { - transfer_index: .key, - transaction_id: .value.transaction_id, - receipt_id: .value.receipt_id, - asset_id: .value.asset_id, - amount: .value.amount, - human_amount: ( - if .value.human_amount == null then null - else (.value.human_amount * 1000 | round / 1000) - end - ), - usd_amount: ( - if .value.usd_amount == null then null - else (.value.usd_amount * 100 | round / 100) - end - ), - block_timestamp: .value.block_timestamp, - method_name: .value.method_name, - transfer_type: .value.transfer_type, - start_of_block_balance: .value.start_of_block_balance, - end_of_block_balance: .value.end_of_block_balance, - other_account_id: .value.other_account_id, - block_height: .value.block_height + transaction_id, + receipt_id, + asset_id, + amount, + human_amount, + usd_amount, + other_account_id, + block_height } ] -}' /tmp/transfers-window.json - -RESUME_TOKEN="$( - jq -r '.resume_token // empty' /tmp/transfers-window.json -)" - -if [ -n "$RESUME_TOKEN" ]; then - curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" \ - --arg resume_token "$RESUME_TOKEN" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - ignore_system: true, - min_amount: $min_amount, - desc: true, - limit: 5, - resume_token: $resume_token - }')" \ - | jq '{ - next_page_resume_token: .resume_token, - next_transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - human_amount: ( - if .human_amount == null then null - else (.human_amount * 1000 | round / 1000) - end - ), - transfer_type, - other_account_id, - block_height - } - ] - }' -fi - -TRANSACTION_ID="$( - jq -r --argjson transfer_index "$TRANSFER_INDEX" \ - '.transfers[$transfer_index].transaction_id // empty' \ - /tmp/transfers-window.json -)" - -RECEIPT_ID="$( - jq -r --argjson transfer_index "$TRANSFER_INDEX" \ - '.transfers[$transfer_index].receipt_id // empty' \ - /tmp/transfers-window.json -)" - -printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" -printf 'Chosen transaction id: %s\n' "$TRANSACTION_ID" -printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" +}' /tmp/transfers-feed.json ``` -Этим вы отвечаете на первый вопрос: какие отфильтрованные строки совпали, чего они стоили и какую строку перевода стоит разбирать дальше? - -#### Необязательное продолжение: execution-anchor или transaction-story? - -Используйте `receipt_id`, когда нужен execution-anchor именно для этой строки. Используйте `transaction_id`, когда нужна читаемая история того, что именно подписал signer. +Необязательный следующий шаг: если одной строке всё-таки нужна её точка исполнения, поднимите её `receipt_id` и один раз перейдите в Transactions API. ```bash -if [ -n "$RECEIPT_ID" ]; then - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' -fi - -if [ -n "$TRANSACTION_ID" ]; then - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TRANSACTION_ID" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ) - }' -fi +RECEIPT_ID="$(jq -r '.transfers[0].receipt_id' /tmp/transfers-feed.json)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' ``` **Зачем нужен следующий шаг?** -Именно здесь Transfers API показывает свою ценность. Первый запрос уже отвечает на вопрос о движении средств в терминах, удобных для wallet- и analytics-сценариев: отфильтрованные строки, humanized amount, тип перевода, method-clue и running balances. Если всё ещё нужна следующая страница, переиспользуйте тот же `resume_token` с теми же фильтрами. Если нужен chain-контекст, следуйте по `receipt_id` ради execution-anchor или по `transaction_id` ради читаемой истории транзакции. +Запрос переводов напрямую отвечает на первый вопрос: как сейчас выглядит отфильтрованная лента этого аккаунта и как её продолжать без потери места? Только после того как сама лента подскажет, какая строка действительно важна, имеет смысл переходить по `receipt_id` и забирать историю исполнения из `/tx`. + +## Частые задачи + +### Отфильтровать ленту переводов одного аккаунта + +**Начните здесь** + +- [Запрос переводов](/transfers/query) с аккаунтом и самым узким стабильным набором фильтров для ленты: направление, актив, сумма и порядок. + +**Следующая страница при необходимости** + +- Уточните те же фильтры по активу или сумме, если в первой странице всё ещё слишком много лишних строк. + +**Остановитесь, когда** + +- Уже можно объяснить, как выглядит эта отфильтрованная лента и как листать её дальше. + +**Переходите дальше, когда** + +- Одна конкретная строка уже требует истории исполнения или следа по receipt. Переходите к [Transactions API](/tx). + +### Листать ленту переводов дальше и не потерять своё место + +**Начните здесь** + +- [Запрос переводов](/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. + +**Следующая страница при необходимости** + +- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. +- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. + +**Остановитесь, когда** + +- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. + +**Переходите дальше, когда** + +- Пользователь просит метаданные транзакции сверх самих переводов. +- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](/api). + +### Перейти от истории переводов к полному расследованию транзакции + +**Начните здесь** + +- [Запрос переводов](/transfers/query), чтобы выделить конкретные интересующие переводы. + +**Следующая страница при необходимости** + +- [История аккаунта в Transactions API](/tx/account), если нужна окружающая история исполнения для того же аккаунта. +- [Transactions by Hash](/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. + +**Остановитесь, когда** + +- Уже определено правильное событие перевода и понятно, какой API открывать следующим. + +**Переходите дальше, когда** +- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](/tx), затем к [RPC Reference](/rpc), если потребуется. ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. -- Считать историю переводов полной историей исполнения вместо отфильтрованного movement-view. +- Считать историю переводов полной историей исполнения. - Переиспользовать `resume_token` с другими фильтрами. -- Игнорировать `method_name`, `transfer_type` или running balances, хотя именно из-за них этот API часто удобнее сырой transaction-history. -- Начинать здесь с вопросов про testnet, хотя этот API сегодня работает только в mainnet. ## Полезные связанные страницы diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index 35df6e2..e0e0e00 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -1,42 +1,15 @@ **Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) -## Быстрый старт - -Начните с одного поиска по публичному ключу и одного широкого чтения аккаунта. - -```bash -API_BASE_URL=https://api.fastnear.com -PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' - -ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" - -ACCOUNT_ID="$( - curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ - | jq -r '.account_ids[0]' -)" - -echo "$ACCOUNT_ID" - -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' -``` - -Это самый короткий путь к вопросам «какой это аккаунт?» и «что сейчас видно по этому кошельку?» - ## Готовые сценарии +Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. + ### Определить аккаунт по публичному ключу, а затем получить сводку по нему Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» Стратегия - Сначала определите личность, а затем либо сразу проверьте один аккаунт, либо пройдитесь по всему списку, если ключ сопоставляется с несколькими аккаунтами. + Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. @@ -45,8 +18,8 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Что вы делаете** - Ищете по публичному ключу один или несколько `account_id`. -- Сначала считаете, сколько `account_id` вернулось, прежде чем выбирать один. -- Сразу переиспользуете один аккаунт или проходите по всему списку, если ключ сопоставляется с несколькими аккаунтами. +- Извлекаете первый найденный `account_id` через `jq`. +- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. ```bash API_BASE_URL=https://api.fastnear.com @@ -62,53 +35,32 @@ ACCOUNT_ID="$( | jq -r '.account_ids[0]' )" -ACCOUNT_COUNT="$( - jq -r '.account_ids | length' /tmp/fastnear-public-key.json -)" +jq '{account_ids}' /tmp/fastnear-public-key.json -jq '{ - account_ids, - account_count: (.account_ids | length) -}' /tmp/fastnear-public-key.json - -if [ "$ACCOUNT_COUNT" -eq 1 ]; then - curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' -else - jq -r '.account_ids[]' /tmp/fastnear-public-key.json \ - | while read -r candidate_account_id; do - curl -s "$API_BASE_URL/v1/account/$candidate_account_id/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' - done -fi +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' ``` **Зачем нужен следующий шаг?** -Поиск по публичному ключу говорит, с каким аккаунтом или аккаунтами вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, именно здесь стоит либо пройтись по каждому найденному `account_id`, либо перейти к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) для более широкого исторического ответа. +Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. -### Есть ли у этого аккаунта прямой стейкинг прямо сейчас? +### Показывает ли этот кошелёк прямой стейкинг, ликвидные стейкинг-токены или и то и другое? -Используйте этот сценарий, когда история проста: «скажи, есть ли у аккаунта видимые прямые staking pool прямо сейчас, и покажи, какие именно это пулы». +Используйте этот сценарий, когда история звучит так: «покажи, видно ли по этому кошельку прямые позиции в staking pool, ликвидные стейкинг-токены или и то и другое». Стратегия - Один раз прочитайте staking-эндпоинт и превратите видимый список пулов в ответ “да / нет”. + Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. - 01GET /v1/account/.../staking возвращает видимые прямые staking-позиции аккаунта. - 02jq превращает ответ в has_direct_staking_now, pool_count и pool_ids. - 03Если массив pools пуст, ответ этой поверхности просто звучит как «прямой стейкинг сейчас не виден». + 01GET /v1/account/.../staking находит прямую экспозицию через пулы. + 02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них. + 03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed. **Сеть** @@ -117,208 +69,344 @@ fi **Официальные ссылки** - [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) +- [Liquid staking](https://docs.near.org/primitives/liquid-staking) + +Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. **Что вы делаете** - Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Печатаете короткий итог “да / нет” и список видимых `pool_id`. -- На этом останавливаетесь, если только следующий вопрос уже не касается `unstake` или `withdraw` в конкретном пуле. +- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. +- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. +- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' ``` 1. Получите представление по прямому стейкингу. ```bash curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json >/dev/null - -jq '{ - account_id, - has_direct_staking_now: ((.pools // []) | length > 0), - pool_count: ((.pools // []) | length), - pool_ids: ((.pools // []) | map(.pool_id)) -}' /tmp/account-staking.json + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' ``` -На момент написания для `mike.near` здесь возвращались видимые прямые staking-пулы. Если для вашего аккаунта `pool_ids` пуст, этот эндпоинт отвечает: «прямой стейкинг сейчас не виден». +2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. -**Зачем нужен следующий шаг?** - -Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: liquid staking-позиции обычно сначала видны как FT-holdings в конкретных LST-контрактах, поэтому правильный follow-up здесь — FT-пример ниже. И ещё одна граница этой поверхности: этот эндпоинт сейчас не показывает pending-unstake или withdraw-ready amount, так что по нему не стоит отвечать на вопросы о задержках по эпохам. - -#### Необязательное продолжение: Что сделал этот контрактный вызов для делегирования? - -Используйте это продолжение, когда staking-эндпоинт уже показал пул вроде `polkachu.poolv1.near`, и теперь вы хотите увидеть форму одной реальной делегационной транзакции. +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` -Этот зафиксированный mainnet tx хорош тем, что очень ясно показывает весь паттерн: +3. Классифицируйте аккаунт на основе этих двух индексированных представлений. -- хеш транзакции: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` -- top-level receiver: `polkachu.poolv1.near` -- top-level метод: `deposit_and_stake` -- attached deposit: `34650000000000000000000000` (≈34.65 NEAR) +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' +``` -Важная форма chain-истории здесь такая: +**Зачем нужен следующий шаг?** -- делегатор отправляет `FunctionCall deposit_and_stake` в pool-контракт -- pool-контракт учитывает депозит и staking shares -- затем pool выпускает self-receipt с настоящим `Stake` action +Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz +### Заархивировать версию BOS-виджета как provenance NFT -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/staking-delegation-tx.json >/dev/null +Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». -jq '{ - top_level_call: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - attached_deposit: .transactions[0].transaction.actions[0].FunctionCall.deposit - }, - pool_side_effects: [ - .transactions[0].receipts[] - | select(.receipt.receiver_id == "polkachu.poolv1.near") - | { - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - actions: ( - .receipt.receipt.Action.actions - | map(if type == "string" then . else keys[0] end) - ), - first_logs: (.execution_outcome.outcome.logs[:3]) - } - ] -}' /tmp/staking-delegation-tx.json -``` + Стратегия + Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. -Простой вывод здесь такой: делегатор не подписывал сырой `Stake` action напрямую. Он вызвал staking-pool-контракт через `deposit_and_stake` и приложил депозит, а затем уже pool-контракт сам выполнил `Stake` action на своём аккаунте. + 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. + 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. + 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner. -### Какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает? +**Сети** -Используйте этот сценарий, когда у wallet-экрана, support-инструмента или агента уже есть `account_id` и нужен быстрый индексированный обзор holdings: FT-балансы плюс NFT-коллекции, из которых этот аккаунт сейчас что-то показывает. +- mainnet для чтения виджета из `social.near` +- testnet для безопасного mint provenance NFT в `nft.examples.testnet` - Стратегия - Сначала прочитайте FT-балансы, затем NFT-коллекции и только потом соберите их в один компактный индексированный инвентарь. +**Официальные ссылки** - 01GET /v1/account/.../ft даёт индексированные FT-балансы кошелька. - 02GET /v1/account/.../nft даёт NFT-коллекции, из которых этот кошелёк сейчас показывает holdings. - 03jq превращает эти два индексированных чтения в один wallet-friendly инвентарь. +- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) +- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) **Что вы делаете** -- Читаете FT-балансы аккаунта. -- Читаете NFT-holdings аккаунта на уровне коллекций. -- Печатаете один короткий индексированный инвентарь, который можно переиспользовать в wallet- или support-сценарии. - -Этот пример не отвечает на вопросы про нативный баланс, стейкинг, пулы, точные NFT token ID или метаданные. +- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. +- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. +- Хешируете исходник виджета и превращаете его в provenance-метаданные. +- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. +- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. -FT-эндпоинт здесь решает задачу балансов. Он не включает display-метаданные вроде `symbol` или `decimals`; когда нужно форматировать баланс для UI, вызовите у токен-контракта read-метод `ft_metadata` через RPC. +Зафиксированный исходный виджет: -NFT-эндпоинт здесь работает на уровне коллекций. Воспринимайте его как ответ на вопрос «из каких NFT-контрактов этот аккаунт сейчас что-то держит?», а не как полный per-token crawl. +- аккаунт автора: `mob.near` +- путь виджета: `mob.near/widget/Profile` +- SocialDB-блок уровня виджета: `86494825` ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID - -# Пример живого значения, проверенного 19 апреля 2026 года: -# ACCOUNT_ID=mike.near +API_BASE_URL=https://test.api.fastnear.com +MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com +TESTNET_RPC_URL=https://rpc.testnet.fastnear.com +AUTHOR_ACCOUNT_ID=mob.near +WIDGET_NAME=Profile +DESTINATION_COLLECTION_ID=nft.examples.testnet +RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Прочитайте FT-балансы аккаунта. +1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. ```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null +curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ + | tee /tmp/provenance-account-nfts.json >/dev/null -jq '{ - account_id, - ft_contracts: ( - .tokens - | map(select((.balance // "0") != "0") | { +jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ + existing_archive_tokens: [ + .tokens[]? + | select(.contract_id == $destination_collection_id) + | { contract_id, - balance, + token_id, last_update_block_height - }) - | .[:10] - ) -}' /tmp/account-ft.json + } + ] +}' /tmp/provenance-account-nfts.json ``` -2. Прочитайте NFT-коллекции для того же аккаунта. +2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. ```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ - | tee /tmp/account-nft.json >/dev/null +WIDGET_ARGS_BASE64="$( + jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($author_account_id + "/widget/" + $widget_name)], + options: {with_block_height: true} + }' | base64 | tr -d '\n' +)" -jq '{ - account_id, - nft_collections: ( - (.tokens // []) - | map({ - contract_id, - last_update_block_height - }) - | unique_by(.contract_id) - | .[:10] +curl -s "$MAINNET_RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/bos-widget.json >/dev/null + +jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + widget_path: ($author_account_id + "/widget/" + $widget_name), + socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], + source_preview: ( + .[$author_account_id].widget[$widget_name][""] + | split("\n")[0:8] ) -}' /tmp/account-nft.json +}' /tmp/bos-widget.json ``` -3. Соберите из этих двух чтений один компактный инвентарь. +3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. ```bash -jq -n \ - --slurpfile ft /tmp/account-ft.json \ - --slurpfile nft /tmp/account-nft.json ' - ($ft[0].tokens // []) as $ft_tokens - | ($nft[0].tokens // []) as $nft_tokens - | { - account_id: ($ft[0].account_id // $nft[0].account_id), - ft_contract_count: ( - $ft_tokens - | map(select((.balance // "0") != "0")) - | length - ), - nft_collection_count: ( - $nft_tokens - | map(.contract_id) - | unique - | length - ), - ft_contracts: ( - $ft_tokens - | map(select((.balance // "0") != "0") | { - contract_id, - balance, - last_update_block_height - }) - | .[:10] - ), - nft_collections: ( - $nft_tokens - | map({ - contract_id, - last_update_block_height - }) - | unique_by(.contract_id) - | .[:10] - ) +jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][""] +' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx + +WIDGET_BLOCK_HEIGHT="$( + jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][":block"] + ' /tmp/bos-widget.json +)" + +SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" +SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" +TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" + +PROVENANCE_METADATA_JSON="$( + jq -nc \ + --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" \ + --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ + --arg block_height "$WIDGET_BLOCK_HEIGHT" \ + --arg source_sha256 "$SOURCE_SHA256" '{ + title: ("BOS widget archive: " + $widget_path), + description: ("Archived from social.near on mainnet at block " + $block_height), + copies: 1, + extra: ({ + author_account_id: $author_account_id, + widget_name: $widget_name, + widget_path: $widget_path, + source_contract_id: "social.near", + source_network: "mainnet", + socialdb_block_height: ($block_height | tonumber), + source_sha256: $source_sha256 + } | @json) }' +)" + +printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' ``` -Для `mike.near` на 19 апреля 2026 года эти чтения вернули десятки FT-контрактов и NFT-коллекций. Этого достаточно для частого wallet-вопроса: «какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает?» +4. Выпустите provenance NFT в testnet. + +```bash +near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ + --arg token_id "$TOKEN_ID" \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --argjson metadata "$PROVENANCE_METADATA_JSON" '{ + token_id: $token_id, + receiver_id: $receiver_id, + metadata: $metadata + }')" \ + --accountId "$SIGNER_ACCOUNT_ID" \ + --deposit 0.1 \ + --networkId testnet +``` + +5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. + +Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. + +```bash +NFT_TOKEN_ARGS_BASE64="$( + jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ + | base64 | tr -d '\n' +)" + +for attempt in 1 2 3 4 5; do + curl -s "$TESTNET_RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$DESTINATION_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_token", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/bos-widget-provenance-token.json >/dev/null + + if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then + break + fi + + sleep 1 +done + +jq '{ + token_id, + owner_id, + title: .metadata.title, + provenance: (.metadata.extra | fromjson) +}' /tmp/bos-widget-provenance-token.json +``` **Зачем нужен следующий шаг?** -Переходите к [`GET /v1/account/{account_id}/full`](https://docs.fastnear.com/ru/api/v1/account-full), когда следующий вопрос уже требует ещё и стейкинг, пулы или нативное состояние аккаунта. Переходите к contract-specific чтениям только тогда, когда вопрос меняется на «какие именно идентификаторы NFT-токенов и метаданные мне принадлежат?» +FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). + +## Частые задачи + +### Что этот аккаунт вообще держит прямо сейчас? + +**Начните здесь** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» + +**Следующая страница при необходимости** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» + +**Остановитесь, когда** + +- Сводка уже отвечает на вопрос по активам в одной выдаче. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Определить аккаунты по публичному ключу + +**Начните здесь** + +- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. +- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. + +**Остановитесь, когда** + +- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). +- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? + +**Начните здесь** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. +- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft), когда вопрос конкретно про владение NFT. +- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» + +**Остановитесь, когда** + +- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. + +**Переходите дальше, когда** + +- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). ## Частые ошибки diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index 35df6e2..e0e0e00 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -1,42 +1,15 @@ **Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) -## Быстрый старт - -Начните с одного поиска по публичному ключу и одного широкого чтения аккаунта. - -```bash -API_BASE_URL=https://api.fastnear.com -PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' - -ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" - -ACCOUNT_ID="$( - curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ - | jq -r '.account_ids[0]' -)" - -echo "$ACCOUNT_ID" - -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' -``` - -Это самый короткий путь к вопросам «какой это аккаунт?» и «что сейчас видно по этому кошельку?» - ## Готовые сценарии +Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. + ### Определить аккаунт по публичному ключу, а затем получить сводку по нему Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» Стратегия - Сначала определите личность, а затем либо сразу проверьте один аккаунт, либо пройдитесь по всему списку, если ключ сопоставляется с несколькими аккаунтами. + Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. @@ -45,8 +18,8 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ **Что вы делаете** - Ищете по публичному ключу один или несколько `account_id`. -- Сначала считаете, сколько `account_id` вернулось, прежде чем выбирать один. -- Сразу переиспользуете один аккаунт или проходите по всему списку, если ключ сопоставляется с несколькими аккаунтами. +- Извлекаете первый найденный `account_id` через `jq`. +- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. ```bash API_BASE_URL=https://api.fastnear.com @@ -62,53 +35,32 @@ ACCOUNT_ID="$( | jq -r '.account_ids[0]' )" -ACCOUNT_COUNT="$( - jq -r '.account_ids | length' /tmp/fastnear-public-key.json -)" +jq '{account_ids}' /tmp/fastnear-public-key.json -jq '{ - account_ids, - account_count: (.account_ids | length) -}' /tmp/fastnear-public-key.json - -if [ "$ACCOUNT_COUNT" -eq 1 ]; then - curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' -else - jq -r '.account_ids[]' /tmp/fastnear-public-key.json \ - | while read -r candidate_account_id; do - curl -s "$API_BASE_URL/v1/account/$candidate_account_id/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' - done -fi +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + state, + token_count: (.tokens | length), + nft_count: (.nfts | length), + pool_count: (.pools | length) + }' ``` **Зачем нужен следующий шаг?** -Поиск по публичному ключу говорит, с каким аккаунтом или аккаунтами вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, именно здесь стоит либо пройтись по каждому найденному `account_id`, либо перейти к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) для более широкого исторического ответа. +Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. -### Есть ли у этого аккаунта прямой стейкинг прямо сейчас? +### Показывает ли этот кошелёк прямой стейкинг, ликвидные стейкинг-токены или и то и другое? -Используйте этот сценарий, когда история проста: «скажи, есть ли у аккаунта видимые прямые staking pool прямо сейчас, и покажи, какие именно это пулы». +Используйте этот сценарий, когда история звучит так: «покажи, видно ли по этому кошельку прямые позиции в staking pool, ликвидные стейкинг-токены или и то и другое». Стратегия - Один раз прочитайте staking-эндпоинт и превратите видимый список пулов в ответ “да / нет”. + Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. - 01GET /v1/account/.../staking возвращает видимые прямые staking-позиции аккаунта. - 02jq превращает ответ в has_direct_staking_now, pool_count и pool_ids. - 03Если массив pools пуст, ответ этой поверхности просто звучит как «прямой стейкинг сейчас не виден». + 01GET /v1/account/.../staking находит прямую экспозицию через пулы. + 02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них. + 03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed. **Сеть** @@ -117,208 +69,344 @@ fi **Официальные ссылки** - [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) +- [Liquid staking](https://docs.near.org/primitives/liquid-staking) + +Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. **Что вы делаете** - Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Печатаете короткий итог “да / нет” и список видимых `pool_id`. -- На этом останавливаетесь, если только следующий вопрос уже не касается `unstake` или `withdraw` в конкретном пуле. +- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. +- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. +- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=YOUR_ACCOUNT_ID +LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' ``` 1. Получите представление по прямому стейкингу. ```bash curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json >/dev/null - -jq '{ - account_id, - has_direct_staking_now: ((.pools // []) | length > 0), - pool_count: ((.pools // []) | length), - pool_ids: ((.pools // []) | map(.pool_id)) -}' /tmp/account-staking.json + | tee /tmp/account-staking.json \ + | jq '{account_id, pools}' ``` -На момент написания для `mike.near` здесь возвращались видимые прямые staking-пулы. Если для вашего аккаунта `pool_ids` пуст, этот эндпоинт отвечает: «прямой стейкинг сейчас не виден». +2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. -**Зачем нужен следующий шаг?** - -Так вопрос остаётся узким и практическим. Если ответ `true`, важно помнить, что это значит на chain-уровне: аккаунт обычно делегировал средства в staking-pool-контракт вроде `polkachu.poolv1.near`, отправив `FunctionCall` наподобие `deposit_and_stake` с attached deposit. Сам `Stake` action позже выполняет уже сам pool-контракт на своём аккаунте. Если ответ `false`, не делайте из этого примера выводов про liquid staking: liquid staking-позиции обычно сначала видны как FT-holdings в конкретных LST-контрактах, поэтому правильный follow-up здесь — FT-пример ниже. И ещё одна граница этой поверхности: этот эндпоинт сейчас не показывает pending-unstake или withdraw-ready amount, так что по нему не стоит отвечать на вопросы о задержках по эпохам. - -#### Необязательное продолжение: Что сделал этот контрактный вызов для делегирования? - -Используйте это продолжение, когда staking-эндпоинт уже показал пул вроде `polkachu.poolv1.near`, и теперь вы хотите увидеть форму одной реальной делегационной транзакции. +```bash +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ + | tee /tmp/account-ft.json >/dev/null +``` -Этот зафиксированный mainnet tx хорош тем, что очень ясно показывает весь паттерн: +3. Классифицируйте аккаунт на основе этих двух индексированных представлений. -- хеш транзакции: `5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz` -- top-level receiver: `polkachu.poolv1.near` -- top-level метод: `deposit_and_stake` -- attached deposit: `34650000000000000000000000` (≈34.65 NEAR) +```bash +jq -n \ + --slurpfile staking /tmp/account-staking.json \ + --slurpfile ft /tmp/account-ft.json \ + --argjson providers "$LIQUID_PROVIDERS_JSON" ' + ($staking[0].pools // []) as $direct_pools + | ($ft[0].tokens // []) as $tokens + | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + | { + classification: + if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" + elif (($direct_pools | length) > 0) then "direct_only" + elif (($liquid_tokens | length) > 0) then "liquid_only" + else "no_visible_staking_position" + end, + direct_pools: ($direct_pools | map(.pool_id)), + liquid_tokens: ( + $liquid_tokens + | map({ + contract_id, + balance, + last_update_block_height + }) + ) + }' +``` -Важная форма chain-истории здесь такая: +**Зачем нужен следующий шаг?** -- делегатор отправляет `FunctionCall deposit_and_stake` в pool-контракт -- pool-контракт учитывает депозит и staking shares -- затем pool выпускает self-receipt с настоящим `Stake` action +Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=5Qo96GonLaAfuh6eHWdi8zPRk92TFW8W2xWqSAoYKBVz +### Заархивировать версию BOS-виджета как provenance NFT -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/staking-delegation-tx.json >/dev/null +Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». -jq '{ - top_level_call: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - attached_deposit: .transactions[0].transaction.actions[0].FunctionCall.deposit - }, - pool_side_effects: [ - .transactions[0].receipts[] - | select(.receipt.receiver_id == "polkachu.poolv1.near") - | { - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - actions: ( - .receipt.receipt.Action.actions - | map(if type == "string" then . else keys[0] end) - ), - first_logs: (.execution_outcome.outcome.logs[:3]) - } - ] -}' /tmp/staking-delegation-tx.json -``` + Стратегия + Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. -Простой вывод здесь такой: делегатор не подписывал сырой `Stake` action напрямую. Он вызвал staking-pool-контракт через `deposit_and_stake` и приложил депозит, а затем уже pool-контракт сам выполнил `Stake` action на своём аккаунте. + 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. + 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. + 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner. -### Какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает? +**Сети** -Используйте этот сценарий, когда у wallet-экрана, support-инструмента или агента уже есть `account_id` и нужен быстрый индексированный обзор holdings: FT-балансы плюс NFT-коллекции, из которых этот аккаунт сейчас что-то показывает. +- mainnet для чтения виджета из `social.near` +- testnet для безопасного mint provenance NFT в `nft.examples.testnet` - Стратегия - Сначала прочитайте FT-балансы, затем NFT-коллекции и только потом соберите их в один компактный индексированный инвентарь. +**Официальные ссылки** - 01GET /v1/account/.../ft даёт индексированные FT-балансы кошелька. - 02GET /v1/account/.../nft даёт NFT-коллекции, из которых этот кошелёк сейчас показывает holdings. - 03jq превращает эти два индексированных чтения в один wallet-friendly инвентарь. +- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) +- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) +- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) **Что вы делаете** -- Читаете FT-балансы аккаунта. -- Читаете NFT-holdings аккаунта на уровне коллекций. -- Печатаете один короткий индексированный инвентарь, который можно переиспользовать в wallet- или support-сценарии. - -Этот пример не отвечает на вопросы про нативный баланс, стейкинг, пулы, точные NFT token ID или метаданные. +- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. +- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. +- Хешируете исходник виджета и превращаете его в provenance-метаданные. +- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. +- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. -FT-эндпоинт здесь решает задачу балансов. Он не включает display-метаданные вроде `symbol` или `decimals`; когда нужно форматировать баланс для UI, вызовите у токен-контракта read-метод `ft_metadata` через RPC. +Зафиксированный исходный виджет: -NFT-эндпоинт здесь работает на уровне коллекций. Воспринимайте его как ответ на вопрос «из каких NFT-контрактов этот аккаунт сейчас что-то держит?», а не как полный per-token crawl. +- аккаунт автора: `mob.near` +- путь виджета: `mob.near/widget/Profile` +- SocialDB-блок уровня виджета: `86494825` ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID - -# Пример живого значения, проверенного 19 апреля 2026 года: -# ACCOUNT_ID=mike.near +API_BASE_URL=https://test.api.fastnear.com +MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com +TESTNET_RPC_URL=https://rpc.testnet.fastnear.com +AUTHOR_ACCOUNT_ID=mob.near +WIDGET_NAME=Profile +DESTINATION_COLLECTION_ID=nft.examples.testnet +RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet +SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" ``` -1. Прочитайте FT-балансы аккаунта. +1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. ```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null +curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ + | tee /tmp/provenance-account-nfts.json >/dev/null -jq '{ - account_id, - ft_contracts: ( - .tokens - | map(select((.balance // "0") != "0") | { +jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ + existing_archive_tokens: [ + .tokens[]? + | select(.contract_id == $destination_collection_id) + | { contract_id, - balance, + token_id, last_update_block_height - }) - | .[:10] - ) -}' /tmp/account-ft.json + } + ] +}' /tmp/provenance-account-nfts.json ``` -2. Прочитайте NFT-коллекции для того же аккаунта. +2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. ```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ - | tee /tmp/account-nft.json >/dev/null +WIDGET_ARGS_BASE64="$( + jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + keys: [($author_account_id + "/widget/" + $widget_name)], + options: {with_block_height: true} + }' | base64 | tr -d '\n' +)" -jq '{ - account_id, - nft_collections: ( - (.tokens // []) - | map({ - contract_id, - last_update_block_height - }) - | unique_by(.contract_id) - | .[:10] +curl -s "$MAINNET_RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: "social.near", + method_name: "get", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/bos-widget.json >/dev/null + +jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ + widget_path: ($author_account_id + "/widget/" + $widget_name), + socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], + source_preview: ( + .[$author_account_id].widget[$widget_name][""] + | split("\n")[0:8] ) -}' /tmp/account-nft.json +}' /tmp/bos-widget.json ``` -3. Соберите из этих двух чтений один компактный инвентарь. +3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. ```bash -jq -n \ - --slurpfile ft /tmp/account-ft.json \ - --slurpfile nft /tmp/account-nft.json ' - ($ft[0].tokens // []) as $ft_tokens - | ($nft[0].tokens // []) as $nft_tokens - | { - account_id: ($ft[0].account_id // $nft[0].account_id), - ft_contract_count: ( - $ft_tokens - | map(select((.balance // "0") != "0")) - | length - ), - nft_collection_count: ( - $nft_tokens - | map(.contract_id) - | unique - | length - ), - ft_contracts: ( - $ft_tokens - | map(select((.balance // "0") != "0") | { - contract_id, - balance, - last_update_block_height - }) - | .[:10] - ), - nft_collections: ( - $nft_tokens - | map({ - contract_id, - last_update_block_height - }) - | unique_by(.contract_id) - | .[:10] - ) +jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][""] +' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx + +WIDGET_BLOCK_HEIGHT="$( + jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' + .[$author_account_id].widget[$widget_name][":block"] + ' /tmp/bos-widget.json +)" + +SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" +SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" +TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" + +PROVENANCE_METADATA_JSON="$( + jq -nc \ + --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ + --arg widget_name "$WIDGET_NAME" \ + --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ + --arg block_height "$WIDGET_BLOCK_HEIGHT" \ + --arg source_sha256 "$SOURCE_SHA256" '{ + title: ("BOS widget archive: " + $widget_path), + description: ("Archived from social.near on mainnet at block " + $block_height), + copies: 1, + extra: ({ + author_account_id: $author_account_id, + widget_name: $widget_name, + widget_path: $widget_path, + source_contract_id: "social.near", + source_network: "mainnet", + socialdb_block_height: ($block_height | tonumber), + source_sha256: $source_sha256 + } | @json) }' +)" + +printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' ``` -Для `mike.near` на 19 апреля 2026 года эти чтения вернули десятки FT-контрактов и NFT-коллекций. Этого достаточно для частого wallet-вопроса: «какие FT-балансы и NFT-коллекции этот аккаунт сейчас показывает?» +4. Выпустите provenance NFT в testnet. + +```bash +near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ + --arg token_id "$TOKEN_ID" \ + --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ + --argjson metadata "$PROVENANCE_METADATA_JSON" '{ + token_id: $token_id, + receiver_id: $receiver_id, + metadata: $metadata + }')" \ + --accountId "$SIGNER_ACCOUNT_ID" \ + --deposit 0.1 \ + --networkId testnet +``` + +5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. + +Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. + +```bash +NFT_TOKEN_ARGS_BASE64="$( + jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ + | base64 | tr -d '\n' +)" + +for attempt in 1 2 3 4 5; do + curl -s "$TESTNET_RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$DESTINATION_COLLECTION_ID" \ + --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "query", + params: { + request_type: "call_function", + account_id: $account_id, + method_name: "nft_token", + args_base64: $args_base64, + finality: "final" + } + }')" \ + | jq '.result.result | implode | fromjson' \ + | tee /tmp/bos-widget-provenance-token.json >/dev/null + + if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then + break + fi + + sleep 1 +done + +jq '{ + token_id, + owner_id, + title: .metadata.title, + provenance: (.metadata.extra | fromjson) +}' /tmp/bos-widget-provenance-token.json +``` **Зачем нужен следующий шаг?** -Переходите к [`GET /v1/account/{account_id}/full`](https://docs.fastnear.com/ru/api/v1/account-full), когда следующий вопрос уже требует ещё и стейкинг, пулы или нативное состояние аккаунта. Переходите к contract-specific чтениям только тогда, когда вопрос меняется на «какие именно идентификаторы NFT-токенов и метаданные мне принадлежат?» +FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). + +## Частые задачи + +### Что этот аккаунт вообще держит прямо сейчас? + +**Начните здесь** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» + +**Следующая страница при необходимости** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» + +**Остановитесь, когда** + +- Сводка уже отвечает на вопрос по активам в одной выдаче. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Определить аккаунты по публичному ключу + +**Начните здесь** + +- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. +- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. + +**Остановитесь, когда** + +- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. + +**Переходите дальше, когда** + +- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). +- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? + +**Начните здесь** + +- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. +- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft), когда вопрос конкретно про владение NFT. +- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. + +**Следующая страница при необходимости** + +- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. +- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» + +**Остановитесь, когда** + +- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. + +**Переходите дальше, когда** + +- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). +- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). ## Частые ошибки diff --git a/static/ru/fastdata/kv.md b/static/ru/fastdata/kv.md index 91089bc..05bb44e 100644 --- a/static/ru/fastdata/kv.md +++ b/static/ru/fastdata/kv.md @@ -2,7 +2,7 @@ # KV FastData API -KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации FastData. +KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации хранилища. ## Базовые URL @@ -14,12 +14,41 @@ https://kv.main.fastnear.com https://kv.test.fastnear.com ``` +## Быстрый старт + +Если вы уже знаете один точный ключ, начните с последней индексированной строки и остановитесь, как только она ответит на вопрос. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' +``` + +Это самый узкий полезный KV-запрос: один точный ключ и одна последняя индексированная строка. Если следующий вопрос уже звучит как «как этот ключ менялся со временем?», переходите к [истории по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или к более подробным [примерам KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples). + ## Используйте этот API, когда - нужно последнее индексированное состояние по одному ключу или известному семейству ключей - нужна история изменений по аккаунту, ключу или `predecessor_id` - нужны пакетные поиски по уже известным точным ключам -- идёт отладка индексированных строк FastData, эмитированных контрактом или предшественником +- идёт отладка хранилища контракта в индексированном виде ## Не стартуйте здесь, когда @@ -45,18 +74,18 @@ https://kv.test.fastnear.com ## Нужен сценарий? -Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории точного ключа, анализа по `predecessor_id` и привязки к транзакции. +Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. ## Рабочий цикл по умолчанию 1. Выберите самую узкую область, подходящую под вопрос пользователя. 2. Оставайтесь внутри KV FastData, пока вопрос остаётся про индексированные данные «ключ–значение». 3. Используйте эндпоинты «последнего значения» для текущих индексированных представлений, а исторические эндпоинты — только когда пользователю нужны ответы с изменением во времени. -4. Остановитесь, как только индексированных строк уже достаточно для ответа на FastData-вопрос. +4. Остановитесь, как только индексированных строк уже достаточно для ответа на вопрос о хранилище. ## Аутентификация и доступность -- Публичные индексированные чтения FastData часто работают и без ключа. +- Публичные индексированные чтения хранилища часто работают и без ключа. - Если вы стандартизируете один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. - Добавьте `?network=testnet`, чтобы переключить страницу на testnet-бэкенд там, где это поддерживается. - В ответах со списками поле `page_token` отсутствует, когда новых результатов больше нет. @@ -67,7 +96,7 @@ https://kv.test.fastnear.com - пользователю нужна каноническая семантика состояния контракта - индексированное представление хранилища — неподходящая абстракция для вопроса -Тогда расширяйтесь на [Просмотр состояния контракта](https://docs.fastnear.com/ru/rpc/contract/view-state) в [Справочнике RPC](https://docs.fastnear.com/ru/rpc) или на собственный read-метод контракта. Не предполагайте, что один ключ FastData напрямую соответствует одному raw-ключу из `view_state`. +Тогда расширяйтесь на [Просмотр состояния контракта](https://docs.fastnear.com/ru/rpc/contract/view-state) в [Справочнике RPC](https://docs.fastnear.com/ru/rpc). ## Устранение неполадок diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index 0750ea7..a7f89ce 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -1,98 +1,74 @@ **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) -## Быстрый старт - -Если точные FastData-ключи уже известны, читайте их напрямую. - -```bash -KV_BASE_URL=https://kv.test.fastnear.com -CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=kv.gork-agent.testnet - -curl -s "$KV_BASE_URL/v0/multi" \ - -H 'content-type: application/json' \ - --data '{ - "keys": [ - "kv.gork-agent.testnet/kv.gork-agent.testnet/key", - "kv.gork-agent.testnet/kv.gork-agent.testnet/value" - ] - }' \ - | jq '{ - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] - }' -``` - -Это самое короткое полезное чтение FastData на странице: один запрос и сразу две точные строки. - ## Готовое расследование -### Прочитать одну индексированную настройку и посмотреть её историю +### Посмотреть индексированные записи одного `predecessor_id`, а затем сузиться до ключа, который изменился -Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки и менялось ли оно раньше?» +Используйте это расследование, когда сначала известен предшественник, а настоящий вопрос звучит так: «что вообще записал этот `predecessor_id`, какая строка здесь действительно интересна и что потом происходило с этим ключом?» Стратегия - Сначала читайте точные строки настройки, расширяйтесь до метаданных предшественника только если нужна provenance-цепочка, и переходите к Transactions API только для финального доказательства. + Начните с области по `predecessor_id`, переходите к точному ключу только после того, как он заслужил внимание, а RPC оставляйте на самый конец. - 01multi или get-latest-key читают точные индексированные строки настройки. - 02get-history-key показывает, менялось ли это индексированное значение позже. - 03Только если важна provenance-цепочка, latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки. + 01all-by-predecessor даёт последние индексированные строки для одного `predecessor_id` по затронутым контрактам. + 02get-history-key или history-by-predecessor объясняют, как менялась интересующая строка во времени. + 03RPC view_state — это уже необязательное точное чтение, когда нужен именно канонический current state, а не индексированная история. **Цель** -- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта и подтвердить историю точного ключа для одной строки. +- Объяснить, что записал этот `predecessor_id`, какой точный ключ стал настоящим фокусом, как он менялся и нужен ли вам вообще финальный `view_state`. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Чтение точной настройки | KV FastData [`multi`](https://docs.fastnear.com/ru/fastdata/kv/multi) | Читаем известные строки `key` и `value` одним запросом | Это самое узкое полезное чтение, когда точные индексированные строки настройки уже известны | -| Чтение точной строки | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Повторно читаем одну точную строку по path-маршруту | Полезно, когда вопрос только про одну строку, а не про всю пару настройки | -| История точного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) | Проверяем историю точной строки `value` | Показывает, менялось ли именно это индексированное значение в нескольких записях | -| Необязательный мост к provenance | KV FastData [`latest-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) | Восстанавливаем `tx_hash` и `receipt_id` для индексированных строк только если provenance действительно важна | Это необязательный мост от индексированных строк обратно к одной записи | -| Необязательная гидратация транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем исходные args только когда нужно это доказательство | Финальное необязательное доказательство того, что обе строки создал один вызов | +| Последние индексированные строки по области | KV FastData [`all-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) | Сначала получаем текущие индексированные записи одного `predecessor_id` по затронутым контрактам | Отвечает на вопрос по области раньше, чем вы начинаете притворяться, будто точный ключ уже известен | +| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Забираем историю точного ключа или ещё на один шаг оставляем историю шире, на уровне предшественника | Показывает, стабильно ли интересующее значение, насколько оно недавнее и не входит ли в более широкий паттерн записей | +| Точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | **Что должен включать полезный ответ** -- какие именно `current_account_id`, `predecessor_id` и индексированные строки настройки исследовались -- как выглядят последние индексированные строки и история точного ключа для одной из них -- какой `tx_hash` или `receipt_id` создал эти строки, только если важна provenance-цепочка -- остаётся ли вопрос про индексированные строки FastData или уже перешёл к каноническому состоянию контракта - -### Проверенный read-only testnet shell-сценарий +- с какой области по `predecessor_id` вы начали +- какой точный ключ стал настоящим фокусом +- как этот ключ менялся в истории +- нужен ли вообще финальный `view_state` -Используйте этот сценарий, когда нужен полностью read-only пример на стабильных sample-данных в `kv.gork-agent.testnet`. +### Shell-сценарий по области предшественника -Этот минимальный контракт ведёт себя как крошечное хранилище настроек: одна запись эмитирует две индексированные строки, `key` и `value`. Сейчас sample-настройка выглядит как `test=hello`, и этого достаточно, чтобы показать shape FastData без притворства, будто перед нами более богатый прикладной объект. -Этот sample-контракт индексирует собственные записи, поэтому в этом walkthrough `CURRENT_ACCOUNT_ID` и `PREDECESSOR_ID` намеренно совпадают. +Используйте этот сценарий, когда сначала известен один `predecessor_id` и нужно аккуратно перейти от вопроса «что он вообще записал?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» **Что вы делаете** -- Читаете точные индексированные строки настройки вместе. -- Повторно читаете те же строки по отдельности, чтобы был понятен shape exact-key маршрута. -- Забираете историю точного ключа для строки `value` этой настройки. -- Останавливаетесь на этом, если provenance дальше не нужна. +- Читаете последние индексированные строки для одного `predecessor_id` по затронутым контрактам. +- Поднимаете интересующие `current_account_id` и точный `key` через `jq`. +- Переиспользуете эти значения в документированном маршруте истории по точному ключу. +- Только после этого решаете, нужен ли вам `view_state` для канонического current state. ```bash -KV_BASE_URL=https://kv.test.fastnear.com -TX_BASE_URL=https://tx.test.fastnear.com -CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=kv.gork-agent.testnet +KV_BASE_URL=https://kv.main.fastnear.com +PREDECESSOR_ID=james.near -curl -s "$KV_BASE_URL/v0/multi" \ +curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ -H 'content-type: application/json' \ - --data '{ - "keys": [ - "kv.gork-agent.testnet/kv.gork-agent.testnet/key", - "kv.gork-agent.testnet/kv.gork-agent.testnet/value" - ] - }' \ + --data '{"include_metadata":true,"limit":10}' \ + | tee /tmp/kv-predecessor.json >/dev/null + +jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ] +}' /tmp/kv-predecessor.json + +CURRENT_ACCOUNT_ID="$(jq -r '.entries[0].current_account_id' /tmp/kv-predecessor.json)" +EXACT_KEY="$(jq -r '.entries[0].key' /tmp/kv-predecessor.json)" +ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" + +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ | jq '{ entries: [ .entries[] @@ -105,112 +81,100 @@ curl -s "$KV_BASE_URL/v0/multi" \ } ] }' +``` -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ - | jq '{ - latest_key_row: ( - .entries[0] - | { - block_height, - key, - value - } - ) - }' +**Зачем нужен следующий шаг?** -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ - | jq '{ - latest_value_row: ( - .entries[0] - | { - block_height, - key, - value - } - ) - }' +Первый запрос отвечает на вопрос по области: «что этот `predecessor_id` сейчас пишет?». Сужение из этой ленты до одного точного ключа отвечает на более точный вопрос: «как именно эта строка дошла до такого состояния?». Если картина всё ещё шире одного ключа, ещё немного побудьте на [History by Predecessor](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), а уже потом переходите к точной истории ключа или RPC. -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ - | jq '{ - page_token, - entries: [ - .entries[] - | { - block_height, - key, - value - } - ] - }' -``` +## Частые задачи -На этом основной read-path заканчивается: точные строки, их exact latest-чтение и история точного ключа для той же индексированной настройки. +### Начать с записей одного `predecessor_id` -### Необязательное расширение до provenance +**Начните здесь** -Переходите сюда только тогда, когда следующий вопрос уже звучит как «какая запись создала эти строки?» +- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor), когда вы знаете, кто писал строки, но ещё не знаете, какой точный ключ важнее всего. -```bash +**Следующая страница при необходимости** -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ - -H 'content-type: application/json' \ - --data '{"include_metadata": true, "limit": 10}' \ - | tee /tmp/kv-predecessor-latest.json >/dev/null +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если одна строка становится настоящим фокусом. +- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), если более широкая картина записей всё ещё важнее точного ключа. -jq '{ - entries: [ - .entries[] - | { - block_height, - key, - value, - tx_hash, - receipt_id - } - ] -}' /tmp/kv-predecessor-latest.json +**Остановитесь, когда** -INDEXED_TX_HASH="$( - jq -r ' - first(.entries[] | select(.key == "value") | .tx_hash) - ' /tmp/kv-predecessor-latest.json -)" +- Уже можно объяснить, что писал этот `predecessor_id`, и заслуживает ли одна строка более глубокой истории. -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$INDEXED_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - args: ( - .transactions[0].transaction.actions[0].FunctionCall.args - | @base64d - | fromjson - ), - receipt_ids: .transactions[0].execution_outcome.outcome.receipt_ids - }' -``` +**Переходите дальше, когда** -**Зачем нужен следующий шаг?** +- Пользователю нужно каноническое текущее состояние в цепочке, а не индексированная история записей. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). + +### Превратить один точный ключ в историю изменений + +**Начните здесь** + +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для поиска истории по пути. +- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. + +**Следующая страница при необходимости** + +- Возвращайтесь к [Последнему по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. + +**Остановитесь, когда** + +- Уже можно объяснить, как ключ менялся со временем. + +**Переходите дальше, когда** + +- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. + +### Проследить записи от одного `predecessor_id` + +**Начните здесь** + +- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. +- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. + +**Следующая страница при необходимости** + +- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. + +**Остановитесь, когда** + +- Уже можно ответить, что именно этот предшественник изменил и где. + +**Переходите дальше, когда** + +- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. + +### Пакетно проверить несколько известных ключей + +**Начните здесь** + +- [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. + +**Следующая страница при необходимости** + +- Переведите один интересный ключ в [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. + +**Остановитесь, когда** + +- Пакетный ответ уже показывает, какие ключи действительно важны. -Этот sample-контракт эмитирует две индексированные строки из одной записи: `key=test` и `value=hello`. Рассматривайте их как одну индексированную настройку. Exact-key маршруты напрямую доказывают эти строки. Lookup по предшественнику с метаданными — это необязательный мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, которые создали эти строки. Гидратация транзакции доказывает, что эти индексированные строки произошли из одного вызова `__fastdata_kv` с декодированными args `{ "key": "test", "value": "hello" }`. +**Переходите дальше, когда** -Именно здесь проходит важная граница этой поверхности: KV FastData отвечает на вопросы про индексированные строки FastData. Если вопрос меняется на каноническое состояние контракта, переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную layout-структуру хранилища. +- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. ## Частые ошибки -- Начинать с широких выборок по предшественнику, когда точные строки FastData уже известны. -- Считать [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) тем же самым, что и [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). Первый маршрут глобальный по строке ключа, второй остаётся внутри одного контракта и predecessor. -- Использовать KV FastData, когда настоящий вопрос про балансы, holdings или account summaries. -- Путать индексированные строки FastData с каноническим on-chain-состоянием контракта. -- Предполагать, что для каждого FastData-расследования сначала обязательно нужна новая запись. +- Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. +- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. +- Путать индексированную историю с точным текущим состоянием в цепочке. +- Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. ## Полезные связанные страницы - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv) -- [Transactions API](https://docs.fastnear.com/ru/tx) - [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [FastNear API](https://docs.fastnear.com/ru/api) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index 0750ea7..a7f89ce 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -1,98 +1,74 @@ **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) -## Быстрый старт - -Если точные FastData-ключи уже известны, читайте их напрямую. - -```bash -KV_BASE_URL=https://kv.test.fastnear.com -CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=kv.gork-agent.testnet - -curl -s "$KV_BASE_URL/v0/multi" \ - -H 'content-type: application/json' \ - --data '{ - "keys": [ - "kv.gork-agent.testnet/kv.gork-agent.testnet/key", - "kv.gork-agent.testnet/kv.gork-agent.testnet/value" - ] - }' \ - | jq '{ - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] - }' -``` - -Это самое короткое полезное чтение FastData на странице: один запрос и сразу две точные строки. - ## Готовое расследование -### Прочитать одну индексированную настройку и посмотреть её историю +### Посмотреть индексированные записи одного `predecessor_id`, а затем сузиться до ключа, который изменился -Используйте это расследование, когда контракт и предшественник уже известны, а вопрос звучит так: «какое текущее значение у этой индексированной настройки и менялось ли оно раньше?» +Используйте это расследование, когда сначала известен предшественник, а настоящий вопрос звучит так: «что вообще записал этот `predecessor_id`, какая строка здесь действительно интересна и что потом происходило с этим ключом?» Стратегия - Сначала читайте точные строки настройки, расширяйтесь до метаданных предшественника только если нужна provenance-цепочка, и переходите к Transactions API только для финального доказательства. + Начните с области по `predecessor_id`, переходите к точному ключу только после того, как он заслужил внимание, а RPC оставляйте на самый конец. - 01multi или get-latest-key читают точные индексированные строки настройки. - 02get-history-key показывает, менялось ли это индексированное значение позже. - 03Только если важна provenance-цепочка, latest-by-predecessor с метаданными плюс POST /v0/transactions доказывают, какая запись создала эти индексированные строки. + 01all-by-predecessor даёт последние индексированные строки для одного `predecessor_id` по затронутым контрактам. + 02get-history-key или history-by-predecessor объясняют, как менялась интересующая строка во времени. + 03RPC view_state — это уже необязательное точное чтение, когда нужен именно канонический current state, а не индексированная история. **Цель** -- Прочитать одну стабильную индексированную настройку из минимального публичного testnet-контракта и подтвердить историю точного ключа для одной строки. +- Объяснить, что записал этот `predecessor_id`, какой точный ключ стал настоящим фокусом, как он менялся и нужен ли вам вообще финальный `view_state`. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Чтение точной настройки | KV FastData [`multi`](https://docs.fastnear.com/ru/fastdata/kv/multi) | Читаем известные строки `key` и `value` одним запросом | Это самое узкое полезное чтение, когда точные индексированные строки настройки уже известны | -| Чтение точной строки | KV FastData [`get-latest-key`](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key) | Повторно читаем одну точную строку по path-маршруту | Полезно, когда вопрос только про одну строку, а не про всю пару настройки | -| История точного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) | Проверяем историю точной строки `value` | Показывает, менялось ли именно это индексированное значение в нескольких записях | -| Необязательный мост к provenance | KV FastData [`latest-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/latest-by-predecessor) | Восстанавливаем `tx_hash` и `receipt_id` для индексированных строк только если provenance действительно важна | Это необязательный мост от индексированных строк обратно к одной записи | -| Необязательная гидратация транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Гидратируем найденный `tx_hash` и декодируем исходные args только когда нужно это доказательство | Финальное необязательное доказательство того, что обе строки создал один вызов | +| Последние индексированные строки по области | KV FastData [`all-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) | Сначала получаем текущие индексированные записи одного `predecessor_id` по затронутым контрактам | Отвечает на вопрос по области раньше, чем вы начинаете притворяться, будто точный ключ уже известен | +| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Забираем историю точного ключа или ещё на один шаг оставляем историю шире, на уровне предшественника | Показывает, стабильно ли интересующее значение, насколько оно недавнее и не входит ли в более широкий паттерн записей | +| Точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | **Что должен включать полезный ответ** -- какие именно `current_account_id`, `predecessor_id` и индексированные строки настройки исследовались -- как выглядят последние индексированные строки и история точного ключа для одной из них -- какой `tx_hash` или `receipt_id` создал эти строки, только если важна provenance-цепочка -- остаётся ли вопрос про индексированные строки FastData или уже перешёл к каноническому состоянию контракта - -### Проверенный read-only testnet shell-сценарий +- с какой области по `predecessor_id` вы начали +- какой точный ключ стал настоящим фокусом +- как этот ключ менялся в истории +- нужен ли вообще финальный `view_state` -Используйте этот сценарий, когда нужен полностью read-only пример на стабильных sample-данных в `kv.gork-agent.testnet`. +### Shell-сценарий по области предшественника -Этот минимальный контракт ведёт себя как крошечное хранилище настроек: одна запись эмитирует две индексированные строки, `key` и `value`. Сейчас sample-настройка выглядит как `test=hello`, и этого достаточно, чтобы показать shape FastData без притворства, будто перед нами более богатый прикладной объект. -Этот sample-контракт индексирует собственные записи, поэтому в этом walkthrough `CURRENT_ACCOUNT_ID` и `PREDECESSOR_ID` намеренно совпадают. +Используйте этот сценарий, когда сначала известен один `predecessor_id` и нужно аккуратно перейти от вопроса «что он вообще записал?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» **Что вы делаете** -- Читаете точные индексированные строки настройки вместе. -- Повторно читаете те же строки по отдельности, чтобы был понятен shape exact-key маршрута. -- Забираете историю точного ключа для строки `value` этой настройки. -- Останавливаетесь на этом, если provenance дальше не нужна. +- Читаете последние индексированные строки для одного `predecessor_id` по затронутым контрактам. +- Поднимаете интересующие `current_account_id` и точный `key` через `jq`. +- Переиспользуете эти значения в документированном маршруте истории по точному ключу. +- Только после этого решаете, нужен ли вам `view_state` для канонического current state. ```bash -KV_BASE_URL=https://kv.test.fastnear.com -TX_BASE_URL=https://tx.test.fastnear.com -CURRENT_ACCOUNT_ID=kv.gork-agent.testnet -PREDECESSOR_ID=kv.gork-agent.testnet +KV_BASE_URL=https://kv.main.fastnear.com +PREDECESSOR_ID=james.near -curl -s "$KV_BASE_URL/v0/multi" \ +curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ -H 'content-type: application/json' \ - --data '{ - "keys": [ - "kv.gork-agent.testnet/kv.gork-agent.testnet/key", - "kv.gork-agent.testnet/kv.gork-agent.testnet/value" - ] - }' \ + --data '{"include_metadata":true,"limit":10}' \ + | tee /tmp/kv-predecessor.json >/dev/null + +jq '{ + page_token, + entries: [ + .entries[] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ] +}' /tmp/kv-predecessor.json + +CURRENT_ACCOUNT_ID="$(jq -r '.entries[0].current_account_id' /tmp/kv-predecessor.json)" +EXACT_KEY="$(jq -r '.entries[0].key' /tmp/kv-predecessor.json)" +ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" + +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ | jq '{ entries: [ .entries[] @@ -105,112 +81,100 @@ curl -s "$KV_BASE_URL/v0/multi" \ } ] }' +``` -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/key" \ - | jq '{ - latest_key_row: ( - .entries[0] - | { - block_height, - key, - value - } - ) - }' +**Зачем нужен следующий шаг?** -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ - | jq '{ - latest_value_row: ( - .entries[0] - | { - block_height, - key, - value - } - ) - }' +Первый запрос отвечает на вопрос по области: «что этот `predecessor_id` сейчас пишет?». Сужение из этой ленты до одного точного ключа отвечает на более точный вопрос: «как именно эта строка дошла до такого состояния?». Если картина всё ещё шире одного ключа, ещё немного побудьте на [History by Predecessor](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), а уже потом переходите к точной истории ключа или RPC. -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/value" \ - | jq '{ - page_token, - entries: [ - .entries[] - | { - block_height, - key, - value - } - ] - }' -``` +## Частые задачи -На этом основной read-path заканчивается: точные строки, их exact latest-чтение и история точного ключа для той же индексированной настройки. +### Начать с записей одного `predecessor_id` -### Необязательное расширение до provenance +**Начните здесь** -Переходите сюда только тогда, когда следующий вопрос уже звучит как «какая запись создала эти строки?» +- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor), когда вы знаете, кто писал строки, но ещё не знаете, какой точный ключ важнее всего. -```bash +**Следующая страница при необходимости** -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID" \ - -H 'content-type: application/json' \ - --data '{"include_metadata": true, "limit": 10}' \ - | tee /tmp/kv-predecessor-latest.json >/dev/null +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если одна строка становится настоящим фокусом. +- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), если более широкая картина записей всё ещё важнее точного ключа. -jq '{ - entries: [ - .entries[] - | { - block_height, - key, - value, - tx_hash, - receipt_id - } - ] -}' /tmp/kv-predecessor-latest.json +**Остановитесь, когда** -INDEXED_TX_HASH="$( - jq -r ' - first(.entries[] | select(.key == "value") | .tx_hash) - ' /tmp/kv-predecessor-latest.json -)" +- Уже можно объяснить, что писал этот `predecessor_id`, и заслуживает ли одна строка более глубокой истории. -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$INDEXED_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - args: ( - .transactions[0].transaction.actions[0].FunctionCall.args - | @base64d - | fromjson - ), - receipt_ids: .transactions[0].execution_outcome.outcome.receipt_ids - }' -``` +**Переходите дальше, когда** -**Зачем нужен следующий шаг?** +- Пользователю нужно каноническое текущее состояние в цепочке, а не индексированная история записей. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). + +### Превратить один точный ключ в историю изменений + +**Начните здесь** + +- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для поиска истории по пути. +- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. + +**Следующая страница при необходимости** + +- Возвращайтесь к [Последнему по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. + +**Остановитесь, когда** + +- Уже можно объяснить, как ключ менялся со временем. + +**Переходите дальше, когда** + +- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. + +### Проследить записи от одного `predecessor_id` + +**Начните здесь** + +- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. +- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. + +**Следующая страница при необходимости** + +- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. + +**Остановитесь, когда** + +- Уже можно ответить, что именно этот предшественник изменил и где. + +**Переходите дальше, когда** + +- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. + +### Пакетно проверить несколько известных ключей + +**Начните здесь** + +- [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. + +**Следующая страница при необходимости** + +- Переведите один интересный ключ в [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. + +**Остановитесь, когда** + +- Пакетный ответ уже показывает, какие ключи действительно важны. -Этот sample-контракт эмитирует две индексированные строки из одной записи: `key=test` и `value=hello`. Рассматривайте их как одну индексированную настройку. Exact-key маршруты напрямую доказывают эти строки. Lookup по предшественнику с метаданными — это необязательный мост к provenance, потому что именно он возвращает `tx_hash` и `receipt_id`, которые создали эти строки. Гидратация транзакции доказывает, что эти индексированные строки произошли из одного вызова `__fastdata_kv` с декодированными args `{ "key": "test", "value": "hello" }`. +**Переходите дальше, когда** -Именно здесь проходит важная граница этой поверхности: KV FastData отвечает на вопросы про индексированные строки FastData. Если вопрос меняется на каноническое состояние контракта, переходите к собственному read-методу контракта или к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state) только тогда, когда вы независимо знаете нужную layout-структуру хранилища. +- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. ## Частые ошибки -- Начинать с широких выборок по предшественнику, когда точные строки FastData уже известны. -- Считать [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key) тем же самым, что и [GET History by Exact Key](https://docs.fastnear.com/ru/fastdata/kv/get-history-key). Первый маршрут глобальный по строке ключа, второй остаётся внутри одного контракта и predecessor. -- Использовать KV FastData, когда настоящий вопрос про балансы, holdings или account summaries. -- Путать индексированные строки FastData с каноническим on-chain-состоянием контракта. -- Предполагать, что для каждого FastData-расследования сначала обязательно нужна новая запись. +- Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. +- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. +- Путать индексированную историю с точным текущим состоянием в цепочке. +- Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. ## Полезные связанные страницы - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv) -- [Transactions API](https://docs.fastnear.com/ru/tx) - [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [FastNear API](https://docs.fastnear.com/ru/api) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/fastdata/kv/index.md b/static/ru/fastdata/kv/index.md index 91089bc..05bb44e 100644 --- a/static/ru/fastdata/kv/index.md +++ b/static/ru/fastdata/kv/index.md @@ -2,7 +2,7 @@ # KV FastData API -KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации FastData. +KV FastData API — это индексированное семейство «ключ–значение». Используйте его, когда уже известен контракт, аккаунт, `predecessor_id` или область ключа, которую нужно проинспектировать, и нужны индексированные строки без построения собственного слоя индексации хранилища. ## Базовые URL @@ -14,12 +14,41 @@ https://kv.main.fastnear.com https://kv.test.fastnear.com ``` +## Быстрый старт + +Если вы уже знаете один точный ключ, начните с последней индексированной строки и остановитесь, как только она ответит на вопрос. + +```bash +KV_BASE_URL=https://kv.main.fastnear.com +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) + }' +``` + +Это самый узкий полезный KV-запрос: один точный ключ и одна последняя индексированная строка. Если следующий вопрос уже звучит как «как этот ключ менялся со временем?», переходите к [истории по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или к более подробным [примерам KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples). + ## Используйте этот API, когда - нужно последнее индексированное состояние по одному ключу или известному семейству ключей - нужна история изменений по аккаунту, ключу или `predecessor_id` - нужны пакетные поиски по уже известным точным ключам -- идёт отладка индексированных строк FastData, эмитированных контрактом или предшественником +- идёт отладка хранилища контракта в индексированном виде ## Не стартуйте здесь, когда @@ -45,18 +74,18 @@ https://kv.test.fastnear.com ## Нужен сценарий? -Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории точного ключа, анализа по `predecessor_id` и привязки к транзакции. +Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. ## Рабочий цикл по умолчанию 1. Выберите самую узкую область, подходящую под вопрос пользователя. 2. Оставайтесь внутри KV FastData, пока вопрос остаётся про индексированные данные «ключ–значение». 3. Используйте эндпоинты «последнего значения» для текущих индексированных представлений, а исторические эндпоинты — только когда пользователю нужны ответы с изменением во времени. -4. Остановитесь, как только индексированных строк уже достаточно для ответа на FastData-вопрос. +4. Остановитесь, как только индексированных строк уже достаточно для ответа на вопрос о хранилище. ## Аутентификация и доступность -- Публичные индексированные чтения FastData часто работают и без ключа. +- Публичные индексированные чтения хранилища часто работают и без ключа. - Если вы стандартизируете один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. - Добавьте `?network=testnet`, чтобы переключить страницу на testnet-бэкенд там, где это поддерживается. - В ответах со списками поле `page_token` отсутствует, когда новых результатов больше нет. @@ -67,7 +96,7 @@ https://kv.test.fastnear.com - пользователю нужна каноническая семантика состояния контракта - индексированное представление хранилища — неподходящая абстракция для вопроса -Тогда расширяйтесь на [Просмотр состояния контракта](https://docs.fastnear.com/ru/rpc/contract/view-state) в [Справочнике RPC](https://docs.fastnear.com/ru/rpc) или на собственный read-метод контракта. Не предполагайте, что один ключ FastData напрямую соответствует одному raw-ключу из `view_state`. +Тогда расширяйтесь на [Просмотр состояния контракта](https://docs.fastnear.com/ru/rpc/contract/view-state) в [Справочнике RPC](https://docs.fastnear.com/ru/rpc). ## Устранение неполадок diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index c92e161..0f48233 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -13,10 +13,10 @@ - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для чтения точных строк FastData, проверки истории точного ключа и необязательной привязки индексированной настройки к исходной транзакции. -- [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC. +- [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Недавние чтения по блокам и шардам для мониторинга активности контракта, подтверждения оптимистичных наблюдений и проверки изменений на уровне шарда. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. -- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для отправки транзакций, проверки прав ключа доступа, предварительной проверки FT, чтения сырого состояния и поиска Rainbow Bridge. +- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. - [API переводов](https://docs.fastnear.com/ru/transfers.md): История переводов NEAR и FT-токенов по аккаунтам для продуктовых лент и инструментов расследования. - [Транзакции API](https://docs.fastnear.com/ru/tx.md): Индексированные запросы по транзакциям, квитанциям, истории аккаунтов и истории блоков для FastNear. @@ -29,11 +29,11 @@ ## Другие гайды -- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и проверки прямого стейкинга. +- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки contract touch, сравнения optimistic и final head, а также прохода по блокам вперёд. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для мониторинга недавней активности контракта, подтверждения optimistic-наблюдений и проверки изменений на уровне shard. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. -- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для фильтрации переводов, чтения humanized amount и running balances, а также перехода к receipt- или transaction-контексту. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. - [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика. - [Berry Club: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Прочитайте живую доску Berry Club через RPC get_lines, а затем используйте Transactions API, чтобы восстановить одну более раннюю эпоху. - [OutLayer: что сделала эта пара request/resolution?](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы прочитать один caller-side запрос OutLayer, одно более позднее worker-side resolution и обращаться к finish-receipts только когда это действительно нужно. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index a8c712b..25b6600 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -4463,6 +4463,21 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash **Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) +## Быстрый старт + +Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. + +Эти helper-скрипты поддерживаются FastNear и оптимизированы под скорость восстановления. Если в вашей среде нужен review изменений, сначала скачайте скрипт, проверьте его и только потом запускайте, вместо прямого piping в `bash`. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash +``` + +Это самый короткий путь восстановления на странице. Всё остальное нужно только если вам нужен standard RPC или архивный hot/cold-сценарий. + ## Готовое расследование ### Выбрать и выполнить правильный сценарий восстановления mainnet @@ -4495,6 +4510,28 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash - куда на диске должны попасть данные - должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap +### Минимальная команда для optimized mainnet `fast-rpc` + +Используйте этот якорь, когда уже ясно, что нужен высокопроизводительный mainnet RPC-узел, и требуется только минимальная рабочая команда. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash +``` + +### Минимальная команда для стандартного mainnet RPC + +Используйте этот якорь, когда нужен обычный путь восстановления mainnet RPC без optimized-профиля `fast-rpc`. + +```bash +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet bash +``` + ### Shell-сценарий архивного восстановления mainnet Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. @@ -4523,80 +4560,6 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. -## Частые задачи - -### Поднять optimized `fast-rpc`-узел в mainnet - -**Начните здесь** - -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), конкретно путь для optimized `fast-rpc`. - -**Следующая страница при необходимости** - -- Вернитесь к обычному сценарию mainnet RPC, если узел не подходит для optimized profile. - -**Остановитесь, когда** - -- Уже есть правильная команда `fast-rpc` и нужные переменные окружения для целевой машины. - -**Переходите дальше, когда** - -- На самом деле требуется архивное хранение, а не просто быстрый запуск. - -### Восстановить обычный RPC-узел в стандартный каталог nearcore - -**Начните здесь** - -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) или [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) в зависимости от сети и выберите обычный RPC-сценарий для нужного окружения. - -**Следующая страница при необходимости** - -- Настраивайте `DATA_PATH`, `THREADS` или ограничения по пропускной способности только после того, как понятен стандартный сценарий. - -**Остановитесь, когда** - -- Уже можно запускать правильную команду восстановления RPC с ожидаемым путём данных. - -**Переходите дальше, когда** - -- Оператору на самом деле нужна архивная история или разнесение hot/cold-данных по разным хранилищам. - -### Правильно поднять архивные hot- и cold-данные mainnet - -**Начните здесь** - -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet), раздел архивного режима. - -**Следующая страница при необходимости** - -- Сначала получите последнюю высоту архивного снапшота, затем запускайте отдельные загрузки hot- и cold-данных с правильными путями. - -**Остановитесь, когда** - -- План по hot-data и cold-data уже ясен, и порядок шагов выбран правильно. - -**Переходите дальше, когда** - -- Оператору нужны уже общие гайды nearcore по bootstrap, а не только FastNear snapshots. - -### Поднять архивные hot-данные в testnet - -**Начните здесь** - -- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet), раздел архивного режима. - -**Следующая страница при необходимости** - -- Получите последнюю высоту архивного снапшота testnet перед шагом загрузки. - -**Остановитесь, когда** - -- Уже есть правильная команда для архивных hot-данных testnet и опорная высота блока снапшота. - -**Переходите дальше, когда** - -- Пользователь на самом деле не поднимает инфраструктуру и должен быть возвращён к документации API или RPC. - ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. @@ -5160,7 +5123,34 @@ https://tx.test.fastnear.com **Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) -Если нужен более развёрнутый разбор на той же поверхности, переходите к [Berry Club](https://docs.fastnear.com/ru/tx/examples/berry-club) для исторического восстановления доски или к [OutLayer](https://docs.fastnear.com/ru/tx/examples/outlayer) для трассировки воркера и callback-цепочки. +## Быстрый старт + +Начните с одного tx hash и сначала получите самый короткий читаемый ответ. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + actions: ( + .transactions[0].transaction.actions + | map(if type == "string" then . else keys[0] end) + ), + first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) + }' +``` + +Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. ## С чего начать @@ -5307,6 +5297,145 @@ curl -s "$TX_BASE_URL/v0/receipt" \ `POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. +### Какая receipt выдала этот лог или event? + +Используйте это расследование, когда история звучит так: «у меня есть один tx hash и один фрагмент лога, и мне нужно точно понять, какая именно receipt его выдала». + +Это другой вопрос, чем более поздний сценарий «дошёл ли callback?». Здесь цель проще: привязать одну наблюдаемую строку лога к одному точному `receipt_id`, одному методу и одному исполнителю. + + Стратегия + Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога. + + 01POST /v0/transactions даёт полный индексированный список receipt для одного tx hash, включая receipt-логи. + 02jq сужает этот список до receipt, в логах которых встречается нужный вам фрагмент. + 03Как только совпадение осталось одно, сохраняйте его receipt_id, executor и имя метода как точный ответ. + +**Цель** + +- Начать с одного mainnet tx hash и одного фрагмента лога и определить точную receipt, которая выдала этот лог. + +Для этого зафиксированного mainnet-примера используйте: + +- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` +- фрагмент лога: `Refund` +- ожидаемый matching `receipt_id`: `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` +- ожидаемый executor: `wrap.near` +- ожидаемый метод: `ft_resolve_transfer` + +Эта транзакция полезна тем, что в ней есть две разные logged receipt внутри одной истории: + +- ранний лог `Transfer ...` на receipt с `ft_transfer_call` +- более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` + +```mermaid +flowchart LR + T["Один tx hash
2KhhB1uD..."] --> L["Читаем все receipt-логи"] + L --> X["Ищем фрагмент:
Refund"] + X --> R["Точная receipt
9sLHQpaG..."] + R --> A["Ответ:
wrap.near / ft_resolve_transfer"] +``` + +| Поверхность | Эндпоинт | Как используем | Зачем используем | +| --- | --- | --- | --- | +| Атрибуция лога | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | +| Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | + +**Что должен включать полезный ответ** + +- какой `receipt_id` выдал лог +- какой контракт исполнил эту receipt +- какой метод там выполнился +- точную строку лога, которая совпала +- одно простое предложение вроде «лог `Refund` пришёл из `wrap.near` в receipt с методом `ft_resolve_transfer`» + +#### Shell-сценарий атрибуции лога + +Используйте этот сценарий, когда у вас уже есть tx hash и следующий вопрос звучит как «какая receipt это сказала?» + +**Что вы делаете** + +- Один раз получаете транзакцию и сохраняете список её receipt. +- Фильтруете receipt по одному фрагменту лога. +- Останавливаетесь, как только у вас есть один точный `receipt_id`, один executor и одно имя метода. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +LOG_FRAGMENT=Refund +``` + +1. Получите транзакцию и сохраните список receipt. + +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/log-attribution-transaction.json >/dev/null +``` + +2. Отфильтруйте список receipt до логов, которые содержат нужный вам фрагмент. + +```bash +jq --arg fragment "$LOG_FRAGMENT" '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id + }, + matching_receipts: [ + .transactions[0].receipts[] + | select(any(.execution_outcome.outcome.logs[]?; contains($fragment))) + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), + block_height: .execution_outcome.block_height, + logs: .execution_outcome.outcome.logs + } + ] +}' /tmp/log-attribution-transaction.json + +# На что смотреть: +# - фрагмент `Refund` совпадает ровно с одной receipt +# - это receipt 9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w +# - receipt исполнилась на wrap.near +# - имя метода — ft_resolve_transfer +``` + +3. Если хотите увидеть все logged receipt рядом, распечатайте только те receipt, где вообще были логи. + +```bash +jq '{ + logged_receipts: [ + .transactions[0].receipts[] + | select((.execution_outcome.outcome.logs | length) > 0) + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), + logs: .execution_outcome.outcome.logs + } + ] +}' /tmp/log-attribution-transaction.json +``` + +Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. + +**Зачем нужен следующий шаг?** + +Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. + ### Превратить один страшный receipt ID из логов в понятную человеческую историю Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. @@ -5359,19 +5488,6 @@ flowchart LR #### Shell-сценарий: от страшного receipt ID к человеческой истории -## Ошибки и async - -Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и порядок callback-ов. - -Используйте этот сценарий, когда у вас уже есть один сырой `receipt_id` из логов и нужно быстро превратить его в читаемое объяснение. - -**Что вы делаете** - -- Сначала разрешаете receipt. -- Извлекаете `receipt.transaction_hash` через `jq`. -- Переиспользуете этот хеш транзакции в `POST /v0/transactions`. -- Завершаете одним человеческим резюме, которое можно вставить в чат или тикет. - ```bash TX_BASE_URL=https://tx.main.fastnear.com RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' @@ -5432,8 +5548,19 @@ jq '{ ```bash jq -r ' + def zeros($n): + reduce range(0; $n) as $i (""; . + "0"); + def yocto_to_near($yocto): + ($yocto | tostring) as $digits + | if ($digits | length) <= 24 then + ("0." + zeros(24 - ($digits | length)) + $digits) + else + ($digits[0:(($digits | length) - 24)] + "." + $digits[-24:]) + end + | sub("0+$"; "") + | sub("\\.$"; ""); .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил 5 NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." + | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил \(yocto_to_near($tx.transaction.actions[0].Transfer.deposit)) NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." ' /tmp/receipt-parent-transaction.json ``` @@ -5445,6 +5572,12 @@ jq -r ' `POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. +## Ошибки и async + +Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. + +Используйте этот раздел, когда уже понятно, что транзакция жила дольше одной receipt, и следующий вопрос относится уже к форме исполнения, а не к простому поиску объекта. + ### Доказать, что одно неудачное действие сорвало весь пакет Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? @@ -5634,7 +5767,7 @@ jq '{ 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. - 03RPC call_function на роутере показывает, закрепилось ли собственное локальное изменение состояния из первой receipt. + 03Как только эти два представления сходятся на одном и том же разрезе истории, остановитесь. Этот пример держится за сохранённые исторические свидетельства, а не за живой read состояния роутера. **Цель** @@ -5661,26 +5794,24 @@ jq '{ ```mermaid flowchart LR T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] - R --> S["Роутер сохраняет локальное состояние
kicked += late-failure"] R --> D["Detached cross-contract receipt
append(...)"] D --> F["Более поздний сбой
CodeDoesNotExist"] - S -. "состояние из первой receipt всё равно закрепилось" .-> K["kicked() всё ещё содержит late-failure"] + T -. "внешняя транзакция всё равно завершается" .-> X["RPC top-level status
SuccessValue"] ``` | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | | Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | | Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | -| Текущее состояние контракта | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `seq-dr.mike.testnet.kicked()` | Показывает, что локальное изменение состояния из первой receipt закрепилось, хотя более поздняя detached-receipt упала | -Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не откатил более раннее изменение состояния роутера. +Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. **Что должен включать полезный ответ** - что подписанная транзакция успешно передала управление в первую router-receipt - что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` - что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` -- что собственное состояние роутера всё ещё содержит `late-failure`, то есть локальный побочный эффект первой receipt закрепился +- что RPC всё ещё показывает верхнеуровневый `SuccessValue`, хотя более поздняя detached-receipt упала - одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции #### Shell-сценарий более позднего сбоя receipt @@ -5691,14 +5822,13 @@ flowchart LR - Читаете транзакцию и её таймлайн receipt из индексированного представления. - Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. -- Читаете текущее состояние роутера, чтобы показать: локальный побочный эффект первой receipt закрепился. +- Останавливаетесь, как только эти два сохранённых представления сходятся на одном и том же разрезе истории. ```bash TX_BASE_URL=https://tx.test.fastnear.com RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz SIGNER_ACCOUNT_ID=temp.mike.testnet -ROUTER_ACCOUNT_ID=seq-dr.mike.testnet FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es ``` @@ -5787,964 +5917,242 @@ jq \ # - более поздняя receipt append(...) упала с CodeDoesNotExist ``` -3. Прочитайте текущее состояние роутера и подтвердите, что локальный побочный эффект первой receipt закрепился. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ROUTER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "kicked", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee /tmp/later-receipt-failure-kicked.json >/dev/null +Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. -jq '{ - kicked: (.result.result | implode | fromjson), - contains_late_failure: ((.result.result | implode | fromjson) | index("late-failure") != null) -}' /tmp/later-receipt-failure-kicked.json -``` +**Зачем нужен следующий шаг?** -Этот последний read и есть практическое доказательство того, что локальное изменение из первой receipt закрепилось. Более поздняя упавшая receipt не откатила более ранний `kicked.push(...)` внутри роутера. +Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. -**Зачем нужен следующий шаг?** +### Дошёл ли callback вообще? -Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и один read состояния контракта, чтобы доказать, что ранний побочный эффект закрепился. +Используйте это расследование, когда одна транзакция запустила downstream-работу на другом контракте, а настоящий вопрос звучит не как «успешно ли отработал receiver?», а как «вернулся ли callback обратно в исходный контракт?» -### Проследить асинхронную promise-цепочку и доказать порядок callback-ов +Это самый короткий полезный сценарий про callback на странице: -Используйте это расследование, когда одна транзакция создаёт promise-работу на потом, вторая позже её resume-ит, и настоящий вопрос звучит не как «обе ли транзакции успешно прошли?», а как «выполнились ли cross-contract callback-и именно в том порядке, который я задумал?» +- стартуйте с одного tx hash +- найдите downstream-receipt на другом контракте +- найдите более поздний callback-receipt, который вернулся в исходный контракт +- остановитесь, как только доказаны сам факт callback и его результат Стратегия - Смотрите на два хеша как на одну async-историю: докажите, что работа была жива, восстановите запрошенный порядок и сравните его с видимым downstream-state. + Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а. - 01RPC call_function на view отложенной работы доказывает, что promise-работа действительно уже была жива до resume-шага. - 02POST /v0/transactions даёт оба block-anchor и точный порядок, который запросила resume-транзакция. - 03RPC EXPERIMENTAL_tx_status вместе с downstream-view доказывают, где callback-и реально выполнились и в каком видимом порядке. + 01POST /v0/transactions показывает downstream-вызов и более поздний receipt, который возвращается в исходный контракт. + 02jq сужает этот список receipt до одного downstream-вызова и одного callback-receipt. + 03RPC EXPERIMENTAL_tx_status нужен только как дополнительное подтверждение, если вам важны канонический результат callback-а и его логи. **Цель** -- Превратить два хеша транзакций в одну читаемую историю доказательства: какая promise-работа была создана, какой порядок запросил resume-вызов и какой порядок позже стал виден в downstream-состоянии контракта. +- Доказать на одном фиксированном mainnet-примере, что `wrap.near` отправил `ft_transfer_call` в `v2.ref-finance.near`, receiver выполнил `ft_on_transfer`, а затем `wrap.near` получил callback `ft_resolve_transfer` обратно. -Если в кодовой базе или во вспомогательных скриптах это называется staged/release- или yield/resume-сценарием, это нормально. Но для документации полезнее более простая модель: +Этот фиксированный mainnet-пример с callback был замечен **19 апреля 2026 года**: -- **создать promise-работу**: одна транзакция готовит отложенную асинхронную работу на потом -- **resume promise-работы**: более поздняя транзакция просит контракт продолжить эту работу в запрошенном порядке -- **проследить async-путь**: деревья receipt показывают, где реально выполнились cross-contract callback-и -- **посмотреть состояние**: downstream-состояние контракта показывает, какой порядок стал виден пользователю или интегратору +- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` +- аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` +- исходный контракт: `wrap.near` +- downstream-receiver: `v2.ref-finance.near` +- верхнеуровневый метод: `ft_transfer_call` +- downstream-метод: `ft_on_transfer` +- callback-метод: `ft_resolve_transfer` +- блок транзакции: `194692298` +- блок downstream-receipt: `194692300` +- блок callback-receipt: `194692301` ```mermaid flowchart LR - Y["Tx 1
создаёт promise-работу"] --> H["Yielded promises становятся доступны
staged_calls_for(...)"] - H --> R["Tx 2
resume-ит promises в порядке beta -> alpha -> gamma"] - R --> C["Async cross-contract callback-и"] - C --> B["Recorder state
beta"] - B --> A["Recorder state
alpha"] - A --> G["Recorder state
gamma"] - Y -. "здесь живёт главное receipt-tree-доказательство" .-> D["Original promise DAG"] - R -. "запрошенный порядок живёт здесь" .-> P["Resume payload"] - G -. "наблюдаемый порядок заканчивается здесь" .-> O["Наблюдаемый downstream-порядок"] + T["Одна mainnet-транзакция
ft_transfer_call на wrap.near"] --> D["Downstream-receipt
v2.ref-finance.near.ft_on_transfer"] + D --> F["Receiver упал
E51: contract paused"] + F --> C["Callback-receipt обратно в wrap.near
ft_resolve_transfer"] + C --> R["Лог refund на wrap.near"] ``` -Это различие важно, потому что одного факта успешности resume-транзакции всё равно недостаточно, чтобы доказать наблюдаемый порядок. Нужны ещё доказательства, что promise-работа действительно стала доступна до resume, и доказательства, что downstream-состояние изменилось в том же порядке, который запросил resume-вызов. - -Для NEAR-инженера здесь важна такая модель: resume-транзакция несёт **запрошенный порядок**, но главной опорной транзакцией расследования обычно всё равно остаётся исходная promise-транзакция, потому что возобновлённые callback-и продолжают жить на её исходном async receipt-tree. Именно downstream-состояние и позволяет затем сравнить запрошенный порядок с наблюдаемым. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Трассировка promise-цепочки | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем хеш исходной promise-транзакции и хеш более поздней resume-транзакции с `wait_until: "FINAL"`, обычно сначала через основной RPC, а при `UNKNOWN_TRANSACTION` — через архивный RPC | Граф квитанций — это основная поверхность доказательства порядка callback-ов и лучший способ понять, какие квитанции принадлежат какому async-дереву транзакции | -| Проверка готовности promise-работы | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Опрашиваем view-метод контракта, который показывает отложенную promise-работу, например `staged_calls_for({ caller_id })`, с `finality: "final"` до появления yield-нутых promises | Подтверждает, что promise-работа действительно стала доступна до того, как resume-транзакция попыталась её продолжить | -| Якорь запрошенного порядка | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Забираем обе транзакции по хешам, чтобы получить `block_height`, `block_hash`, `receiver_id`, индексированный статус исполнения и payload resume-шага | Даёт каждой транзакции устойчивую привязку к блоку и сохраняет точный порядок, который запросил шаг resume | -| Снимки downstream-состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Читаем состояние контракта recorder до resume, а затем опрашиваем его после resume до появления ожидаемых записей | Доказывает реальный порядок callback-ов в состоянии контракта, а не только в метаданных дерева квитанций | -| Переход по квитанции обратно к транзакции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Используем любой интересный ID отложенной или последующей квитанции, чтобы снова привязать его к исходной транзакции | Позволяет быстро перейти от одной квитанции в графе обратно к более широкому рассказу о транзакции | -| Реконструкция по блокам | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок и каскадные блоки с включёнными квитанциями | Восстанавливает временную шкалу исполнения по блокам, когда уже понятно, какие высоты важны | -| Контекст активности аккаунтов | Transactions API [`POST /v0/account`](https://docs.fastnear.com/ru/tx/account) | Запрашиваем историю вызовов функций для контрактов, участвовавших в каскаде, в том же окне | Даёт более удобное для человека представление истории аккаунтов, которое можно сопоставить с трассой | -| Повторное чтение состояния с привязкой к блоку | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Повторно запускаем нужный view-метод recorder с `block_id`, закреплённым на интересных высотах | Превращает итоговое состояние во временной ряд, чтобы можно было сказать не только что изменилось, но и когда именно | - -**Что должен включать полезный ответ** - -- одно итоговое предложение на простом языке, например: «первая транзакция создала три отложенных promises, вторая транзакция resume-нула их в порядке `beta -> alpha -> gamma`, а состояние recorder-контракта позже подтвердило тот же порядок callback-ов» -- почему именно исходная promise-транзакция, а не только resume-транзакция, обычно является главной опорной транзакцией расследования -- какой порядок callback-ов был запрошен и какой порядок downstream-эффектов в итоге наблюдался -- в каких блоках стали видны изменения состояния -- какие receipt-ы или account-pivot-ы стоит сохранить для следующего расследования - -## Доказательства по SocialDB - -Эти примеры стартуют с читаемого состояния в NEAR Social и откатываются назад к точной записи, которая это состояние создала. - -### Доказать, что `mike.near` установил `profile.name` в `Mike Purvis`, а затем восстановить транзакцию записи профиля в SocialDB - -Используйте это расследование, когда история звучит так: «я вижу `Mike Purvis` в профиле NEAR Social аккаунта `mike.near`, но хочу точно доказать, когда это поле было записано и какая транзакция его записала». - - Стратегия - Начните с читаемого значения поля, а затем превратите его field-level block в один receipt и одну транзакцию записи. - - 01NEAR Social POST /get даёт текущее значение profile.name и field-level :block. - 02POST /v0/block превращает этот блок в конкретный receipt и хеш транзакции mike.near -> social.near. - 03POST /v0/transactions доказывает payload записи, а RPC call_function get подтверждает, что поле и сейчас разрешается так же. - -**Цель** - -- Начать с одного читаемого поля профиля в SocialDB, а затем восстановить точный receipt и исходную транзакцию, которые его записали. - -**Официальные ссылки** - -- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) -- [Живая поверхность чтения NEAR Social](https://api.near.social) - -Этот сценарий следует тому же рецепту доказательства, что и расследование по подписке, но добавляет ещё один важный нюанс SocialDB: для исторического доказательства `:block` на уровне конкретного поля обычно точнее, чем `:block` у родительского объекта. В этом живом примере `mike.near/profile/name` был записан на блоке `78675795`, тогда как более широкий объект `mike.near/profile` позже сдвинулся на другой блок из-за изменений в соседних полях. Роль FastNear в этом сценарии — превратить этот блок уровня поля в receipt, затем в транзакцию и потом в читаемый payload записи. - -Для этого живого примера текущее значение `profile.name` равно `Mike Purvis`, блок записи SocialDB на уровне поля равен `78675795`, ID receipt — `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN`, хеш исходной транзакции — `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ`, а внешний блок транзакции — `78675794`. +Здесь хорошо видна одна полезная деталь NEAR: downstream-сбой не означает, что callback исчез. В этом случае `v2.ref-finance.near` уронил свой `ft_on_transfer`, но `wrap.near` всё равно позже получил `ft_resolve_transfer` и залогировал refund. | Поверхность | Эндпоинт | Как используем | Зачем используем | | --- | --- | --- | --- | -| Семантическое чтение поля | NEAR Social `POST /get` | Читаем `mike.near/profile/name` с включёнными метаданными блока | Даёт читаемое значение поля и опорный `:block` SocialDB на уровне поля, где это значение было записано | -| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем блок уровня поля из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи уровня поля в конкретный receipt и хеш исходной транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который нёс `profile.name` и окружающие поля профиля в одном payload | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что поле и сейчас имеет это значение, хотя предыдущие шаги уже доказали конкретную историческую запись | +| Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | +| Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | **Что должен включать полезный ответ** -- разрешается ли `mike.near/profile/name` сейчас в `Mike Purvis` -- высоту блока записи SocialDB на уровне поля (`78675795`) и объяснение, почему для этого вопроса этот якорь лучше, чем блок родительского профиля -- конкретный ID receipt и хеш исходной транзакции за этой записью -- доказательство того, что запись была вызовом `set`, который нёс `profile.name` и другие поля профиля в том же payload -- различие между блоком исполнения receipt (`78675795`) и блоком включения внешней транзакции (`78675794`) +- какой контракт получил downstream-вызов +- какой метод выполнился на downstream-контракте +- вернулся ли более поздний receipt в исходный контракт +- какой callback-метод там выполнился и в каком блоке +- одно простое предложение вроде «receiver упал, но исходный контракт всё равно получил callback и завершил перевод» -#### Shell-сценарий доказательства поля профиля в NEAR Social +#### Shell-сценарий проверки callback-а -Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемого поля профиля в NEAR Social до точной транзакции записи в SocialDB. +Используйте этот сценарий, когда нужен один конкретный proof callback-а без превращения страницы в полный курс по теории promises. **Что вы делаете** -- Читаете текущее поле `profile.name` из NEAR Social и сохраняете блок записи SocialDB на уровне поля. -- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`, несущей `profile.name`. -- Завершаете каноническим RPC-подтверждением того, что поле всё ещё разрешается в то же значение на `final`. +- Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. +- Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. +- Останавливаетесь сразу, как только можете сказать, вернулся ли callback и что он сделал. ```bash -SOCIAL_API_BASE_URL=https://api.near.social TX_BASE_URL=https://tx.main.fastnear.com RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near -PROFILE_FIELD=profile/name +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +SENDER_ACCOUNT_ID=7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72 +ORIGIN_CONTRACT_ID=wrap.near +DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near ``` -1. Прочитайте поле профиля из NEAR Social и сохраните блок записи SocialDB на уровне поля. +1. Получите транзакцию и сохраните receipt-цепочку. ```bash -PROFILE_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-profile-name.json \ - | jq -r --arg account_id "$ACCOUNT_ID" \ - '.[ $account_id ].profile.name[":block"]' -)" +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/callback-check-transaction.json >/dev/null +``` -jq --arg account_id "$ACCOUNT_ID" '{ - current_name: .[$account_id].profile.name[""], - field_block_height: .[$account_id].profile.name[":block"], - parent_profile_block_height: .[$account_id].profile[":block"] -}' /tmp/mike-profile-name.json +2. Сначала ответьте на самый короткий полезный вопрос: вернулся ли callback вообще? -# Ожидаемое current_name: "Mike Purvis" -# Ожидаемая высота блока уровня поля: 78675795 +```bash +jq --arg origin "$ORIGIN_CONTRACT_ID" ' + [ + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + ] | length > 0 +' /tmp/callback-check-transaction.json ``` -2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. +3. Если ответ `true`, распечатайте downstream-receipt вместе с callback-receipt. ```bash -PROFILE_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-profile-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" -jq --arg account_id "$ACCOUNT_ID" '{ - profile_receipt: ( +CALLBACK_RECEIPT_ID="$( + jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | { - receipt_id, - transaction_hash, - block_height, - tx_block_height - } + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) + | .receipt.receipt_id ) - ) -}' /tmp/mike-profile-block.json - -# Ожидаемый receipt ID: 2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN -# Ожидаемый хеш транзакции: 6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ -``` - -3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-profile-transaction.json >/dev/null + ' /tmp/callback-check-transaction.json +)" -jq '{ +jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" '{ transaction: { hash: .transactions[0].transaction.hash, signer_id: .transactions[0].transaction.signer_id, receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height + method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, + tx_block_height: .transactions[0].execution_outcome.block_height }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), - description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), - tags: ( - .args - | @base64d - | fromjson - | .data["mike.near"].profile.tags - | keys - ) - } - ) -}' /tmp/mike-profile-transaction.json -``` - -4. Завершите каноническим подтверждением текущего состояния через raw RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-profile-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - finality: "final", - current_name: ( - .result.result - | implode - | fromjson - | .[$account_id].profile.name - ) -}' /tmp/mike-profile-rpc.json -``` - -Этот последний шаг подтверждает, что поле и сейчас разрешается в `Mike Purvis`. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись установила это поле и какая транзакция несла эту запись. - -**Зачем нужен следующий шаг?** - -NEAR Social даёт семантическое значение поля. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемый payload профиля. RPC даёт каноническое подтверждение текущего состояния. - -### Доказать, что `mike.near` подписался на `mob.near`, а затем восстановить транзакцию записи в SocialDB - -Используйте это расследование, когда история звучит так: «я вижу, что `mike.near` подписан на `mob.near`, но хочу точно доказать, когда именно была записана эта связь и какая транзакция её записала». - - Стратегия - Начните с семантической связи подписки, а затем используйте блок записи как мост назад к одному receipt и одной транзакции. - - 01NEAR Social POST /get даёт читаемую связь подписки и SocialDB :block, где она была записана. - 02POST /v0/block превращает этот блок записи в конкретный receipt и хеш транзакции за этой связью. - 03POST /v0/transactions доказывает payload с graph.follow и index.graph, а RPC call_function get подтверждает, что связь и сейчас существует. - -**Цель** - -- Начать с читаемой связи подписки из NEAR Social, а затем восстановить точный receipt и исходную транзакцию, которые записали её в SocialDB. - -**Официальные ссылки** - -- [API и поверхность контракта SocialDB](https://github.com/NearSocial/social-db#api) -- [Живая поверхность чтения NEAR Social](https://api.near.social) - -Читаемая связь подписки приходит из данных NEAR Social, а не из FastNear. Ключевой мост здесь — метаданные SocialDB `:block`: они указывают на блок, в котором исполнился receipt, записавший это значение. Этот блок не совпадает с блоком, в который была включена внешняя транзакция. Роль FastNear в этом сценарии — превратить эту высоту блока в receipt, затем в транзакцию и, наконец, в читаемую историю исполнения. - -Для этого живого примера текущая связь выглядит как `mike.near -> mob.near`, блок записи SocialDB равен `79574924`, ID receipt — `UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th`, хеш исходной транзакции — `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb`, а внешний блок транзакции — `79574923`. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Семантическое чтение связи | NEAR Social `POST /get` | Читаем `mike.near/graph/follow/mob.near` с включёнными метаданными блока | Даёт читаемую связь подписки и опорный `:block` из SocialDB, где это значение было записано | -| Мост к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Используем высоту блока из SocialDB с `with_receipts: true`, а затем фильтруем receipt обратно до `mike.near -> social.near` | Превращает блок записи SocialDB в конкретный receipt и хеш исходной транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию по хешу и декодируем payload первого `FunctionCall.args` | Доказывает, что базовая запись была вызовом `social.near set`, который записал и `graph.follow`, и записи `index.graph` | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Напрямую вызываем `social.near get` с `final` | Подтверждает, что связь подписки существует и сейчас, хотя предыдущие шаги уже доказали конкретную историческую запись | - -**Что должен включать полезный ответ** - -- существует ли сейчас связь подписки `mike.near -> mob.near` -- высоту блока записи SocialDB (`79574924`) и объяснение, почему это блок исполнения receipt -- конкретный ID receipt и хеш исходной транзакции за этой записью -- доказательство того, что запись была вызовом `set`, который нёс и `graph.follow.mob.near`, и соответствующую запись `index.graph` -- различие между блоком исполнения receipt (`79574924`) и блоком включения внешней транзакции (`79574923`) - -#### Shell-сценарий доказательства подписки в NEAR Social - -Используйте этот сценарий, когда нужен конкретный и воспроизводимый путь доказательства: от читаемой связи подписки в NEAR Social до точной транзакции записи в SocialDB. - -**Что вы делаете** - -- Читаете текущую связь подписки из NEAR Social и сохраняете блок записи SocialDB. -- Переиспользуете эту высоту блока в FastNear block receipts, чтобы получить ID receipt и хеш транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы доказать, что payload был записью `social.near set`. -- Завершаете каноническим RPC-подтверждением того, что связь всё ещё существует на `final`. - -```bash -SOCIAL_API_BASE_URL=https://api.near.social -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near -TARGET_ACCOUNT_ID=mob.near -``` - -1. Прочитайте связь подписки из NEAR Social и сохраните блок записи SocialDB. - -```bash -FOLLOW_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - keys: [($account_id + "/graph/follow/" + $target_account_id)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-follow-edge.json \ - | jq -r --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" \ - '.[ $account_id ].graph.follow[ $target_account_id ][":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - follow_edge: .[$account_id].graph.follow[$target_account_id][""], - follow_block_height: .[$account_id].graph.follow[$target_account_id][":block"] -}' /tmp/mike-follow-edge.json - -# Ожидаемая высота блока записи: 79574924 -``` - -2. Переиспользуйте эту высоту блока в FastNear block receipts и восстановите мост к receipt и транзакции. - -```bash -FOLLOW_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$FOLLOW_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-follow-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - follow_receipt: ( + downstream_receipt: ( first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") + .transactions[0].receipts[] + | select(.receipt.receiver_id == $downstream) | { - receipt_id, - transaction_hash, - block_height, - tx_block_height + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: ( + .receipt.receipt.Action.actions[0] + | if type == "string" then . + else (.FunctionCall.method_name // keys[0]) + end + ), + status: .execution_outcome.outcome.status, + block_height: .execution_outcome.block_height } ) - ) -}' /tmp/mike-follow-block.json - -# Ожидаемый receipt ID: UiyiQaqHbkkMxkrB6rDkYr7X5EQLt8QG9MDATrES7Th -# Ожидаемый хеш транзакции: FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -``` - -3. Переиспользуйте полученный хеш транзакции в `POST /v0/transactions` и декодируйте payload записи SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$FOLLOW_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-follow-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - follow_edge: (.args | @base64d | fromjson | .data["mike.near"].graph.follow["mob.near"]), - index_graph: ( - .args - | @base64d - | fromjson - | .data["mike.near"].index.graph - | fromjson - | map(select(.value.accountId == "mob.near")) - ) - } - ) -}' /tmp/mike-follow-transaction.json -``` - -4. Завершите каноническим подтверждением текущего состояния через raw RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - keys: [($account_id + "/graph/follow/" + $target_account_id)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mike-follow-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg target_account_id "$TARGET_ACCOUNT_ID" '{ - finality: "final", - current_follow_edge: ( - .result.result - | implode - | fromjson - | .[$account_id].graph.follow[$target_account_id] - ) -}' /tmp/mike-follow-rpc.json -``` - -Этот последний шаг подтверждает, что связь подписки существует и сейчас. Предыдущие шаги через NEAR Social и FastNear доказали, какая именно историческая запись создала эту связь и какая транзакция несла эту запись. - -**Зачем нужен следующий шаг?** - -NEAR Social даёт семантическую связь. FastNear block receipts дают мост к конкретной записи. FastNear lookup транзакции превращает эту запись в читаемую историю. RPC даёт каноническое подтверждение текущего состояния. - -### Какая транзакция записала `mob.near/widget/Profile`? - -Используйте это расследование, когда вопрос звучит так: «я уже знаю, что `mob.near/widget/Profile` существует прямо сейчас. Какая именно транзакция записала ту версию виджета, на которую я смотрю?» - -Это естественное tx-продолжение к более лёгкому RPC-сценарию про виджет и к provenance-NFT-сценарию. Задача здесь прямолинейная: - -- стартуем с собственного SocialDB-блока виджета -- превращаем этот блок в один `mob.near -> social.near` receipt -- восстанавливаем исходную транзакцию -- декодируем payload `set` и доказываем, что он действительно нёс исходник виджета - - Стратегия - Смотрите на write-block виджета как на весь мост сразу: блок в receipt, receipt в транзакцию, транзакцию в исходник. - - 01POST /v0/block начинает с блока виджета и сужает его до одного receipt mob.near -> social.near. - 02POST /v0/transactions превращает этот receipt в один читаемый payload set с исходником виджета. - 03RPC call_function get — это финальное подтверждение текущего состояния, что виджет и сейчас существует. - -**Цель** - -- Превратить один SocialDB-блок уровня виджета в один читаемый ответ: какая транзакция записала `mob.near/widget/Profile`, какой receipt исполнил запись и какой именно исходник виджета лежал в payload. - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -Для этого живого якоря: - -- аккаунт: `mob.near` -- виджет: `Profile` -- блок записи в SocialDB: `86494825` -- receipt ID: `CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6` -- хеш исходной транзакции: `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` -- внешний блок транзакции: `86494824` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Мост от блока к receipt | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Берём блок `86494825` с `with_receipts: true`, а затем фильтруем его обратно до `mob.near -> social.near` | Превращает блок записи виджета в один конкретный receipt и один конкретный хеш транзакции | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем исходную транзакцию и декодируем payload `FunctionCall.args` | Доказывает, что запись была вызовом `social.near set`, который нёс исходник `mob.near/widget/Profile` | -| Каноническое подтверждение текущего состояния | RPC [`query(call_function)`](https://docs.fastnear.com/ru/rpc/contract/call-function) | Вызываем `social.near get` напрямую на `final` для того же пути виджета | Подтверждает, что виджет всё ещё существует сейчас, хотя предыдущие шаги уже доказали, какая историческая транзакция его записала | - -**Что должен включать полезный ответ** - -- высоту блока записи и объяснение, что это блок исполнения receipt, а не внешний блок транзакции -- конкретный receipt ID и хеш исходной транзакции за этой записью виджета -- доказательство, что payload записи был `set` с `mob.near/widget/Profile` -- одно простое предложение вроде «`mob.near` записал `widget/Profile` в транзакции `9QDup...`, и в payload действительно лежал текущий исходник profile-виджета» - -#### Shell-сценарий доказательства записи виджета в NEAR Social - -## Трассировка расчёта - -Это самое насыщенное расследование на странице: один живой расчёт NEAR Intents от верхнеуровневой транзакции до receipts и событий, которые его объясняют. - -Используйте этот сценарий, когда хотите превратить один блоковый якорь виджета в точную транзакцию, которая его записала. - -**Что вы делаете** - -- Стартуете с блока последней записи виджета. -- Переиспользуете эту высоту в FastNear block receipts, чтобы получить receipt и мост к транзакции. -- Переиспользуете хеш транзакции в `POST /v0/transactions`, чтобы декодировать записанный исходник виджета. -- Завершаете сырым RPC-подтверждением, что виджет всё ещё существует сейчас. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -WIDGET_BLOCK_HEIGHT=86494825 -``` - -1. Начните с блока последней записи виджета и восстановите SocialDB-receipt вместе с хешем транзакции. - -```bash -WIDGET_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$WIDGET_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mob-widget-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ - widget_write_receipt: ( + ), + callback_receipt: ( first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" + ) | { - receipt_id, - transaction_hash, - block_height, - tx_block_height + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + method_name: .receipt.receipt.Action.actions[0].FunctionCall.method_name, + logs: .execution_outcome.outcome.logs, + status: .execution_outcome.outcome.status, + block_height: .execution_outcome.block_height } ) - ) -}' /tmp/mob-widget-block.json - -# Ожидаемый receipt ID: CZyjiBjphzE95tFEqi1YH6eLCLhqknaW4SQ5R4L6pkC6 -# Ожидаемый хеш транзакции: 9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia -``` - -2. Переиспользуйте хеш транзакции и декодируйте payload `set` из SocialDB. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$WIDGET_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mob-widget-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].transaction.actions[0].FunctionCall - | { - method_name, - widget_source_head: ( - .args - | @base64d - | fromjson - | .data["mob.near"].widget.Profile[""] - | split("\n")[0:12] + ), + callback_ran: ( + first( + .transactions[0].receipts[] + | select( + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" ) - } + | true + ) // false ) -}' /tmp/mob-widget-transaction.json -``` - -Во втором шаге и происходит главный payoff. Тут вы уже не просто говорите «в том блоке что-то обновило SocialDB». Тут вы доказываете, что транзакция `9QDup...` вызвала `social.near set` и пронесла в `args` настоящий исходник `mob.near/widget/Profile`. - -3. Завершите каноническим подтверждением текущего состояния через сырой RPC. - -```bash -SOCIAL_GET_ARGS_BASE64="$( - jq -nr --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] - } | @base64' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$SOCIAL_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/mob-widget-rpc.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - finality: "final", - current_widget_head: ( - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:5] - ) -}' /tmp/mob-widget-rpc.json -``` - -Этот последний шаг подтверждает, что виджет всё ещё существует сейчас. А предыдущие шаги по блоку и транзакции доказывают, какая именно историческая запись его создала. - -**Зачем нужен следующий шаг?** - -Блок записи виджета даёт вам мост. FastNear block receipts превращают этот мост в один receipt и один хеш транзакции. FastNear transaction lookup превращает хеш в читаемое доказательство записи. RPC после этого подтверждает, что виджет всё ещё существует сейчас. - -### Проследить один расчёт NEAR Intents и показать, что именно произошло - -Используйте это расследование, когда история звучит так: «у меня есть одна транзакция `intents.near`. Покажи, что реально произошло в сети, какие контракты участвовали и какие события это подтверждают». - - Стратегия - Смотрите на один расчёт как на читаемую трассу, а не как на теорию протокола с первой строки. - - 01POST /v0/transactions даёт каркас расчёта: входную точку, первые downstream-контракты и ранние логи. - 02POST /v0/block переиспользует тот же якорь, когда нужен контекст включающего блока вокруг этого расчёта. - 03RPC EXPERIMENTAL_tx_status нужен там, где уже требуется канонический DAG по receipt и имена событий, которые доказывают реальное движение активов. - -**Цель** - -- Начать с одной фиксированной транзакции `intents.near` и превратить её в читаемую историю расчёта: какой метод запустил расчёт, какие downstream-контракты появились дальше и какие семейства событий объясняют движение активов. - -**Официальные ссылки** - -- [Обзор NEAR Intents](https://docs.near.org/chain-abstraction/intents/overview) -- [Типы intent и исполнение](https://docs.near-intents.org/integration/verifier-contract/intent-types-and-execution) -- [Абстракция аккаунтов](https://docs.near-intents.org/integration/verifier-contract/account-abstraction) - -Для живой трассировки ниже используйте этот фиксированный якорь расчёта из mainnet, зафиксированный **18 апреля 2026 года**: - -- хеш транзакции: `4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7` -- аккаунт `signer` и `receiver`: `intents.near` -- высота включающего блока: `194573310` - -Быстрая полезная модель здесь простая: - -- `intents.near` выполняет входную точку расчёта -- последующие receipt расходятся по контрактам, которые реально переводят или выводят активы -- журналы событий показывают, какие действия расчёта случились, через имена вроде `token_diff`, `intents_executed`, `mt_transfer` и `mt_withdraw` - -Для этого конкретного расчёта короткий человеческий ответ уже неплохой: - -- `intents.near` вызвал `execute_intents` -- downstream receipt ушли в `v2_1.omni.hot.tg` и `bridge-refuel.hot.tg` -- трасса выдала события `token_diff`, `intents_executed`, `mt_transfer`, `mt_withdraw` и `mt_burn` - -Если нужен протокольный фон, базовая форма сопоставления здесь — это двухсторонний `token_diff` intent: одна сторона подписывает желаемую разницу по активам, вторая сторона подписывает противоположную разницу, а затем совпавшая пара отправляется на расчёт. Но для рабочего расследования обычно понятнее начать с одной реальной транзакции расчёта и читать доказательства прямо с цепочки. - -```mermaid -flowchart LR - T["Одна mainnet-транзакция
4cfei8p4..."] --> I["intents.near
execute_intents"] - I --> R["Последующие receipt"] - R --> C["Подключаются другие контракты"] - R --> E["Появляются журналы событий"] - E --> TD["token_diff"] - E --> IE["intents_executed"] - E --> MT["mt_transfer / mt_withdraw"] -``` - -Публичных FastNear-поверхностей уже достаточно, чтобы ответить на практический вопрос: - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Каркас расчёта | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Начинаем с фиксированного хеша транзакции и печатаем основную транзакцию плюс первые последующие receipt | Даёт самый быстрый читаемый ответ на вопрос «во что этот расчёт пошёл дальше?» | -| Контекст блока | Transactions API [`POST /v0/block`](https://docs.fastnear.com/ru/tx/block) | Загружаем включающий блок с receipt и затем фильтруем его обратно по тому же хешу транзакции | Показывает, в какой блок попал расчёт и какие receipt этой транзакции видны в блоке | -| Каноническое доказательство по receipt и событиям | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `receipts_outcome` плюс логи `EVENT_JSON` | Даёт протокольно-канонический DAG, `executor_id` и имена событий, которые объясняют, что реально сделал расчёт | - -**Что должен включать полезный ответ** - -- какую входную точку расчёта вы увидели на `intents.near` -- какие downstream-контракты и методы появились сразу после неё -- какие семейства событий выпустила трассировка -- одно итоговое предложение простым языком о том, что произошло - -Этот пример намеренно остаётся на публичных FastNear-поверхностях. NEAR Intents Explorer и 1Click Explorer тоже полезны, но их Explorer API защищён JWT и не подходит как дефолтный публичный сценарий в документации. +}' /tmp/callback-check-transaction.json -#### Shell-сценарий расчёта NEAR Intents - -Используйте этот сценарий, когда нужен один конкретный расчёт через `intents.near`, который можно сразу разобрать через публичные FastNear-эндпоинты. - -**Что вы делаете** - -- Получаете читаемую историю расчёта через Transactions API. -- Переиспользуете хеш включающего блока в `POST /v0/block`, чтобы исследовать сам блок. -- Подтверждаете канонический DAG по receipt и семейства событий через `EXPERIMENTAL_tx_status`. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -INTENTS_TX_HASH=4cfei8p4HBeNxJnCLjfShhDYGmXZwFVwFgY1sYpyygE7 -INTENTS_SIGNER_ID=intents.near -``` - -1. Начните с самой транзакции расчёта и восстановите первый читаемый поток последующих receipt. - -```bash -INTENTS_BLOCK_HASH="$( - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$INTENTS_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/intents-transaction.json \ - | jq -r '.transactions[0].execution_outcome.block_hash' -)" - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - receipt_flow: [ - .transactions[0].receipts[:6][] - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - block_height: .execution_outcome.block_height, - methods: ( - [.receipt.receipt.Action.actions[]?.FunctionCall.method_name] - | map(select(. != null)) - ), - first_log: (.execution_outcome.outcome.logs[0] // null) - } - ] -}' /tmp/intents-transaction.json -``` - -Этот первый шаг уже даёт сильный операторский ответ: `intents.near` выполнил транзакцию расчёта, а ранние последующие receipt показывают, какие контракты вошли в каскад сразу после этого. - -2. Переиспользуйте хеш блока, чтобы исследовать включающий блок с включёнными receipt. - -```bash -curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg block_id "$INTENTS_BLOCK_HASH" '{ - block_id: $block_id, - with_receipts: true, - with_transactions: false - }')" \ - | tee /tmp/intents-block.json >/dev/null - -jq --arg tx_hash "$INTENTS_TX_HASH" '{ - block_height: .block.block_height, - block_hash: .block.block_hash, - tx_receipts: [ - .block_receipts[] - | select(.transaction_hash == $tx_hash) - | { - receipt_id, - predecessor_id, - receiver_id, - block_height - } - ] -}' /tmp/intents-block.json -``` - -3. Подтвердите канонический DAG по receipt и извлеките семейства событий через RPC. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$INTENTS_TX_HASH" \ - --arg sender_account_id "$INTENTS_SIGNER_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $sender_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/intents-rpc.json >/dev/null - -jq '{ - final_execution_status: .result.final_execution_status, - receipts_outcome: [ - .result.receipts_outcome[:6][] - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - first_log: (.outcome.logs[0] // null) - } - ] -}' /tmp/intents-rpc.json - -jq -r ' - .result.receipts_outcome[] - | .outcome.logs[] - | select(startswith("EVENT_JSON:")) - | capture("event\":\"(?[^\"]+)\"").event -' /tmp/intents-rpc.json | sort -u +# На что смотреть: +# - downstream-receipt выполнил ft_on_transfer на v2.ref-finance.near +# - более поздний callback-receipt выполнил ft_resolve_transfer на wrap.near +# - callback_ran равно true, даже несмотря на downstream-сбой ``` -**Зачем нужен следующий шаг?** - -`POST /v0/transactions` показывает, во что расчёт пошёл дальше. `POST /v0/block` показывает, где этот расчёт оказался в контексте блока. `EXPERIMENTAL_tx_status` — это каноническое продолжение, когда нужны `executor_id`, структура DAG по receipt и имена событий, чтобы объяснить, что реально произошло. - -## Частые задачи - -### Найти одну транзакцию - -**Начните здесь** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда идентификатор транзакции уже известен. - -**Следующая страница при необходимости** - -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если важной стала последующая квитанция. -- [Block](https://docs.fastnear.com/ru/tx/block), если нужен контекст блока. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), если требуется каноническое подтверждение через RPC. - -**Остановитесь, когда** - -- Уже можно объяснить результат, затронутые аккаунты и главный вывод по исполнению. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точной RPC-семантике статуса или поведения отправки. -- Одного поиска по транзакции недостаточно, чтобы объяснить последующее исполнение. - -### Исследовать квитанцию - -**Начните здесь** - -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), когда ID квитанции — лучший якорь для расследования. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы связать квитанцию с исходной транзакцией. -- [Account History](https://docs.fastnear.com/ru/tx/account), если нужно увидеть активность вокруг одного из затронутых аккаунтов. - -**Остановитесь, когда** - -- Уже можно объяснить, где квитанция находится в цепочке исполнения и почему она важна. - -**Переходите дальше, когда** - -- Нужна точная каноническая проверка сверх индексированного вида квитанции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос расширяется от одной квитанции к более широкому историческому расследованию. - -### Посмотреть недавнюю активность аккаунта - -**Начните здесь** - -- [Account History](https://docs.fastnear.com/ru/tx/account) для ленты активности по аккаунту. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) для конкретной транзакции из ленты. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если фокус смещается на одну квитанцию. - -**Остановитесь, когда** - -- История аккаунта уже отвечает на вопрос о том, что этот аккаунт делал. - -**Переходите дальше, когда** - -- Пользователя интересуют только переводы, а не более широкий контекст исполнения. Переходите к [Transfers API](https://docs.fastnear.com/ru/transfers). -- Пользователю нужно точное текущее состояние или активы, а не история. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc) или [FastNear API](https://docs.fastnear.com/ru/api). - -### Восстановить ограниченное окно по блокам - -**Начните здесь** - -- [Blocks](https://docs.fastnear.com/ru/tx/blocks) для ограниченного просмотра диапазона блоков. -- [Block](https://docs.fastnear.com/ru/tx/block), когда известен точный блок, который нужно исследовать. - -**Следующая страница при необходимости** +4. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), чтобы провалиться в конкретный элемент из окна блоков. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если одной квитанции достаточно для следующего шага расследования. +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg tx_hash "$TX_HASH" \ + --arg sender_account_id "$SENDER_ACCOUNT_ID" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "EXPERIMENTAL_tx_status", + params: { + tx_hash: $tx_hash, + sender_account_id: $sender_account_id, + wait_until: "FINAL" + } + }')" \ + | tee /tmp/callback-check-rpc.json >/dev/null -**Остановитесь, когда** +jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ + top_level_status: .result.status, + callback_receipt: ( + first( + .result.receipts_outcome[] + | select(.id == $callback_receipt_id) + | { + receipt_id: .id, + executor_id: .outcome.executor_id, + logs: .outcome.logs, + status: .outcome.status + } + ) + ) +}' /tmp/callback-check-rpc.json -- Ограниченное историческое окно уже отвечает на вопрос без перехода к более низкоуровневым протокольным деталям. +# На что смотреть: +# - downstream ft_on_transfer receipt упал на v2.ref-finance.near +# - wrap.near всё равно позже получил ft_resolve_transfer +# - лог callback-а показывает refund обратно отправителю +``` -**Переходите дальше, когда** +**Зачем нужен следующий шаг?** -- Пользователю нужны точные канонические поля блока или финальность транзакции. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- На самом деле нужен polling по самым свежим блокам, а не индексированная история. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). +Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. ## Частые ошибки - Пытаться отправлять транзакцию через history API вместо сырого RPC. - Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. - Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». -- Повторно использовать непрозрачные токены пагинации с другим эндпоинтом или другим набором фильтров. ## Полезные связанные страницы @@ -6752,138 +6160,68 @@ jq -r ' - [RPC Reference](https://docs.fastnear.com/ru/rpc) - [FastNear API](https://docs.fastnear.com/ru/api) - [NEAR Data API](https://docs.fastnear.com/ru/neardata) +- [Berry Club: живая доска и один путь исторической реконструкции](https://docs.fastnear.com/ru/tx/examples/berry-club) +- [OutLayer: связать одну транзакцию запроса с одним ответом воркера](https://docs.fastnear.com/ru/tx/examples/outlayer) +- [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) --- -## Berry Club: как восстанавливать исторические доски +## Berry Club: как читать живую доску и разбирать одну эпоху - HTML-маршрут: https://docs.fastnear.com/ru/tx/examples/berry-club - Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples/berry-club.md **Источник:** [https://docs.fastnear.com/ru/tx/examples/berry-club](https://docs.fastnear.com/ru/tx/examples/berry-club) -{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как восстанавливать доски Berry Club через FastNear. Он отделяет текущее состояние из get_lines от исторического разбора через диапазоны блоков, историю аккаунта, раскрытие транзакций и проигрывание draw-аргументов. */} - -# Berry Club: как восстанавливать исторические доски - -Используйте этот разбор, когда вопрос звучит так: «как Berry Club выглядел в определённую эпоху и какие `draw`-вызовы сделали доску именно такой?» +{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough показывает краткий и полезный путь для Berry Club: сначала прочитайте живую доску через RPC get_lines, а Transactions API используйте только тогда, когда нужно восстановить одну более раннюю эпоху по draw-вызовам. */} -Это read-only разбор из семейства Transactions examples. Если нужна только доска прямо сейчас, используйте `get_lines` и остановитесь. Если нужно объяснить, как доска пришла к такому виду, переключайтесь на историю блоков, историю аккаунта, раскрытые `draw`-вызовы и проигрывание. +# Berry Club: как читать живую доску и разбирать одну эпоху - Стратегия - Сначала прочитайте живую доску, затем ограничьте эпоху и только после этого проигрывайте draw-вызовы, которые её объясняют. - - 01RPC call_function get_lines даёт текущую доску 50x50 и показывает, как выглядит «сейчас». - 02POST /v0/blocks вместе с POST /v0/account ограничивают одну эпоху и дают кандидатные хеши draw. - 03POST /v0/transactions раскрывает эти draw-вызовы, чтобы их можно было проиграть в исторические контрольные точки. - -Держите рядом: - -- [js.fastnear.com](https://js.fastnear.com/) -- [fastnear/js-monorepo](https://github.com/fastnear/js-monorepo) -- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) -- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) -- [Transactions API: диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) -- [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) - -В этом руководстве история Berry Club разбирается только на mainnet. Снимки ниже собраны из воспроизводимых данных mainnet, которые уже сохранены в этом репозитории. - -## Короткая версия - -Berry Club даёт чистый view текущего состояния через `get_lines`, но не даёт готового эндпоинта вида «доска на блоке N». - -Из-за этого задача делится на две части: - -- используйте RPC `call_function`, когда вопрос звучит как «как доска выглядит сейчас?» -- используйте индексированную историю, когда вопрос звучит как «какие записи привели к этой доске?» -- используйте архивный RPC только тогда, когда нужно напрямую материализовать уже известную контрольную точку - -```mermaid -flowchart TD - A["RPC call_function: get_lines"] --> B["Текущая доска 50x50"] - C["Transactions API: /v0/blocks"] --> D["Ограничить эпоху"] - D --> E["/v0/account для berryclub.ek.near"] - E --> F["Кандидатные хеши draw-транзакций"] - F --> G["Раскрытие через /v0/transactions"] - G --> H["Проигрывание draw-записей в историческую доску"] -``` - -## Почему Berry Club хорошо учит истории в NEAR +Используйте этот walkthrough, когда живую доску читать легко, но нужен один понятный путь к исторической реконструкции. -Berry Club удобно показывает обе стороны задачи: +Начните с живой доски. Если этого уже достаточно для ответа, на этом можно остановиться. -- чистое чтение текущего состояния через `get_lines` -- длинную историю вызовов `draw` с обычными аргументами `FunctionCall` -- формат доски, который легко декодировать и рендерить обычным JavaScript +Переходите к Transactions API только тогда, когда вопрос становится историческим: «как Berry Club выглядел в одной более ранней эпохе и какие `draw`-вызовы сделали доску именно такой?» -Это очень NEAR-подобная форма: один view-метод для текущего состояния, один write-метод для изменений и индексированная история, когда нужно объяснить, как это состояние вообще появилось. +Карточка живой доски: запрашивает `berryclub.ek.near` `get_lines` через mainnet RPC и рендерит текущую сетку 50x50 в интерфейсе документации. -## 1. Сначала прочитайте текущую доску +## 1. Прочитайте живую доску -Живое демо использует `berryclub.ek.near` и читает доску через view-вызов `get_lines`: +Это самый короткий полезный запрос: -```javascript -await near.view({ - contractId: 'berryclub.ek.near', - methodName: 'get_lines', - args: { - lines: [...Array(50).keys()], - }, -}); -``` - -Это путь текущего состояния. Он не отвечает на вопрос, как доска пришла к такому виду. - -| Вопрос | Лучшая поверхность | Почему | -| --- | --- | --- | -| как доска выглядит сейчас? | [RPC `call_function`](https://docs.fastnear.com/ru/rpc/contract/call-function) | контракт уже отдаёт текущее состояние через `get_lines` | -| какие `draw` были в этой эпохе? | [`/v0/account`](https://docs.fastnear.com/ru/tx/account) + [`/v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | индексированная история даёт ограниченный набор записей и раскрытые аргументы | -| как доска выглядела в известной контрольной точке? | архивный RPC или полное проигрывание | можно напрямую материализовать состояние из архива или восстановить его самому по историческим записям | - -## 2. Как декодировать `get_lines` в сетку 50x50 - -Полезная часть Berry Club-разметки из `js.fastnear.com` — это декодер строк: - -- каждая строка приходит в base64 -- её нужно декодировать в байты -- первые 4 байта нужно пропустить -- дальше цвета читаются как 32-битные little-endian значения каждые 8 байт - -```javascript -function decodeLine(encodedLine) { - const bytes = Buffer.from(encodedLine, 'base64'); - const colors = []; - - for (let offset = 4; offset < bytes.length; offset += 8) { - colors.push(bytes.readUInt32LE(offset) & 0xffffff); - } +```bash +ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" - return colors; -} +curl -sS https://rpc.mainnet.fastnear.com \ + -H 'content-type: application/json' \ + --data "{ + \"jsonrpc\": \"2.0\", + \"id\": \"berry-live-board\", + \"method\": \"query\", + \"params\": { + \"request_type\": \"call_function\", + \"finality\": \"final\", + \"account_id\": \"berryclub.ek.near\", + \"method_name\": \"get_lines\", + \"args_base64\": \"$ARGS_BASE64\" + } + }" | jq '.result | {block_height, line_count: (.result | implode | fromjson | length)}' ``` -Примените это ко всем 50 строкам — и получите полную сетку 50x50, готовую к рендерингу. - -## 3. Ограничьте эпоху, которую хотите изучить +Этот запрос отдаёт текущую доску 50x50 прямо из контракта. Дальше нужно только декодировать каждую base64-строку в 50 цветов пикселей. -Сначала ограничьте эпоху, прежде чем искать draw-записи. Проверочный снимок запуска в этом репозитории находится на блоке `21898354`, а средний снимок — на блоке `97601515`. +## 2. Восстановите одну более раннюю эпоху -Сначала зафиксируйте ближайший диапазон блоков: +Когда нужна история, держите путь коротким: -```bash -curl -sS https://tx.main.fastnear.com/v0/blocks \ - -H 'content-type: application/json' \ - --data '{ - "from_block_height": 21898350, - "to_block_height": 21898355, - "desc": false, - "limit": 5 - }' -``` +1. ограничьте одну эпоху +2. получите кандидатные `draw`-транзакции для `berryclub.ek.near` +3. раскройте эти хеши +4. проиграйте массивы `pixels` от старых к новым -Затем переключитесь на историю аккаунта и запросите активность Berry Club внутри ограниченного диапазона блоков: +В этом примере используется узкое окно вокруг блока `97601515`: ```bash curl -sS https://tx.main.fastnear.com/v0/account \ @@ -6895,21 +6233,14 @@ curl -sS https://tx.main.fastnear.com/v0/account \ "is_real_receiver": true, "from_tx_block_height": 97576515, "to_tx_block_height": 97601516, - "desc": true, - "limit": 40 - }' + "desc": false, + "limit": 200 + }' | jq '.account_txs | map({transaction_hash, tx_block_height}) | .[-5:]' ``` -Здесь полезна именно такая последовательность: - -- `/v0/blocks` помогает понять соседство по высотам блоков -- `/v0/account` возвращает кандидатные хеши транзакций Berry Club внутри этого диапазона - -## 4. Раскройте транзакции и оставьте только `draw` - -Когда кандидатные хеши уже есть, раскройте их и оставьте только верхнеуровневые вызовы `draw`, где получатель — `berryclub.ek.near`. +Если окно ещё нужно подобрать, сначала можно использовать [`/v0/blocks`](https://docs.fastnear.com/ru/tx/blocks). Это не часть основного Berry Club-сценария. -Аргументы вызова — это обычные данные `FunctionCall` вида `{ pixels: [...] }`: +Раскройте кандидатные хеши и оставьте только верхнеуровневые вызовы `draw`: ```bash curl -sS https://tx.main.fastnear.com/v0/transactions \ @@ -6923,329 +6254,279 @@ curl -sS https://tx.main.fastnear.com/v0/transactions \ | select(.transaction.receiver_id == "berryclub.ek.near") | .transaction.actions[]?.FunctionCall | select(.method_name == "draw") - | { - method_name, - args: (.args | @base64d | fromjson) - }' + | {method_name, args: (.args | @base64d | fromjson)}' ``` -Это даёт всё, что нужно для проигрывания: - -- какая транзакция записывала пиксели -- какие координаты были затронуты -- какие цвета были записаны - -## 5. Проиграйте исторические `draw`-вызовы в доску - -Для полного проигрывания держите в памяти массив 50x50 и применяйте раскрытые транзакции `draw` от старых к новым. +Затем проиграйте массивы `pixels` от старых к новым: ```javascript const board = Array.from({ length: 50 }, () => Array(50).fill(0)); -function applyDraw(boardState, drawArgs) { - for (const pixel of drawArgs.pixels) { +for (const drawTx of drawTransactionsOldestFirst) { + for (const pixel of drawTx.args.pixels) { if (pixel.x < 0 || pixel.x >= 50 || pixel.y < 0 || pixel.y >= 50) { continue; } - boardState[pixel.y][pixel.x] = pixel.color; + board[pixel.y][pixel.x] = pixel.color; } } - -for (const drawTx of drawTransactionsOldestFirst) { - applyDraw(board, drawTx.args); -} ``` -Важно не путать два разных пути: - -- `get_lines` — это текущее состояние -- `tx/account` плюс `tx/transactions` — это материал для проигрывания - -## 6. Готовые контрольные точки по эпохам - -Галерея ниже использует уже сохранённые данные снимков, собранные из mainnet-истории Berry Club: - -- `launch` — последний успешный `draw` в пределах первых 24 часов после первого успешного draw -- `mid` — последний успешный `draw` не позже средней временной точки всей истории Berry Club -- `recent` — последний успешный `draw`, который увидел скрипт при пересборке снимков - -Галерея снимков: контрольные точки launch, mid и recent из сохранённого `src/data/berryClubSnapshots.json`. - -Сейчас эти снимки привязаны к таким транзакциям: +В этом и состоит исторический паттерн. У Berry Club нет готового эндпоинта «доска на блоке N», поэтому старые эпохи восстанавливаются проигрыванием `draw`-записей. -- `launch`: `BDNFpCpLXjBrgjR6z6wCZmB9EWdHnVMdqau3iTWTRE5H` на блоке `21898354` -- `mid`: `Hq5qwsuiM2emJrqczWM9awCa7o6sTBYqYpcifUX2SUhQ` на блоке `97601515` -- `recent`: `8tBip5M2TrozhSyepAA3tYXpyKooi5t7b9c64wXjFvfL` на блоке `194588754` - -## Куда идти за подписанными взаимодействиями - -Эта страница должна оставаться в режиме чтения. - -Если нужны живые подписанные сценарии для `draw` и `buy_tokens`, переходите сюда: - -- [js.fastnear.com](https://js.fastnear.com/) -- [Berry Club example в fastnear/js-monorepo](https://github.com/fastnear/js-monorepo/tree/main/examples/static/berryclub) +## Связанные руководства -Именно там уместны кошелёк и подписанные действия. Эта страница посвящена историческому восстановлению. +- [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) +- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) --- -## OutLayer: как проследить один запрос от вызова до callback +## OutLayer: что сделала эта пара request/resolution? - HTML-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer - Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer.md **Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) -{/* FASTNEAR_AI_DISCOVERY: Этот подробный разбор показывает, как использовать FastNear RPC и Transactions API, чтобы разбирать живое исполнение OutLayer в терминах NEAR. Он отделяет видимый request/worker/callback-поток, который уже можно трассировать через FastNear, от задокументированного внутреннего пути yield/resume и CKD/MPC. */} +{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как прочитать один caller-side запрос OutLayer вместе с более поздним worker-side resolution, а затем разобрать finish-receipts только если это действительно нужно. */} -# OutLayer: как проследить один запрос от вызова до callback +# OutLayer: что сделала эта пара request/resolution? -Используйте этот разбор, когда вопрос звучит так: «я вижу OutLayer в цепочке. Какая транзакция открыла работу, какая более поздняя транзакция пришла от воркера и где проявились callback, списание и возврат средств?» +Используйте этот walkthrough, когда вопрос звучит так: «что сделал этот запрос OutLayer, какое более позднее resolution к нему относится и нужно ли мне вообще смотреть на finish-receipts?» -Это продвинутый разбор асинхронного исполнения в семействе Transactions examples. Держите NEAR-рамку первой: один `FunctionCall` со стороны вызывающего, одна более поздняя транзакция со стороны воркера, и квитанции только тогда, когда действительно нужно разбирать фазу завершения. +Оставайтесь в пределах публичных chain-данных: прочитайте request tx, прочитайте более поздний resolution tx и только потом решайте, нужны ли вам finish-receipts. - Стратегия - Сначала найдите caller-транзакцию и worker-транзакцию, а receipts подключайте только тогда, когда настоящим вопросом становится finish-путь. - - 01POST /v0/account — самый быстрый способ найти caller-side и worker-side хеши из одной и той же истории. - 02POST /v0/transactions раскрывает оба хеша и показывает читаемые request, worker-resolution и ранние логи. - 03Только после этого имеет смысл разбирать callback, списание и refund на уровне receipts или уходить в точные RPC-проверки идентичности. +## Компактный shell-сценарий -Полезные ссылки: +Эта пара работала 18 апреля 2026 года: -- [История аккаунта](https://docs.fastnear.com/ru/tx/account) -- [Транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) -- [Просмотр аккаунта](https://docs.fastnear.com/ru/rpc/account/view-account) -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) +- caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` +- worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` -## Короткая версия +### 1. Основной ответ: сразу раскройте request tx и resolution tx вместе -Если вы видите активность OutLayer в цепочке, практические вопросы обычно такие: +```bash +TX_BASE_URL=https://tx.main.fastnear.com +REQUEST_TX_HASH=AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4 +WORKER_TX_HASH=AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs -- какая транзакция создала асинхронную единицу работы? -- какая более поздняя транзакция пришла от воркера? -- где именно проявились callback, списание и возврат средств? +curl -sS "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg request_tx_hash "$REQUEST_TX_HASH" --arg worker_tx_hash "$WORKER_TX_HASH" '{ + tx_hashes: [$request_tx_hash, $worker_tx_hash] + }')" \ + | tee /tmp/outlayer-pair.json >/dev/null -Это не вопрос о текущем состоянии. Это вопрос об истории исполнения. +jq '{ + transactions: [ + .transactions[] + | { + hash: .transaction.hash, + signer_id: .transaction.signer_id, + receiver_id: .transaction.receiver_id, + methods: [ + .transaction.actions[] + | .FunctionCall.method_name? + | select(. != null) + ], + first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) + } + ] +}' /tmp/outlayer-pair.json +``` -Полезный ход через FastNear — связать одну транзакцию `request_execution` со стороны вызывающего с одной транзакцией разрешения со стороны воркера, а к receipt переходить только на этапе завершения. +Это и есть основной ответ: один request tx, один более поздний resolution tx и читаемые signer-, receiver-, method- и log-доказательства для обеих транзакций. -```mermaid -sequenceDiagram - autonumber - participant Caller as "Вызывающая сторона" - participant Outlayer as "outlayer.*" - participant Worker as "worker.outlayer.*" - participant Near as "исполняющая среда NEAR" +### Необязательное продолжение: Что сделал finish-путь? - Caller->>Outlayer: FunctionCall request_execution - Outlayer-->>Near: создаёт асинхронную работу и контекст учёта - Worker->>Outlayer: resolve_execution или submit_execution_output_and_resolve - Outlayer-->>Near: логи завершения, путь callback, списание, возвраты +```bash +jq --arg worker_tx_hash "$WORKER_TX_HASH" ' + .transactions[] + | select(.transaction.hash == $worker_tx_hash) + | { + worker_tx_hash: .transaction.hash, + receipts: [ + .receipts[] + | { + receipt_id: .receipt.receipt_id, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + actions: [.receipt.receipt.Action.actions[] | keys[0]], + logs: .execution_outcome.outcome.logs + } + ] + } +' /tmp/outlayer-pair.json ``` -Всё это уже видно сегодня через FastNear и RPC. +Смотрите на самое маленькое читаемое доказательство finish-пути: -## 1. Раскройте одну транзакцию запроса и одно разрешение воркера - -Если хотите сразу увидеть всю форму потока, начните с уже известной пары хешей и раскройте оба. +- `FunctionCall`-receipts, которые продолжают finish-путь +- логи списания вроде `[[yNEAR charged: "..."]]` +- последующие `Transfer`-receipts, которые похожи на refund или settlement-движение -Эта пара работала 18 апреля 2026 года: +Именно здесь receipts становятся нужной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции здесь относятся друг к другу?» -- `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` — `request_execution` со стороны вызывающего -- `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` — `submit_execution_output_and_resolve` со стороны воркера +### Необязательный шаг: сначала найдите два хеша -```bash title="Раскройте хеш запроса и хеш разрешения воркера" -curl -sS https://tx.main.fastnear.com/v0/transactions \ +```bash +curl -sS "$TX_BASE_URL/v0/account" \ -H 'content-type: application/json' \ - --data '{ - "tx_hashes":[ - "AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4", - "AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs" - ] - }' | jq '.transactions[] | { - hash: .transaction.hash, - signer: .transaction.signer_id, - receiver: .transaction.receiver_id, - actions: [.transaction.actions[] | keys[0]], - logs: (.receipts[0].execution_outcome.outcome.logs[:2]) - }' + --data '{"account_id":"outlayer.near","desc":true,"limit":10}' \ + | jq '{ + txs_count, + recent_hashes: [.account_txs[:10][] | .transaction_hash] + }' ``` -В этом выборочном выводе: +Используйте это только если вы ещё не знаете пару хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` превращает их в читаемый ответ. -- хеш запроса шёл от `solarflux.near` к `outlayer.near` -- в логах фигурировал разрешённый проект: `zavodil.near/near-email` -- хеш воркера шёл от `worker.outlayer.near` к `outlayer.near` -- в логах воркера было `Stored pending output` и `Resolving execution ... (combined flow)` - -Этого уже достаточно для видимой истории в терминах NEAR: исходный `FunctionCall` создал асинхронную единицу работы, позже воркер вернулся как отдельный подписант, а контракт разрешил результат в цепочке. +## Полезные связанные страницы -Если копировать с этой страницы только одну команду, то именно эту. +- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) +- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +- [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) -## 2. Найдите два нужных хеша сами +--- -Если пары хешей у вас ещё нет, переключитесь на [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account). +## Расширенный поиск записи SocialDB -```bash title="Недавняя mainnet-активность для outlayer.near" -curl -sS https://tx.main.fastnear.com/v0/account \ - -H 'content-type: application/json' \ - --data '{"account_id":"outlayer.near","desc":true}' \ - | jq '{txs_count, first: .account_txs[0]}' -``` +- HTML-маршрут: https://docs.fastnear.com/ru/tx/socialdb-proofs +- Markdown-маршрут: https://docs.fastnear.com/ru/tx/socialdb-proofs.md -18 апреля 2026 года эта поверхность показывала более 5 000 трассированных транзакций для `outlayer.near`, а самый свежий выборочный хеш был таким: +**Источник:** [https://docs.fastnear.com/ru/tx/socialdb-proofs](https://docs.fastnear.com/ru/tx/socialdb-proofs) -```text -AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs -``` +# Расширенный поиск записи SocialDB -Этот хеш не был исходным пользовательским запросом. Это уже было последующее действие со стороны воркера. +Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому поиску записи. -Именно поэтому история аккаунта — правильный первый поиск: здесь задача не в том, чтобы описать контракт целиком, а в том, чтобы найти две конкретные транзакции в одной истории исполнения. +Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" -```mermaid -flowchart TD - A["Transactions API: /v0/account для outlayer.*"] --> B["Найдите недавние хеши транзакций"] - B --> C["Хеш request_execution со стороны вызывающего"] - B --> D["Хеш разрешения со стороны воркера"] - C --> E["Раскройте оба через /v0/transactions"] - D --> E - E --> F["Проверьте логи, request_id, project/source, callback-receipt, списание и возвраты"] -``` +## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` -## 3. Разберите фазу callback и возврата средств +Используйте этот сценарий, когда читаемый факт уже звучит как "текущее `profile.name` равно `Mike Purvis`", а остаётся только вопрос, какая запись сделала это поле истинным. -Если нужно пройти дальше, чем просто «воркер вернул результат», посмотрите список receipt у раскрытой воркерской транзакции. +Здесь достаточно сохранить один важный нюанс SocialDB: для исторического provenance правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. -```bash title="Показать последующие действия на уровне receipt для разрешения воркером" -curl -sS https://tx.main.fastnear.com/v0/transactions \ - -H 'content-type: application/json' \ - --data '{"tx_hashes":["AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs"]}' \ - | jq '.transactions[0] | { - hash: .transaction.hash, - receipts: [ - .receipts[] | { - predecessor: .receipt.predecessor_id, - receiver: .receiver_id, - actions: [.receipt.receipt.Action.actions[] | keys[0]], - logs: .execution_outcome.outcome.logs - } - ] - }' -``` +Для этого живого якоря: -На что смотреть: +- текущее `profile.name`: `Mike Purvis` +- блок записи SocialDB на уровне поля: `78675795` +- receipt ID: `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN` +- хеш исходной транзакции: `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ` +- внешний блок транзакции: `78675794` -- `FunctionCall:on_execution_response` -- логи списания вроде `[[yNEAR charged: "..."]]` -- события завершения вроде `execution_completed` -- последующие receipt `Transfer` +### Shell-сценарий -Здесь понятие receipt как раз становится правильной абстракцией: не в начале урока, а тогда, когда уже отлаживается реальный путь завершения. +1. Прочитайте поле из NEAR Social и сохраните block записи на уровне поля. -## 4. Подтвердите контракт, если нужна точная проверка +```bash +SOCIAL_API_BASE_URL=https://api.near.social +TX_BASE_URL=https://tx.main.fastnear.com +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +PROFILE_FIELD=profile/name -Если нужна точная проверка аккаунта и `code_hash`, используйте сырой RPC. Это шаг для проверки идентичности, а не для восстановления истории исполнения. +PROFILE_BLOCK_HEIGHT="$( + curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc \ + --arg account_id "$ACCOUNT_ID" \ + --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | tee /tmp/mike-profile-name.json \ + | jq -r --arg account_id "$ACCOUNT_ID" \ + '.[ $account_id ].profile.name[":block"]' +)" -```bash title="Mainnet: view_account для outlayer.near" -curl -sS https://rpc.mainnet.fastnear.com \ - -H 'content-type: application/json' \ - --data '{ - "jsonrpc":"2.0", - "id":"1", - "method":"query", - "params":{ - "request_type":"view_account", - "finality":"final", - "account_id":"outlayer.near" - } - }' | jq '.result | {amount, locked, code_hash, storage_usage}' +jq --arg account_id "$ACCOUNT_ID" '{ + current_name: .[$account_id].profile.name[""], + field_block_height: .[$account_id].profile.name[":block"], + parent_profile_block_height: .[$account_id].profile[":block"] +}' /tmp/mike-profile-name.json ``` -```bash title="Testnet: view_account для outlayer.testnet" -curl -sS https://rpc.testnet.fastnear.com \ - -H 'content-type: application/json' \ - --data '{ - "jsonrpc":"2.0", - "id":"1", - "method":"query", - "params":{ - "request_type":"view_account", - "finality":"final", - "account_id":"outlayer.testnet" - } - }' | jq '.result | {amount, locked, code_hash, storage_usage}' -``` +2. Переиспользуйте этот block уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. -По состоянию на 18 апреля 2026 года оба контракта возвращали один и тот же `code_hash`: +```bash +PROFILE_TX_HASH="$( + curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | tee /tmp/mike-profile-block.json \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" -```text -94uKcoDB3QbEpxDj1xsw9CQwu9bAY1PoVPr2BZYRRv4K +jq --arg account_id "$ACCOUNT_ID" '{ + profile_receipt: ( + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | { + receipt_id, + transaction_hash, + block_height, + tx_block_height + } + ) + ) +}' /tmp/mike-profile-block.json ``` -Это сильный сигнал, что на обеих сетях развёрнут один и тот же бинарник контракта. - -## 5. Что происходит внутри? - -Видимая история выше — это то, что NEAR-разработчику нужно сначала. Более глубокий слой объясняет, почему этот поток вообще интересен. - -### Что можно наблюдать уже сейчас - -Через FastNear и RPC уже видно: +3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. -- вызывающую транзакцию `request_execution` -- воркерскую `resolve_execution` или `submit_execution_output_and_resolve` -- завершающие receipt, где материализуются callback, списание и возврат средств - -Интеграция вашего контракта при этом остаётся обычной асинхронной композицией в NEAR: вы вызываете `outlayer.*`, а потом обрабатываете свой callback. - -### Что задокументировано как внутренний механизм +```bash +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | tee /tmp/mike-profile-transaction.json >/dev/null -Документация OutLayer описывает более глубокий внутренний слой: `outlayer.*` использует семантику NEAR `yield/resume` как свою внутреннюю асинхронную границу, внешняя работа выполняется в TEE-воркерах, а защищённые секреты проходят через отдельный путь доверия, где TEE-keystore получает DAO-gated CKD через MPC signer. +jq '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | { + method_name, + profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), + description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), + tags: ( + .args + | @base64d + | fromjson + | .data["mike.near"].profile.tags + | keys + ) + } + ) +}' /tmp/mike-profile-transaction.json +``` -Для NEAR-разработчика здесь важна точность: мы не говорим, что ваш вызывающий контракт сам пишет `promise_yield_create`. Примитивы `yield/resume` в NEAR работают только в рамках одного и того же аккаунта, поэтому если этот механизм используется здесь, то yielding и resuming делает `outlayer.*`, а не исходный вызывающий контракт. Для сырой модели выполнения смотрите [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features). +Это и есть весь паттерн lookup-а: читаемое значение, block уровня поля, мост через receipt и payload транзакции. -Документация Secrets / CKD описывает этот путь keystore как двухуровневый: сначала keystore получает derivation key через DAO-gated путь к MPC, а затем использует уже полученную derivation capability для защищённых секретов во время исполнений приложения. Это объяснение доверительной модели, а не утверждение, что каждое обычное исполнение OutLayer делает новый DAO -> MPC round trip. +Тот же мост работает и для других читаемых значений SocialDB: -Публичный gateway-аккаунт для этого пути keystore / DAO в наших текущих публичных chain-данных всё ещё не подтверждён, поэтому эту часть надо держать в корзине «задокументировано внутри», а не в корзине «уже наблюдается сейчас». +- вариант для связи подписки: `mike.near -> mob.near`, блок `79574924`, tx `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` +- вариант для исходника виджета: `mob.near/widget/Profile`, блок `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` -```mermaid -flowchart TB - subgraph Observable["Что уже можно наблюдать через FastNear / RPC"] - Caller["Пользователь или вызывающий контракт"] -->|FunctionCall request_execution| Entry["outlayer.*"] - Entry -->|поздняя активность воркера| Worker["worker.outlayer.*"] - Worker -->|resolve_execution или submit_execution_output_and_resolve| Entry - Entry -->|on_execution_response, списание, refund-переводы| Finish["Callback / finish receipt"] - end - - subgraph Internal["Что задокументировано как внутренний слой"] - Yield["внутренняя yield-точка в outlayer.*"] --> TEE["TEE-воркер исполняет WASM"] - TEE -->|resume с результатом| Yield - TEE --> Keystore["TEE-keystore для защищённых секретов"] - Keystore -->|задокументированный DAO-gated request_key| DAO["DAO gateway (задокументирован)"] - DAO -->|задокументированный CKD request| MPC["v1.signer / v1.signer-prod.testnet"] - MPC -->|derivation key для keystore| Keystore - end - - Entry -. та же логическая история исполнения .-> Yield - - classDef observable fill:#e7f1ff,stroke:#2563eb,color:#0f172a; - classDef internal fill:#fff4e5,stroke:#b45309,color:#0f172a; - class Caller,Entry,Worker,Finish observable; - class Yield,TEE,Keystore,DAO,MPC internal; -``` - -## Куда читать дальше - -- [Transactions API](https://docs.fastnear.com/ru/tx) для истории аккаунта, receipt и раскрытия транзакций -- [Продвинутые возможности](https://docs.fastnear.com/ru/transaction-flow/advanced-features) для семантики `yield/resume` в NEAR -- [Асинхронная модель](https://docs.fastnear.com/ru/transaction-flow/async-model) для лексики promise и callback -- [NEAR Integration в OutLayer](https://outlayer.fastnear.com/docs/near-integration) для задокументированного контрактного интерфейса -- [Secrets / CKD в OutLayer](https://outlayer.fastnear.com/docs/secrets) для задокументированного пути keystore, DAO и MPC +Ключевая идея не меняется: начните с читаемого значения и его write-block, восстановите receipt `*.near -> social.near` из блока, а затем декодируйте payload `social.near set` из исходной транзакции. --- diff --git a/static/ru/llms.txt b/static/ru/llms.txt index 0a47bc8..f84fefa 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -16,10 +16,10 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для чтения точных строк FastData, проверки истории точного ключа и необязательной привязки индексированной настройки к исходной транзакции. -- [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Кэшированные и архивные чтения по блокам для оптимистичных, финализированных и сценариев с перенаправлением. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC. +- [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Недавние чтения по блокам и шардам для мониторинга активности контракта, подтверждения оптимистичных наблюдений и проверки изменений на уровне шарда. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. -- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для отправки транзакций, проверки прав ключа доступа, предварительной проверки FT, чтения сырого состояния и поиска Rainbow Bridge. +- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. - [API переводов](https://docs.fastnear.com/ru/transfers.md): История переводов NEAR и FT-токенов по аккаунтам для продуктовых лент и инструментов расследования. - [Транзакции API](https://docs.fastnear.com/ru/tx.md): Индексированные запросы по транзакциям, квитанциям, истории аккаунтов и истории блоков для FastNear. @@ -32,11 +32,11 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп ## Другие гайды -- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, инвентаризации активов и проверки прямого стейкинга. +- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для проверки contract touch, сравнения optimistic и final head, а также прохода по блокам вперёд. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для мониторинга недавней активности контракта, подтверждения optimistic-наблюдений и проверки изменений на уровне shard. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. -- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для фильтрации переводов, чтения humanized amount и running balances, а также перехода к receipt- или transaction-контексту. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. - [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика. - [Berry Club: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Прочитайте живую доску Berry Club через RPC get_lines, а затем используйте Transactions API, чтобы восстановить одну более раннюю эпоху. - [OutLayer: что сделала эта пара request/resolution?](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы прочитать один caller-side запрос OutLayer, одно более позднее worker-side resolution и обращаться к finish-receipts только когда это действительно нужно. diff --git a/static/ru/neardata.md b/static/ru/neardata.md index f46f3cf..2361dfc 100644 --- a/static/ru/neardata.md +++ b/static/ru/neardata.md @@ -2,7 +2,7 @@ # NEAR Data API -NEAR Data API — это поверхность для чтения данных почти в реальном времени, а также по семействам блоков. Используйте её, когда нужны свежие срезы блоков, вспомогательные маршруты с перенаправлением или недавние финализированные и оптимистичные чтения, но без позиционирования продукта как потокового сервиса. +NEAR Data API — это поверхность для недавних блоков и шардов. Используйте её, когда нужны свежие срезы блоков, мониторинг активности контракта, вспомогательные маршруты с перенаправлением или сравнение оптимистичных и финализированных чтений без превращения продукта в потоковый API. ## Базовые URL @@ -17,8 +17,9 @@ https://testnet.neardata.xyz ## Лучше всего подходит для - опроса недавних финализированных и оптимистичных блоков; -- вспомогательных маршрутов по блокам и сценариев с перенаправлением; -- лёгких проверок свежести данных и мониторинга. +- обнаружения того, появился ли живой контракт в недавнем блоке и изменил ли он состояние; +- сравнения оптимистичного сигнала с финализированным подтверждением; +- проверки одного недавнего шарда, когда уже известно, какой блок важен. ## Когда его не стоит использовать @@ -33,13 +34,14 @@ https://testnet.neardata.xyz ## С чего обычно начинают -- [Оптимистичный блок](https://docs.fastnear.com/ru/neardata/block-optimistic) — для самого свежего опроса блоков. -- [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block) и [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers) — для запросов по финализированным блокам. -- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) и [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) — когда нужны вспомогательные маршруты с перенаправлением. +- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) и [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) — когда нужно быстро узнать самый новый недавний блок. +- [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block) — когда нужен один недавний гидратированный блок с уже приложенными данными по шардам. +- [Шард блока](https://docs.fastnear.com/ru/neardata/block-shard) — когда недавний блок уже показал нужный шард и его надо разобрать глубже. +- [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers) — когда важнее движение головы цепочки и финальности, а не широкий блоковый документ. ## Нужен сценарий? -Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для простых пошаговых сценариев: опроса оптимистичных блоков, подтверждения финализированных блоков, работы с перенаправлениями и перехода к каноническому RPC. +Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для компактных сценариев: обнаружения активности контракта, сравнения оптимистичных и финализированных наблюдений, а также проверки изменений на уровне шарда. ## Устранение неполадок diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index 163c9b8..22548d4 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -1,90 +1,60 @@ **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) -## Быстрый старт +NEAR Data особенно хороша там, где вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. -Начните с одного недавнего финализированного блока и сначала запросите самую маленькую возможную touch-сводку. - -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=intents.near - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | jq --arg target "$TARGET_ACCOUNT_ID" '{ - height: .block.header.height, - hash: .block.header.hash, - direct_tx_count: ([.shards[].chunk.transactions[]? - | select(.transaction.receiver_id == $target)] | length), - incoming_receipt_count: ([.shards[].chunk.receipts[]? - | select(.receiver_id == $target)] | length), - outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? - | select( - (.receipt.receiver_id // "") == $target - or (.execution_outcome.outcome.executor_id // "") == $target - )] | length), - state_change_count: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target)] | length), - state_change_types: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target) - | .type] | unique | sort) - } | . + { - touched: ( - (.direct_tx_count > 0) - or (.incoming_receipt_count > 0) - or (.outcome_hit_count > 0) - or (.state_change_count > 0) - ) - }' -``` - -Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. Здесь закреплён `intents.near`, чтобы первый запуск с высокой вероятностью сразу вернул реальный touched-блок, а уже потом вы сможете подставить свой контракт. - -Блоки NEAR шардингованы, поэтому фильтр проходит по `.shards[]`, прежде чем смотреть транзакции, receipts, outcomes или изменения состояния. `chunk.receipts` означает работу, которая приземлилась в этом блоке; `receipt_execution_outcomes` означает работу, которая исполнилась в этом блоке, даже если была запланирована раньше. - -## Готовое расследование +## Готовые расследования ### Был ли мой контракт затронут в последнем финализированном блоке? -Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC. +Используйте это, когда приложению, боту или инструменту поддержки нужен один быстрый ответ о живом контракте. Мы будем проверять `intents.near`, но та же сводка работает для любого аккаунта контракта. Стратегия - Сначала ответьте на вопрос о contract touch, а затем оставьте только один tx hash или receipt id для следующего шага. - - 01last-block-final даёт одну стабильную высоту блока без угадывания. - 02block — это главный read: он уже содержит транзакции, входящие receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» - 03Только если ответ «да», расширяйтесь дальше: сохраните один точный tx hash или receipt id из того же сохранённого блока, а затем передайте этот идентификатор в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). - -**Цель** - -- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только компактные счётчики плюс один точный идентификатор для следующего шага. - -**Что должен включать полезный ответ** - -- финализированную высоту и хеш -- ответ “затронут / не затронут” -- счётчики прямых транзакций, входящих receipts, outcome-hit и state changes -- компактный список `state_change_types` -- один sample tx hash или receipt id, когда он есть - -### Shell-сценарий от финализированного блока к ответу по contract touch - -Используйте этот сценарий, когда целевой аккаунт уже известен и нужен один свежий финализированный ответ, а не длинный polling-цикл. + Сначала дайте NEAR Data ответить на задачу мониторинга, а уже потом сохраняйте tx hash или receipt ID для следующей поверхности, если это вообще понадобится. -**Что вы делаете** + 01last-block-final находит самую новую финализированную высоту. + 02block даёт один недавний гидратированный документ блока с уже присоединёнными данными по shard. + 03Суммируйте прямые транзакции, входящие receipts, результаты выполнения и state_changes для нужного контракта. Считайте state_changes самым сильным сигналом того, что контракт действительно изменился. -- Получаете redirect target для последнего финализированного блока. -- Один раз загружаете полный документ блока. -- Собираете один компактный ответ по одному `TARGET_ACCOUNT_ID`. -- Получаете ответ “да / нет” плюс минимально полезные счётчики, типы изменений состояния и sample-идентификаторы. +Такой сценарий вполне честно может вернуть `touched: false`, если блок тихий. Это тоже полезный ответ: в самом новом финализированном блоке сейчас нет ничего, что требовало бы более глубокого разбора. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=intents.near +TARGET_CONTRACT=intents.near + +contract_touch_summary() { + jq -r --arg contract "$1" ' + [ .shards[] | { + shard_id, + direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length), + sample_tx_hash: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash] | .[0]), + sample_receipt_id: ( + [ .chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id ] + + [ .receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract) | .execution_outcome.id ] + + [ .state_changes[]? | select(.change.account_id? == $contract) | (.cause.receipt_hash? // empty) ] + | .[0] + ) + } + | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) + ] as $rows + | { + height: .block.header.height, + hash: .block.header.hash, + contract: $contract, + touched: (($rows | length) > 0), + shards: ($rows | map(.shard_id)), + evidence: { + direct_txs: (($rows | map(.direct_txs) | add) // 0), + incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), + execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), + state_changes: (($rows | map(.state_changes) | add) // 0) + }, + sample_tx_hash: ([ $rows[] | .sample_tx_hash | select(.) ] | .[0]), + sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) + }' +} FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -92,196 +62,153 @@ FINAL_LOCATION="$( | tr -d '\r' )" -printf 'Final redirect target: %s\n' "$FINAL_LOCATION" +printf 'Latest finalized block: %s\n' "$FINAL_LOCATION" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-block.json >/dev/null - -jq --arg target "$TARGET_ACCOUNT_ID" ' - ( - [ - .shards[] - | .chunk.transactions[]? - | select(.transaction.receiver_id == $target) - | .transaction.hash - ] - ) as $txs - | ( - [ - .shards[] - | .chunk.receipts[]? - | select(.receiver_id == $target) - | .receipt_id - ] - ) as $receipts - | ( - [ - .shards[] - | .receipt_execution_outcomes[]? - | select( - (.receipt.receiver_id // "") == $target - or (.execution_outcome.outcome.executor_id // "") == $target - ) - | .tx_hash - | select(. != null) - ] - | unique - ) as $outcomes - | ( - [ - .shards[] - | .state_changes[]? - | select((.change.account_id // "") == $target) - | .type - ] - ) as $state_changes - | ( - $state_changes - | unique - | sort - ) as $state_change_types - | { - height: .block.header.height, - hash: .block.header.hash, - touched: ( - ($txs | length) > 0 - or ($receipts | length) > 0 - or ($outcomes | length) > 0 - or ($state_changes | length) > 0 - ), - direct_tx_count: ($txs | length), - incoming_receipt_count: ($receipts | length), - outcome_hit_count: ($outcomes | length), - state_change_count: ($state_changes | length), - state_change_types: $state_change_types, - sample_direct_tx: ($txs[0] // null), - sample_incoming_receipt: ($receipts[0] // null), - sample_outcome_tx_hash: ($outcomes[0] // null) - } -' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json + | tee /tmp/neardata-final-block.json \ + | contract_touch_summary "$TARGET_CONTRACT" ``` -Если позже понадобятся более богатые списки, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. +Читать ответ стоит так: -Типичные `state_change_types` — это `account_update`, `access_key_update`, `data_update` и соответствующие варианты `*_deletion`. Этого часто достаточно, чтобы ещё внутри NEAR Data понять, смотрите ли вы на запись в storage, churn ключей или более широкое изменение аккаунта. +- `touched: false` означает, что самый новый финализированный блок не упомянул и не изменил контракт ни одним из отслеживаемых способов. +- `sample_tx_hash` означает, что у вас уже есть хороший якорь для следующего шага на `/tx`. +- `sample_receipt_id` без tx hash обычно означает, что контракт появился в цепочке через receipts, и NEAR Data уже сэкономила вам более дешёвый этап мониторинга. -#### Необязательное продолжение: Какой tx hash или receipt id разбирать дальше? +### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? -Используйте ту же сохранённую сводку и поднимите один точный идентификатор для следующей поверхности. +Используйте это, когда нужен ранний сигнал по живому контракту, но стабильный ответ всё равно должен пройти через финализированное подтверждение. -```bash -FOLLOW_UP_KIND="$( - jq -r ' - if .sample_direct_tx != null then "tx_hash" - elif .sample_incoming_receipt != null then "receipt_id" - elif .sample_outcome_tx_hash != null then "tx_hash" - else "none" - end - ' /tmp/neardata-touch-summary.json -)" + Стратегия + Используйте один и тот же словарь contract-touch на обеих поверхностях, чтобы сравнение было честным. + + 01last-block-optimistic находит самую новую optimistic-высоту. + 02block-optimistic показывает ранний сигнал для того же контракта. + 03block на той же высоте либо подтверждает то же наблюдение, либо показывает, что finality ещё не догнала. + +Если finality уже догнала, optimistic- и finalized-сводки могут совпасть сразу. Это тоже полезно: ранний сигнал уже попал в стабильную историю. -FOLLOW_UP_VALUE="$( - jq -r ' - .sample_direct_tx - // .sample_incoming_receipt - // .sample_outcome_tx_hash - // empty - ' /tmp/neardata-touch-summary.json +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + +contract_touch_summary() { + jq -r --arg contract "$1" ' + [ .shards[] | { + shard_id, + direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) + } + | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) + ] as $rows + | { + height: .block.header.height, + hash: .block.header.hash, + contract: $contract, + touched: (($rows | length) > 0), + shards: ($rows | map(.shard_id)), + evidence: { + direct_txs: (($rows | map(.direct_txs) | add) // 0), + incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), + execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), + state_changes: (($rows | map(.state_changes) | add) // 0) + } + }' +} + +OPT_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' )" -printf 'Next identifier kind: %s\n' "$FOLLOW_UP_KIND" -printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" -``` +OPT_HEIGHT="${OPT_LOCATION##*/}" -Если идентификатор — это `tx_hash`, передайте его в [Transactions API](https://docs.fastnear.com/ru/tx) или RPC `tx` status. Если это `receipt_id`, передайте его в [Transactions API: Receipt by ID](https://docs.fastnear.com/ru/tx/receipt). И только после этого решайте, нужен ли вам вообще shard-level follow-up. +printf 'Latest optimistic block: %s\n' "$OPT_LOCATION" -**Зачем нужен следующий шаг?** +curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" \ + | tee /tmp/neardata-optimistic-block.json \ + | contract_touch_summary "$TARGET_CONTRACT" -Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один точный tx hash или receipt id уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. +curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT" \ + | tee /tmp/neardata-final-same-height.json >/dev/null -### Насколько optimistic head опережает final прямо сейчас? +if jq -e 'type == "null"' /tmp/neardata-final-same-height.json >/dev/null; then + printf 'Finalized block %s is not available yet; finality has not caught up.\n' "$OPT_HEIGHT" +else + printf 'Finalized block %s is already available; compare the stable answer below.\n' "$OPT_HEIGHT" + contract_touch_summary "$TARGET_CONTRACT" < /tmp/neardata-final-same-height.json +fi +``` -Используйте это, когда нужно выбрать между low-latency read и settled read ещё до запуска polling. +Практический вывод такой: -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz +- optimistic — это ранний сигнал, на который цикл мониторинга может быстро отреагировать; +- finalized — это стабильный ответ, который уже можно показывать пользователям или использовать в устойчивой автоматизации. -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" +### Какой shard действительно изменил мой контракт в этом блоке? -OPTIMISTIC_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" +Используйте это, когда недавний блок уже показал активность контракта, и теперь нужно доказательство на уровне shard того, где именно изменение реально приземлилось. -jq -n \ - --arg final_location "$FINAL_LOCATION" \ - --arg optimistic_location "$OPTIMISTIC_LOCATION" '{ - final_location: $final_location, - optimistic_location: $optimistic_location, - final_height: ($final_location | split("/") | last | tonumber), - optimistic_height: ($optimistic_location | split("/") | last | tonumber) - } | . + { - optimistic_minus_final: (.optimistic_height - .final_height) - }' -``` + Стратегия + Сначала используйте весь блок, чтобы найти нужный shard, а затем дайте block-shard доказать само изменение. -Используйте `last_block/optimistic`, когда приложению важнее скорость, чем settled finality, например для реактивных status-view или ранних алертов. Используйте `last_block/final`, когда ответ пойдёт в accounting, reconciliation или любой workflow, который не должен откатываться назад. + 01Просканируйте список shard внутри финализированного блока и найдите state_changes по вашему контракту. + 02Откройте только тот shard, который действительно изменил контракт. + 03Сохраните совпадающие state_changes и нужные результаты исполнения как доказательство на уровне shard. -### Как идти вперёд блок за блоком? +На момент написания недавний финализированный блок `194727131` дал чистый живой пример для `intents.near`: контракт сначала появился как входящий receipt на shard `8`, а затем действительно выполнился и изменил состояние на shard `7`. -Используйте этот шаблон, когда задача звучит как «начать с высоты N, получить блок, обработать его, увеличить высоту и повторить». Для детерминированной стартовой точки один раз прочитайте [`first-block`](https://docs.fastnear.com/ru/neardata/first-block), а затем идите вперёд. +Если для вашей задачи нужен более свежий блок, переиспользуйте ту же сводку из первого примера на нескольких соседних финализированных высотах, а затем подставьте найденную высоту в тот же вызов `block-shard`. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -FINAL_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | awk -F/ '{print $4}')" -NEXT_HEIGHT=$((FINAL_HEIGHT + 1)) - -while true; do - HTTP_CODE="$( - curl -s -o /tmp/neardata-next-block.json -w '%{http_code}' \ - "$NEARDATA_BASE_URL/v0/block/$NEXT_HEIGHT" - )" - - if [ "$HTTP_CODE" = "200" ]; then - jq '{height: .block.header.height, hash: .block.header.hash}' \ - /tmp/neardata-next-block.json - NEXT_HEIGHT=$((NEXT_HEIGHT + 1)) - elif [ "$HTTP_CODE" = "404" ]; then - sleep 2 - else - printf 'Unexpected status: %s\n' "$HTTP_CODE" >&2 - break - fi -done +TARGET_CONTRACT=intents.near +EXAMPLE_HEIGHT=194727131 + +curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT" \ + | tee /tmp/neardata-block-194727131.json \ + | jq --arg contract "$TARGET_CONTRACT" '[ + .shards[] | { + shard_id, + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) + } + | select(.incoming_receipts + .execution_outcomes + .state_changes > 0) + ]' + +curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT/shard/7" \ + | jq --arg contract "$TARGET_CONTRACT" '{ + shard_id, + chunk_hash: .chunk.header.chunk_hash, + matching_state_changes: [ + .state_changes[] + | select(.change.account_id? == $contract) + | {type, cause, account_id: .change.account_id} + ][0:2], + matching_execution_outcomes: [ + .receipt_execution_outcomes[] + | select(.execution_outcome.outcome.executor_id == $contract) + | { + receipt_id: .execution_outcome.id, + executor_id: .execution_outcome.outcome.executor_id, + status: .execution_outcome.outcome.status, + predecessor_id: .receipt.predecessor_id + } + ][0:2] + }' ``` -Это каноническая форма polling для финализированных данных: получить блок по высоте, обработать один блок, перейти к следующему и трактовать `404` как «ещё не финализирован, подождите и повторите». Если нужен тот же цикл на optimistic-скорости, переключитесь на `/v0/block_opt/` и примите optimistic semantics вместо final. - -## Частые ошибки +Практическое правило здесь простое: -- Воспринимать NEAR Data как push-стрим, а не как polling- или point-read API. -- Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. -- Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. -- Использовать optimistic-данные для settled accounting или reconciliation. -- Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. -- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data один точный tx hash или receipt id. +- используйте `block`, когда первый вопрос звучит как «какой shard вообще важен?»; +- используйте `block-shard`, когда настоящий вопрос уже стал таким: «покажи мне сам payload shard, который изменил состояние». -## Полезные связанные страницы +## Когда пора расширять поверхность -- [NEAR Data API](https://docs.fastnear.com/ru/neardata) -- [Transactions API](https://docs.fastnear.com/ru/tx) -- [RPC Reference](https://docs.fastnear.com/ru/rpc) -- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) -- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +- Используйте [Transactions API](https://docs.fastnear.com/ru/tx), когда у вас уже есть `tx_hash` и нужен человекочитаемый рассказ о транзакции. +- Используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. +- Используйте [Block Headers](https://docs.fastnear.com/ru/neardata/block-headers), когда нужна только динамика head/finality, а не проверка contract-touch. diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index 163c9b8..22548d4 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -1,90 +1,60 @@ **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) -## Быстрый старт +NEAR Data особенно хороша там, где вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. -Начните с одного недавнего финализированного блока и сначала запросите самую маленькую возможную touch-сводку. - -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=intents.near - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | jq --arg target "$TARGET_ACCOUNT_ID" '{ - height: .block.header.height, - hash: .block.header.hash, - direct_tx_count: ([.shards[].chunk.transactions[]? - | select(.transaction.receiver_id == $target)] | length), - incoming_receipt_count: ([.shards[].chunk.receipts[]? - | select(.receiver_id == $target)] | length), - outcome_hit_count: ([.shards[].receipt_execution_outcomes[]? - | select( - (.receipt.receiver_id // "") == $target - or (.execution_outcome.outcome.executor_id // "") == $target - )] | length), - state_change_count: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target)] | length), - state_change_types: ([.shards[].state_changes[]? - | select((.change.account_id // "") == $target) - | .type] | unique | sort) - } | . + { - touched: ( - (.direct_tx_count > 0) - or (.incoming_receipt_count > 0) - or (.outcome_hit_count > 0) - or (.state_change_count > 0) - ) - }' -``` - -Это самая маленькая полезная сводка NEAR Data для команды приложения: один финализированный блок, один ответ “да / нет” и несколько счётчиков до того, как вы начнёте расширяться дальше. Здесь закреплён `intents.near`, чтобы первый запуск с высокой вероятностью сразу вернул реальный touched-блок, а уже потом вы сможете подставить свой контракт. - -Блоки NEAR шардингованы, поэтому фильтр проходит по `.shards[]`, прежде чем смотреть транзакции, receipts, outcomes или изменения состояния. `chunk.receipts` означает работу, которая приземлилась в этом блоке; `receipt_execution_outcomes` означает работу, которая исполнилась в этом блоке, даже если была запланирована раньше. - -## Готовое расследование +## Готовые расследования ### Был ли мой контракт затронут в последнем финализированном блоке? -Используйте это расследование, когда вам нужен конкретный ответ “да / нет” ещё до перехода к Transactions API или RPC. +Используйте это, когда приложению, боту или инструменту поддержки нужен один быстрый ответ о живом контракте. Мы будем проверять `intents.near`, но та же сводка работает для любого аккаунта контракта. Стратегия - Сначала ответьте на вопрос о contract touch, а затем оставьте только один tx hash или receipt id для следующего шага. - - 01last-block-final даёт одну стабильную высоту блока без угадывания. - 02block — это главный read: он уже содержит транзакции, входящие receipts, результаты исполнения receipts и изменения состояния, которых достаточно для ответа на вопрос «был ли контракт затронут?» - 03Только если ответ «да», расширяйтесь дальше: сохраните один точный tx hash или receipt id из того же сохранённого блока, а затем передайте этот идентификатор в [Transactions API](https://docs.fastnear.com/ru/tx) или [RPC Reference](https://docs.fastnear.com/ru/rpc). - -**Цель** - -- Определить, был ли один целевой контракт затронут в последнем финализированном блоке, и оставить только компактные счётчики плюс один точный идентификатор для следующего шага. - -**Что должен включать полезный ответ** - -- финализированную высоту и хеш -- ответ “затронут / не затронут” -- счётчики прямых транзакций, входящих receipts, outcome-hit и state changes -- компактный список `state_change_types` -- один sample tx hash или receipt id, когда он есть - -### Shell-сценарий от финализированного блока к ответу по contract touch - -Используйте этот сценарий, когда целевой аккаунт уже известен и нужен один свежий финализированный ответ, а не длинный polling-цикл. + Сначала дайте NEAR Data ответить на задачу мониторинга, а уже потом сохраняйте tx hash или receipt ID для следующей поверхности, если это вообще понадобится. -**Что вы делаете** + 01last-block-final находит самую новую финализированную высоту. + 02block даёт один недавний гидратированный документ блока с уже присоединёнными данными по shard. + 03Суммируйте прямые транзакции, входящие receipts, результаты выполнения и state_changes для нужного контракта. Считайте state_changes самым сильным сигналом того, что контракт действительно изменился. -- Получаете redirect target для последнего финализированного блока. -- Один раз загружаете полный документ блока. -- Собираете один компактный ответ по одному `TARGET_ACCOUNT_ID`. -- Получаете ответ “да / нет” плюс минимально полезные счётчики, типы изменений состояния и sample-идентификаторы. +Такой сценарий вполне честно может вернуть `touched: false`, если блок тихий. Это тоже полезный ответ: в самом новом финализированном блоке сейчас нет ничего, что требовало бы более глубокого разбора. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_ACCOUNT_ID=intents.near +TARGET_CONTRACT=intents.near + +contract_touch_summary() { + jq -r --arg contract "$1" ' + [ .shards[] | { + shard_id, + direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length), + sample_tx_hash: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash] | .[0]), + sample_receipt_id: ( + [ .chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id ] + + [ .receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract) | .execution_outcome.id ] + + [ .state_changes[]? | select(.change.account_id? == $contract) | (.cause.receipt_hash? // empty) ] + | .[0] + ) + } + | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) + ] as $rows + | { + height: .block.header.height, + hash: .block.header.hash, + contract: $contract, + touched: (($rows | length) > 0), + shards: ($rows | map(.shard_id)), + evidence: { + direct_txs: (($rows | map(.direct_txs) | add) // 0), + incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), + execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), + state_changes: (($rows | map(.state_changes) | add) // 0) + }, + sample_tx_hash: ([ $rows[] | .sample_tx_hash | select(.) ] | .[0]), + sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) + }' +} FINAL_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ @@ -92,196 +62,153 @@ FINAL_LOCATION="$( | tr -d '\r' )" -printf 'Final redirect target: %s\n' "$FINAL_LOCATION" +printf 'Latest finalized block: %s\n' "$FINAL_LOCATION" curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-block.json >/dev/null - -jq --arg target "$TARGET_ACCOUNT_ID" ' - ( - [ - .shards[] - | .chunk.transactions[]? - | select(.transaction.receiver_id == $target) - | .transaction.hash - ] - ) as $txs - | ( - [ - .shards[] - | .chunk.receipts[]? - | select(.receiver_id == $target) - | .receipt_id - ] - ) as $receipts - | ( - [ - .shards[] - | .receipt_execution_outcomes[]? - | select( - (.receipt.receiver_id // "") == $target - or (.execution_outcome.outcome.executor_id // "") == $target - ) - | .tx_hash - | select(. != null) - ] - | unique - ) as $outcomes - | ( - [ - .shards[] - | .state_changes[]? - | select((.change.account_id // "") == $target) - | .type - ] - ) as $state_changes - | ( - $state_changes - | unique - | sort - ) as $state_change_types - | { - height: .block.header.height, - hash: .block.header.hash, - touched: ( - ($txs | length) > 0 - or ($receipts | length) > 0 - or ($outcomes | length) > 0 - or ($state_changes | length) > 0 - ), - direct_tx_count: ($txs | length), - incoming_receipt_count: ($receipts | length), - outcome_hit_count: ($outcomes | length), - state_change_count: ($state_changes | length), - state_change_types: $state_change_types, - sample_direct_tx: ($txs[0] // null), - sample_incoming_receipt: ($receipts[0] // null), - sample_outcome_tx_hash: ($outcomes[0] // null) - } -' /tmp/neardata-block.json | tee /tmp/neardata-touch-summary.json + | tee /tmp/neardata-final-block.json \ + | contract_touch_summary "$TARGET_CONTRACT" ``` -Если позже понадобятся более богатые списки, продолжайте использовать `/tmp/neardata-block.json`. Смысл первого прохода в том, чтобы сначала ответить на вопрос «затронут или нет?», а уже потом расширяться до длинных массивов или более глубокого trace. +Читать ответ стоит так: -Типичные `state_change_types` — это `account_update`, `access_key_update`, `data_update` и соответствующие варианты `*_deletion`. Этого часто достаточно, чтобы ещё внутри NEAR Data понять, смотрите ли вы на запись в storage, churn ключей или более широкое изменение аккаунта. +- `touched: false` означает, что самый новый финализированный блок не упомянул и не изменил контракт ни одним из отслеживаемых способов. +- `sample_tx_hash` означает, что у вас уже есть хороший якорь для следующего шага на `/tx`. +- `sample_receipt_id` без tx hash обычно означает, что контракт появился в цепочке через receipts, и NEAR Data уже сэкономила вам более дешёвый этап мониторинга. -#### Необязательное продолжение: Какой tx hash или receipt id разбирать дальше? +### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? -Используйте ту же сохранённую сводку и поднимите один точный идентификатор для следующей поверхности. +Используйте это, когда нужен ранний сигнал по живому контракту, но стабильный ответ всё равно должен пройти через финализированное подтверждение. -```bash -FOLLOW_UP_KIND="$( - jq -r ' - if .sample_direct_tx != null then "tx_hash" - elif .sample_incoming_receipt != null then "receipt_id" - elif .sample_outcome_tx_hash != null then "tx_hash" - else "none" - end - ' /tmp/neardata-touch-summary.json -)" + Стратегия + Используйте один и тот же словарь contract-touch на обеих поверхностях, чтобы сравнение было честным. + + 01last-block-optimistic находит самую новую optimistic-высоту. + 02block-optimistic показывает ранний сигнал для того же контракта. + 03block на той же высоте либо подтверждает то же наблюдение, либо показывает, что finality ещё не догнала. + +Если finality уже догнала, optimistic- и finalized-сводки могут совпасть сразу. Это тоже полезно: ранний сигнал уже попал в стабильную историю. -FOLLOW_UP_VALUE="$( - jq -r ' - .sample_direct_tx - // .sample_incoming_receipt - // .sample_outcome_tx_hash - // empty - ' /tmp/neardata-touch-summary.json +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + +contract_touch_summary() { + jq -r --arg contract "$1" ' + [ .shards[] | { + shard_id, + direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) + } + | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) + ] as $rows + | { + height: .block.header.height, + hash: .block.header.hash, + contract: $contract, + touched: (($rows | length) > 0), + shards: ($rows | map(.shard_id)), + evidence: { + direct_txs: (($rows | map(.direct_txs) | add) // 0), + incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), + execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), + state_changes: (($rows | map(.state_changes) | add) // 0) + } + }' +} + +OPT_LOCATION="$( + curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + | awk 'tolower($1) == "location:" {print $2}' \ + | tr -d '\r' )" -printf 'Next identifier kind: %s\n' "$FOLLOW_UP_KIND" -printf 'Next identifier value: %s\n' "$FOLLOW_UP_VALUE" -``` +OPT_HEIGHT="${OPT_LOCATION##*/}" -Если идентификатор — это `tx_hash`, передайте его в [Transactions API](https://docs.fastnear.com/ru/tx) или RPC `tx` status. Если это `receipt_id`, передайте его в [Transactions API: Receipt by ID](https://docs.fastnear.com/ru/tx/receipt). И только после этого решайте, нужен ли вам вообще shard-level follow-up. +printf 'Latest optimistic block: %s\n' "$OPT_LOCATION" -**Зачем нужен следующий шаг?** +curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" \ + | tee /tmp/neardata-optimistic-block.json \ + | contract_touch_summary "$TARGET_CONTRACT" -Так вопрос остаётся максимально маленьким: сначала вы отвечаете «был ли затронут мой контракт?», а затем расширяетесь только тогда, когда один точный tx hash или receipt id уже оправдывает более глубокий trace. Здесь NEAR Data выступает как discovery-layer, а не просто как block monitor. +curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT" \ + | tee /tmp/neardata-final-same-height.json >/dev/null -### Насколько optimistic head опережает final прямо сейчас? +if jq -e 'type == "null"' /tmp/neardata-final-same-height.json >/dev/null; then + printf 'Finalized block %s is not available yet; finality has not caught up.\n' "$OPT_HEIGHT" +else + printf 'Finalized block %s is already available; compare the stable answer below.\n' "$OPT_HEIGHT" + contract_touch_summary "$TARGET_CONTRACT" < /tmp/neardata-final-same-height.json +fi +``` -Используйте это, когда нужно выбрать между low-latency read и settled read ещё до запуска polling. +Практический вывод такой: -```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz +- optimistic — это ранний сигнал, на который цикл мониторинга может быстро отреагировать; +- finalized — это стабильный ответ, который уже можно показывать пользователям или использовать в устойчивой автоматизации. -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" +### Какой shard действительно изменил мой контракт в этом блоке? -OPTIMISTIC_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" +Используйте это, когда недавний блок уже показал активность контракта, и теперь нужно доказательство на уровне shard того, где именно изменение реально приземлилось. -jq -n \ - --arg final_location "$FINAL_LOCATION" \ - --arg optimistic_location "$OPTIMISTIC_LOCATION" '{ - final_location: $final_location, - optimistic_location: $optimistic_location, - final_height: ($final_location | split("/") | last | tonumber), - optimistic_height: ($optimistic_location | split("/") | last | tonumber) - } | . + { - optimistic_minus_final: (.optimistic_height - .final_height) - }' -``` + Стратегия + Сначала используйте весь блок, чтобы найти нужный shard, а затем дайте block-shard доказать само изменение. -Используйте `last_block/optimistic`, когда приложению важнее скорость, чем settled finality, например для реактивных status-view или ранних алертов. Используйте `last_block/final`, когда ответ пойдёт в accounting, reconciliation или любой workflow, который не должен откатываться назад. + 01Просканируйте список shard внутри финализированного блока и найдите state_changes по вашему контракту. + 02Откройте только тот shard, который действительно изменил контракт. + 03Сохраните совпадающие state_changes и нужные результаты исполнения как доказательство на уровне shard. -### Как идти вперёд блок за блоком? +На момент написания недавний финализированный блок `194727131` дал чистый живой пример для `intents.near`: контракт сначала появился как входящий receipt на shard `8`, а затем действительно выполнился и изменил состояние на shard `7`. -Используйте этот шаблон, когда задача звучит как «начать с высоты N, получить блок, обработать его, увеличить высоту и повторить». Для детерминированной стартовой точки один раз прочитайте [`first-block`](https://docs.fastnear.com/ru/neardata/first-block), а затем идите вперёд. +Если для вашей задачи нужен более свежий блок, переиспользуйте ту же сводку из первого примера на нескольких соседних финализированных высотах, а затем подставьте найденную высоту в тот же вызов `block-shard`. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz - -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" - -FINAL_HEIGHT="$(printf '%s' "$FINAL_LOCATION" | awk -F/ '{print $4}')" -NEXT_HEIGHT=$((FINAL_HEIGHT + 1)) - -while true; do - HTTP_CODE="$( - curl -s -o /tmp/neardata-next-block.json -w '%{http_code}' \ - "$NEARDATA_BASE_URL/v0/block/$NEXT_HEIGHT" - )" - - if [ "$HTTP_CODE" = "200" ]; then - jq '{height: .block.header.height, hash: .block.header.hash}' \ - /tmp/neardata-next-block.json - NEXT_HEIGHT=$((NEXT_HEIGHT + 1)) - elif [ "$HTTP_CODE" = "404" ]; then - sleep 2 - else - printf 'Unexpected status: %s\n' "$HTTP_CODE" >&2 - break - fi -done +TARGET_CONTRACT=intents.near +EXAMPLE_HEIGHT=194727131 + +curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT" \ + | tee /tmp/neardata-block-194727131.json \ + | jq --arg contract "$TARGET_CONTRACT" '[ + .shards[] | { + shard_id, + incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), + execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), + state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) + } + | select(.incoming_receipts + .execution_outcomes + .state_changes > 0) + ]' + +curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT/shard/7" \ + | jq --arg contract "$TARGET_CONTRACT" '{ + shard_id, + chunk_hash: .chunk.header.chunk_hash, + matching_state_changes: [ + .state_changes[] + | select(.change.account_id? == $contract) + | {type, cause, account_id: .change.account_id} + ][0:2], + matching_execution_outcomes: [ + .receipt_execution_outcomes[] + | select(.execution_outcome.outcome.executor_id == $contract) + | { + receipt_id: .execution_outcome.id, + executor_id: .execution_outcome.outcome.executor_id, + status: .execution_outcome.outcome.status, + predecessor_id: .receipt.predecessor_id + } + ][0:2] + }' ``` -Это каноническая форма polling для финализированных данных: получить блок по высоте, обработать один блок, перейти к следующему и трактовать `404` как «ещё не финализирован, подождите и повторите». Если нужен тот же цикл на optimistic-скорости, переключитесь на `/v0/block_opt/` и примите optimistic semantics вместо final. - -## Частые ошибки +Практическое правило здесь простое: -- Воспринимать NEAR Data как push-стрим, а не как polling- или point-read API. -- Начинать с RPC, не проверив, не отвечает ли уже один финализированный блок на вопрос о контракте. -- Смотреть только на прямые транзакции и забывать, что контракты часто затрагиваются через receipts или state changes. -- Использовать optimistic-данные для settled accounting или reconciliation. -- Предполагать, что сначала нужно проверить какой-то заранее выбранный shard id, а не само семейство блока. -- Переходить к Transactions API или RPC до того, как вы извлекли из NEAR Data один точный tx hash или receipt id. +- используйте `block`, когда первый вопрос звучит как «какой shard вообще важен?»; +- используйте `block-shard`, когда настоящий вопрос уже стал таким: «покажи мне сам payload shard, который изменил состояние». -## Полезные связанные страницы +## Когда пора расширять поверхность -- [NEAR Data API](https://docs.fastnear.com/ru/neardata) -- [Transactions API](https://docs.fastnear.com/ru/tx) -- [RPC Reference](https://docs.fastnear.com/ru/rpc) -- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) -- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +- Используйте [Transactions API](https://docs.fastnear.com/ru/tx), когда у вас уже есть `tx_hash` и нужен человекочитаемый рассказ о транзакции. +- Используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. +- Используйте [Block Headers](https://docs.fastnear.com/ru/neardata/block-headers), когда нужна только динамика head/finality, а не проверка contract-touch. diff --git a/static/ru/neardata/index.md b/static/ru/neardata/index.md index f46f3cf..2361dfc 100644 --- a/static/ru/neardata/index.md +++ b/static/ru/neardata/index.md @@ -2,7 +2,7 @@ # NEAR Data API -NEAR Data API — это поверхность для чтения данных почти в реальном времени, а также по семействам блоков. Используйте её, когда нужны свежие срезы блоков, вспомогательные маршруты с перенаправлением или недавние финализированные и оптимистичные чтения, но без позиционирования продукта как потокового сервиса. +NEAR Data API — это поверхность для недавних блоков и шардов. Используйте её, когда нужны свежие срезы блоков, мониторинг активности контракта, вспомогательные маршруты с перенаправлением или сравнение оптимистичных и финализированных чтений без превращения продукта в потоковый API. ## Базовые URL @@ -17,8 +17,9 @@ https://testnet.neardata.xyz ## Лучше всего подходит для - опроса недавних финализированных и оптимистичных блоков; -- вспомогательных маршрутов по блокам и сценариев с перенаправлением; -- лёгких проверок свежести данных и мониторинга. +- обнаружения того, появился ли живой контракт в недавнем блоке и изменил ли он состояние; +- сравнения оптимистичного сигнала с финализированным подтверждением; +- проверки одного недавнего шарда, когда уже известно, какой блок важен. ## Когда его не стоит использовать @@ -33,13 +34,14 @@ https://testnet.neardata.xyz ## С чего обычно начинают -- [Оптимистичный блок](https://docs.fastnear.com/ru/neardata/block-optimistic) — для самого свежего опроса блоков. -- [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block) и [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers) — для запросов по финализированным блокам. -- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) и [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) — когда нужны вспомогательные маршруты с перенаправлением. +- [Перенаправление на последний финализированный блок](https://docs.fastnear.com/ru/neardata/last-block-final) и [Перенаправление на последний оптимистичный блок](https://docs.fastnear.com/ru/neardata/last-block-optimistic) — когда нужно быстро узнать самый новый недавний блок. +- [Финализированный блок по высоте](https://docs.fastnear.com/ru/neardata/block) — когда нужен один недавний гидратированный блок с уже приложенными данными по шардам. +- [Шард блока](https://docs.fastnear.com/ru/neardata/block-shard) — когда недавний блок уже показал нужный шард и его надо разобрать глубже. +- [Заголовки блока](https://docs.fastnear.com/ru/neardata/block-headers) — когда важнее движение головы цепочки и финальности, а не широкий блоковый документ. ## Нужен сценарий? -Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для простых пошаговых сценариев: опроса оптимистичных блоков, подтверждения финализированных блоков, работы с перенаправлениями и перехода к каноническому RPC. +Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для компактных сценариев: обнаружения активности контракта, сравнения оптимистичных и финализированных наблюдений, а также проверки изменений на уровне шарда. ## Устранение неполадок diff --git a/static/ru/transfers/examples.md b/static/ru/transfers/examples.md index 452fcc3..463acd3 100644 --- a/static/ru/transfers/examples.md +++ b/static/ru/transfers/examples.md @@ -1,246 +1,153 @@ **Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) -## Быстрый старт - -Начните с одного отфильтрованного входящего запроса и сразу выведите поля, ради которых вообще стоит использовать Transfers API. - -```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=intents.near -ASSET_ID=native:near -MIN_AMOUNT_YOCTO=1000000000000000000000000 - -curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - ignore_system: true, - min_amount: $min_amount, - desc: true, - limit: 5 - }')" \ - | jq '{ - resume_token, - transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - asset_id, - amount, - human_amount: ( - if .human_amount == null then null - else (.human_amount * 1000 | round / 1000) - end - ), - usd_amount: ( - if .usd_amount == null then null - else (.usd_amount * 100 | round / 100) - end - ), - block_timestamp, - method_name, - transfer_type, - start_of_block_balance, - end_of_block_balance, - other_account_id, - block_height - } - ] - }' -``` - -Это самый короткий путь к вопросу «какие переводы от 1+ NEAR пришли на этот аккаунт, чего они стоили и какую строку стоит разбирать дальше?» `usd_amount` может быть `null`, если для этой строки нет ценового покрытия. - ## Готовый сценарий -### Какие входящие переводы от 1+ NEAR попали на этот аккаунт и какую строку стоит разобрать? +### Отфильтровать и листать ленту переводов одного аккаунта -Используйте этот сценарий, когда история звучит так: «сначала мне нужен один узкий поиск переводов, я хочу поля, которые уже похожи на wallet- или analytics-данные, и только после этого решу, нужна ли одной строке более глубокая расшифровка». +Используйте этот сценарий, когда история звучит так: «покажи мне осмысленную ленту переводов этого аккаунта, дай мне спокойно листать её дальше, и только потом, если нужно, помоги догнать одну строку до истории исполнения». Стратегия - Сначала используйте Transfers API ради отфильтрованного ответа о движении средств, а расширяйтесь только если одной строке всё ещё нужен chain-контекст. + Сначала соберите саму ленту аккаунта, а `receipt` поднимайте только тогда, когда одна строка действительно требует истории исполнения. - 01POST /v0/transfers сначала делает всю фильтрацию: входящая сторона, один asset, скрытие system-переводов и порог по минимальной сумме. - 02Сначала выведите отличительные поля строки: human_amount, usd_amount, method_name, transfer_type и running balances. - 03Если нужны ещё строки, переиспользуйте непрозрачный resume_token с точно теми же фильтрами. - 04И только потом выбирайте одну строку и решайте, нужен ли вам её receipt_id как execution-anchor или её transaction_id как якорь для читаемой истории. + 01POST /v0/transfers даёт первую страницу отфильтрованной ленты одного аккаунта. + 02jq поднимает сами строки плюс resume_token, чтобы вы могли продолжать листать ту же ленту. + 03POST /v0/receipt — это уже необязательный следующий шаг, если одной строке нужна её история исполнения. + +**Сеть** + +- только mainnet **Что вы делаете** -- Запрашиваете отфильтрованное окно входящих переводов для одного активного mainnet-аккаунта. -- Сначала печатаете поля строки, которые Transfers API уже нормализует за вас. -- Переиспользуете тот же `resume_token`, если вам нужна следующая страница. -- Поднимаете либо `receipt_id`, либо `transaction_id` только тогда, когда одной строке всё ещё нужна более глубокая история. +- Забираете первую страницу одной отфильтрованной ленты переводов для выбранного аккаунта. +- Используете сами параметры ленты как главный учебный материал: `account_id`, `direction`, `asset_id`, `min_amount`, `desc` и `limit`. +- Сначала смотрите на строки ответа и `resume_token`, а не прыгаете сразу в историю исполнения. +- Только если какая-то строка действительно требует дополнительной истории, переиспользуете её `receipt_id` в Transactions API. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=intents.near +ACCOUNT_ID=YOUR_ACCOUNT_ID ASSET_ID=native:near -MIN_AMOUNT_YOCTO=1000000000000000000000000 -TRANSFER_INDEX=0 +MIN_AMOUNT=1000000000000000000000000 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" '{ + --arg min_amount "$MIN_AMOUNT" '{ account_id: $account_id, direction: "receiver", asset_id: $asset_id, - ignore_system: true, min_amount: $min_amount, desc: true, - limit: 5 + limit: 10 }')" \ - | tee /tmp/transfers-window.json >/dev/null + | tee /tmp/transfers-feed.json >/dev/null jq '{ resume_token, transfers: [ - .transfers - | to_entries[] + .transfers[] | { - transfer_index: .key, - transaction_id: .value.transaction_id, - receipt_id: .value.receipt_id, - asset_id: .value.asset_id, - amount: .value.amount, - human_amount: ( - if .value.human_amount == null then null - else (.value.human_amount * 1000 | round / 1000) - end - ), - usd_amount: ( - if .value.usd_amount == null then null - else (.value.usd_amount * 100 | round / 100) - end - ), - block_timestamp: .value.block_timestamp, - method_name: .value.method_name, - transfer_type: .value.transfer_type, - start_of_block_balance: .value.start_of_block_balance, - end_of_block_balance: .value.end_of_block_balance, - other_account_id: .value.other_account_id, - block_height: .value.block_height + transaction_id, + receipt_id, + asset_id, + amount, + human_amount, + usd_amount, + other_account_id, + block_height } ] -}' /tmp/transfers-window.json - -RESUME_TOKEN="$( - jq -r '.resume_token // empty' /tmp/transfers-window.json -)" - -if [ -n "$RESUME_TOKEN" ]; then - curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" \ - --arg resume_token "$RESUME_TOKEN" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - ignore_system: true, - min_amount: $min_amount, - desc: true, - limit: 5, - resume_token: $resume_token - }')" \ - | jq '{ - next_page_resume_token: .resume_token, - next_transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - human_amount: ( - if .human_amount == null then null - else (.human_amount * 1000 | round / 1000) - end - ), - transfer_type, - other_account_id, - block_height - } - ] - }' -fi - -TRANSACTION_ID="$( - jq -r --argjson transfer_index "$TRANSFER_INDEX" \ - '.transfers[$transfer_index].transaction_id // empty' \ - /tmp/transfers-window.json -)" - -RECEIPT_ID="$( - jq -r --argjson transfer_index "$TRANSFER_INDEX" \ - '.transfers[$transfer_index].receipt_id // empty' \ - /tmp/transfers-window.json -)" - -printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" -printf 'Chosen transaction id: %s\n' "$TRANSACTION_ID" -printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" +}' /tmp/transfers-feed.json ``` -Этим вы отвечаете на первый вопрос: какие отфильтрованные строки совпали, чего они стоили и какую строку перевода стоит разбирать дальше? - -#### Необязательное продолжение: execution-anchor или transaction-story? - -Используйте `receipt_id`, когда нужен execution-anchor именно для этой строки. Используйте `transaction_id`, когда нужна читаемая история того, что именно подписал signer. +Необязательный следующий шаг: если одной строке всё-таки нужна её точка исполнения, поднимите её `receipt_id` и один раз перейдите в Transactions API. ```bash -if [ -n "$RECEIPT_ID" ]; then - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' -fi - -if [ -n "$TRANSACTION_ID" ]; then - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TRANSACTION_ID" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ) - }' -fi +RECEIPT_ID="$(jq -r '.transfers[0].receipt_id' /tmp/transfers-feed.json)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' ``` **Зачем нужен следующий шаг?** -Именно здесь Transfers API показывает свою ценность. Первый запрос уже отвечает на вопрос о движении средств в терминах, удобных для wallet- и analytics-сценариев: отфильтрованные строки, humanized amount, тип перевода, method-clue и running balances. Если всё ещё нужна следующая страница, переиспользуйте тот же `resume_token` с теми же фильтрами. Если нужен chain-контекст, следуйте по `receipt_id` ради execution-anchor или по `transaction_id` ради читаемой истории транзакции. +Запрос переводов напрямую отвечает на первый вопрос: как сейчас выглядит отфильтрованная лента этого аккаунта и как её продолжать без потери места? Только после того как сама лента подскажет, какая строка действительно важна, имеет смысл переходить по `receipt_id` и забирать историю исполнения из `/tx`. + +## Частые задачи + +### Отфильтровать ленту переводов одного аккаунта + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом и самым узким стабильным набором фильтров для ленты: направление, актив, сумма и порядок. + +**Следующая страница при необходимости** + +- Уточните те же фильтры по активу или сумме, если в первой странице всё ещё слишком много лишних строк. + +**Остановитесь, когда** + +- Уже можно объяснить, как выглядит эта отфильтрованная лента и как листать её дальше. + +**Переходите дальше, когда** + +- Одна конкретная строка уже требует истории исполнения или следа по receipt. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Листать ленту переводов дальше и не потерять своё место + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. + +**Следующая страница при необходимости** + +- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. +- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. + +**Остановитесь, когда** + +- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. + +**Переходите дальше, когда** + +- Пользователь просит метаданные транзакции сверх самих переводов. +- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). + +### Перейти от истории переводов к полному расследованию транзакции + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. + +**Следующая страница при необходимости** + +- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. + +**Остановитесь, когда** + +- Уже определено правильное событие перевода и понятно, какой API открывать следующим. + +**Переходите дальше, когда** + +- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. -- Считать историю переводов полной историей исполнения вместо отфильтрованного movement-view. +- Считать историю переводов полной историей исполнения. - Переиспользовать `resume_token` с другими фильтрами. -- Игнорировать `method_name`, `transfer_type` или running balances, хотя именно из-за них этот API часто удобнее сырой transaction-history. -- Начинать здесь с вопросов про testnet, хотя этот API сегодня работает только в mainnet. ## Полезные связанные страницы diff --git a/static/ru/transfers/examples/index.md b/static/ru/transfers/examples/index.md index 452fcc3..463acd3 100644 --- a/static/ru/transfers/examples/index.md +++ b/static/ru/transfers/examples/index.md @@ -1,246 +1,153 @@ **Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) -## Быстрый старт - -Начните с одного отфильтрованного входящего запроса и сразу выведите поля, ради которых вообще стоит использовать Transfers API. - -```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -ACCOUNT_ID=intents.near -ASSET_ID=native:near -MIN_AMOUNT_YOCTO=1000000000000000000000000 - -curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - ignore_system: true, - min_amount: $min_amount, - desc: true, - limit: 5 - }')" \ - | jq '{ - resume_token, - transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - asset_id, - amount, - human_amount: ( - if .human_amount == null then null - else (.human_amount * 1000 | round / 1000) - end - ), - usd_amount: ( - if .usd_amount == null then null - else (.usd_amount * 100 | round / 100) - end - ), - block_timestamp, - method_name, - transfer_type, - start_of_block_balance, - end_of_block_balance, - other_account_id, - block_height - } - ] - }' -``` - -Это самый короткий путь к вопросу «какие переводы от 1+ NEAR пришли на этот аккаунт, чего они стоили и какую строку стоит разбирать дальше?» `usd_amount` может быть `null`, если для этой строки нет ценового покрытия. - ## Готовый сценарий -### Какие входящие переводы от 1+ NEAR попали на этот аккаунт и какую строку стоит разобрать? +### Отфильтровать и листать ленту переводов одного аккаунта -Используйте этот сценарий, когда история звучит так: «сначала мне нужен один узкий поиск переводов, я хочу поля, которые уже похожи на wallet- или analytics-данные, и только после этого решу, нужна ли одной строке более глубокая расшифровка». +Используйте этот сценарий, когда история звучит так: «покажи мне осмысленную ленту переводов этого аккаунта, дай мне спокойно листать её дальше, и только потом, если нужно, помоги догнать одну строку до истории исполнения». Стратегия - Сначала используйте Transfers API ради отфильтрованного ответа о движении средств, а расширяйтесь только если одной строке всё ещё нужен chain-контекст. + Сначала соберите саму ленту аккаунта, а `receipt` поднимайте только тогда, когда одна строка действительно требует истории исполнения. - 01POST /v0/transfers сначала делает всю фильтрацию: входящая сторона, один asset, скрытие system-переводов и порог по минимальной сумме. - 02Сначала выведите отличительные поля строки: human_amount, usd_amount, method_name, transfer_type и running balances. - 03Если нужны ещё строки, переиспользуйте непрозрачный resume_token с точно теми же фильтрами. - 04И только потом выбирайте одну строку и решайте, нужен ли вам её receipt_id как execution-anchor или её transaction_id как якорь для читаемой истории. + 01POST /v0/transfers даёт первую страницу отфильтрованной ленты одного аккаунта. + 02jq поднимает сами строки плюс resume_token, чтобы вы могли продолжать листать ту же ленту. + 03POST /v0/receipt — это уже необязательный следующий шаг, если одной строке нужна её история исполнения. + +**Сеть** + +- только mainnet **Что вы делаете** -- Запрашиваете отфильтрованное окно входящих переводов для одного активного mainnet-аккаунта. -- Сначала печатаете поля строки, которые Transfers API уже нормализует за вас. -- Переиспользуете тот же `resume_token`, если вам нужна следующая страница. -- Поднимаете либо `receipt_id`, либо `transaction_id` только тогда, когда одной строке всё ещё нужна более глубокая история. +- Забираете первую страницу одной отфильтрованной ленты переводов для выбранного аккаунта. +- Используете сами параметры ленты как главный учебный материал: `account_id`, `direction`, `asset_id`, `min_amount`, `desc` и `limit`. +- Сначала смотрите на строки ответа и `resume_token`, а не прыгаете сразу в историю исполнения. +- Только если какая-то строка действительно требует дополнительной истории, переиспользуете её `receipt_id` в Transactions API. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=intents.near +ACCOUNT_ID=YOUR_ACCOUNT_ID ASSET_ID=native:near -MIN_AMOUNT_YOCTO=1000000000000000000000000 -TRANSFER_INDEX=0 +MIN_AMOUNT=1000000000000000000000000 curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ --data "$(jq -nc \ --arg account_id "$ACCOUNT_ID" \ --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" '{ + --arg min_amount "$MIN_AMOUNT" '{ account_id: $account_id, direction: "receiver", asset_id: $asset_id, - ignore_system: true, min_amount: $min_amount, desc: true, - limit: 5 + limit: 10 }')" \ - | tee /tmp/transfers-window.json >/dev/null + | tee /tmp/transfers-feed.json >/dev/null jq '{ resume_token, transfers: [ - .transfers - | to_entries[] + .transfers[] | { - transfer_index: .key, - transaction_id: .value.transaction_id, - receipt_id: .value.receipt_id, - asset_id: .value.asset_id, - amount: .value.amount, - human_amount: ( - if .value.human_amount == null then null - else (.value.human_amount * 1000 | round / 1000) - end - ), - usd_amount: ( - if .value.usd_amount == null then null - else (.value.usd_amount * 100 | round / 100) - end - ), - block_timestamp: .value.block_timestamp, - method_name: .value.method_name, - transfer_type: .value.transfer_type, - start_of_block_balance: .value.start_of_block_balance, - end_of_block_balance: .value.end_of_block_balance, - other_account_id: .value.other_account_id, - block_height: .value.block_height + transaction_id, + receipt_id, + asset_id, + amount, + human_amount, + usd_amount, + other_account_id, + block_height } ] -}' /tmp/transfers-window.json - -RESUME_TOKEN="$( - jq -r '.resume_token // empty' /tmp/transfers-window.json -)" - -if [ -n "$RESUME_TOKEN" ]; then - curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT_YOCTO" \ - --arg resume_token "$RESUME_TOKEN" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - ignore_system: true, - min_amount: $min_amount, - desc: true, - limit: 5, - resume_token: $resume_token - }')" \ - | jq '{ - next_page_resume_token: .resume_token, - next_transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - human_amount: ( - if .human_amount == null then null - else (.human_amount * 1000 | round / 1000) - end - ), - transfer_type, - other_account_id, - block_height - } - ] - }' -fi - -TRANSACTION_ID="$( - jq -r --argjson transfer_index "$TRANSFER_INDEX" \ - '.transfers[$transfer_index].transaction_id // empty' \ - /tmp/transfers-window.json -)" - -RECEIPT_ID="$( - jq -r --argjson transfer_index "$TRANSFER_INDEX" \ - '.transfers[$transfer_index].receipt_id // empty' \ - /tmp/transfers-window.json -)" - -printf 'Chosen transfer index: %s\n' "$TRANSFER_INDEX" -printf 'Chosen transaction id: %s\n' "$TRANSACTION_ID" -printf 'Chosen receipt id: %s\n' "$RECEIPT_ID" +}' /tmp/transfers-feed.json ``` -Этим вы отвечаете на первый вопрос: какие отфильтрованные строки совпали, чего они стоили и какую строку перевода стоит разбирать дальше? - -#### Необязательное продолжение: execution-anchor или transaction-story? - -Используйте `receipt_id`, когда нужен execution-anchor именно для этой строки. Используйте `transaction_id`, когда нужна читаемая история того, что именно подписал signer. +Необязательный следующий шаг: если одной строке всё-таки нужна её точка исполнения, поднимите её `receipt_id` и один раз перейдите в Transactions API. ```bash -if [ -n "$RECEIPT_ID" ]; then - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' -fi - -if [ -n "$TRANSACTION_ID" ]; then - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TRANSACTION_ID" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ) - }' -fi +RECEIPT_ID="$(jq -r '.transfers[0].receipt_id' /tmp/transfers-feed.json)" + +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt_id: .receipt.receipt_id, + transaction_hash: .receipt.transaction_hash, + receiver_id: .receipt.receiver_id, + tx_block_height: .receipt.tx_block_height + }' ``` **Зачем нужен следующий шаг?** -Именно здесь Transfers API показывает свою ценность. Первый запрос уже отвечает на вопрос о движении средств в терминах, удобных для wallet- и analytics-сценариев: отфильтрованные строки, humanized amount, тип перевода, method-clue и running balances. Если всё ещё нужна следующая страница, переиспользуйте тот же `resume_token` с теми же фильтрами. Если нужен chain-контекст, следуйте по `receipt_id` ради execution-anchor или по `transaction_id` ради читаемой истории транзакции. +Запрос переводов напрямую отвечает на первый вопрос: как сейчас выглядит отфильтрованная лента этого аккаунта и как её продолжать без потери места? Только после того как сама лента подскажет, какая строка действительно важна, имеет смысл переходить по `receipt_id` и забирать историю исполнения из `/tx`. + +## Частые задачи + +### Отфильтровать ленту переводов одного аккаунта + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом и самым узким стабильным набором фильтров для ленты: направление, актив, сумма и порядок. + +**Следующая страница при необходимости** + +- Уточните те же фильтры по активу или сумме, если в первой странице всё ещё слишком много лишних строк. + +**Остановитесь, когда** + +- Уже можно объяснить, как выглядит эта отфильтрованная лента и как листать её дальше. + +**Переходите дальше, когда** + +- Одна конкретная строка уже требует истории исполнения или следа по receipt. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). + +### Листать ленту переводов дальше и не потерять своё место + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. + +**Следующая страница при необходимости** + +- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. +- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. + +**Остановитесь, когда** + +- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. + +**Переходите дальше, когда** + +- Пользователь просит метаданные транзакции сверх самих переводов. +- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). + +### Перейти от истории переводов к полному расследованию транзакции + +**Начните здесь** + +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. + +**Следующая страница при необходимости** + +- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. +- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. + +**Остановитесь, когда** + +- Уже определено правильное событие перевода и понятно, какой API открывать следующим. + +**Переходите дальше, когда** + +- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. ## Частые ошибки - Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. -- Считать историю переводов полной историей исполнения вместо отфильтрованного movement-view. +- Считать историю переводов полной историей исполнения. - Переиспользовать `resume_token` с другими фильтрами. -- Игнорировать `method_name`, `transfer_type` или running balances, хотя именно из-за них этот API часто удобнее сырой transaction-history. -- Начинать здесь с вопросов про testnet, хотя этот API сегодня работает только в mainnet. ## Полезные связанные страницы From 6bdd0a2abea6d3054bbaa26d4658190172f6b43b Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sun, 19 Apr 2026 18:48:36 -0700 Subject: [PATCH 24/35] docs: tighten examples voice and russian parity --- README.md | 14 + docs/api/examples.md | 30 +- docs/api/index.md | 2 +- docs/fastdata/kv/examples.md | 31 +- docs/fastdata/kv/index.md | 2 +- docs/neardata/examples.md | 22 +- docs/neardata/index.md | 2 +- docs/rpc/examples.md | 94 ++--- docs/rpc/index.md | 2 +- docs/snapshots/examples.mdx | 41 +- docs/snapshots/index.mdx | 2 +- docs/transfers/examples.md | 14 +- docs/transfers/index.md | 2 +- docs/tx/examples.md | 206 ++-------- docs/tx/index.md | 2 +- .../current/api/examples.md | 30 +- .../current/api/index.md | 2 +- .../current/fastdata/kv/examples.md | 32 +- .../current/fastdata/kv/index.md | 2 +- .../current/neardata/examples.md | 20 +- .../current/neardata/index.md | 2 +- .../current/rpc/examples.md | 80 ++-- .../current/rpc/index.md | 2 +- .../current/snapshots/examples.mdx | 40 +- .../current/snapshots/index.mdx | 2 +- .../current/transfers/examples.md | 12 +- .../current/transfers/index.md | 2 +- .../current/tx/examples.md | 163 ++------ .../current/tx/index.md | 2 +- static/ru/api.md | 2 +- static/ru/api/examples.md | 28 +- static/ru/api/examples/index.md | 28 +- static/ru/api/index.md | 2 +- static/ru/fastdata/kv.md | 2 +- static/ru/fastdata/kv/examples.md | 30 +- static/ru/fastdata/kv/examples/index.md | 30 +- static/ru/fastdata/kv/index.md | 2 +- static/ru/guides/llms.txt | 14 +- static/ru/llms-full.txt | 377 +++++------------- static/ru/llms.txt | 14 +- static/ru/neardata.md | 2 +- static/ru/neardata/examples.md | 18 +- static/ru/neardata/examples/index.md | 18 +- static/ru/neardata/index.md | 2 +- static/ru/rpc.md | 2 +- static/ru/rpc/examples.md | 78 ++-- static/ru/rpc/examples/index.md | 78 ++-- static/ru/rpc/index.md | 2 +- static/ru/snapshots.md | 2 +- static/ru/snapshots/examples.md | 38 +- static/ru/snapshots/examples/index.md | 38 +- static/ru/snapshots/index.md | 2 +- static/ru/transfers.md | 2 +- static/ru/transfers/examples.md | 10 +- static/ru/transfers/examples/index.md | 10 +- static/ru/transfers/index.md | 2 +- static/ru/tx.md | 2 +- static/ru/tx/examples.md | 161 ++------ static/ru/tx/examples/index.md | 161 ++------ static/ru/tx/index.md | 2 +- 60 files changed, 552 insertions(+), 1464 deletions(-) diff --git a/README.md b/README.md index a3baf35..f2817eb 100644 --- a/README.md +++ b/README.md @@ -268,6 +268,20 @@ RPC source files now live under `docs/rpc//` and publish at `/rpc/
- Strategy + Flow

Resolve identity first, then reuse the same account ID for one readable wallet snapshot.

@@ -28,7 +24,7 @@ Use this when you have a public key first and the next user-facing question is
-**What you're doing** +**Flow** - Resolve the public key to one or more account IDs. - Extract the first matching account ID with `jq`. @@ -60,17 +56,15 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -**Why this next step?** +**When to pivot** The public-key lookup tells you which account you are dealing with. The full account snapshot is the natural next read when you want balances, NFTs, staking, and pools in one response. If the key maps to multiple accounts instead of one, move to [V1 Public Key Lookup All](/api/v1/public-key-all) or loop through each returned `account_id`. ### Does this wallet show direct staking, liquid staking tokens, or both? -Use this when the user story is “show me whether this wallet currently shows direct staking pool positions, liquid staking tokens, or both.” -
- Strategy + Flow

Compare staking positions and FT balances before you interpret the wallet.

@@ -91,7 +85,7 @@ Use this when the user story is “show me whether this wallet currently shows d This example is intentionally observational. It classifies what FastNear can see from staking positions and FT balances today. It does not prove every possible synthetic or off-platform staking exposure. -**What you're doing** +**Flow** - Read indexed direct staking positions from the account staking endpoint. - Read indexed FT balances from the account FT endpoint. @@ -148,17 +142,15 @@ jq -n \ }' ``` -**Why this next step?** +**When to pivot** If the classification is `direct_only`, the next operational question is usually about unstake and withdraw timing. If it is `liquid_only`, the next question is usually about redeeming or swapping the liquid token. If it is `mixed`, you should treat those as two separate exit paths rather than assuming one flow covers both. ### Archive a BOS widget version as a provenance NFT -Use this when the user story is “this BOS widget is a real on-chain artifact. Mint an NFT that records exactly which version I archived.” -
- Strategy + Flow

Read the exact widget first, then mint only after the provenance fields are deterministic.

@@ -179,7 +171,7 @@ Use this when the user story is “this BOS widget is a real on-chain artifact. - [NEP-171 NFT standard](https://docs.near.org/primitives/nft/standard) - [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) -**What you're doing** +**Flow** - Check whether the receiver already holds NFTs from the archive collection. - Read one exact BOS widget from `social.near`, including its widget-level SocialDB block. @@ -363,7 +355,7 @@ jq '{ }' /tmp/bos-widget-provenance-token.json ``` -**Why this next step?** +**When to pivot** FastNear API gives you the quick receiver-side check. Mainnet RPC gives you the exact widget body and SocialDB block. Testnet minting turns that into a durable NFT record. If you later want to prove which historical transaction wrote the widget, hand off to the NEAR Social proof investigations on [Transactions API examples](/tx/examples). diff --git a/docs/api/index.md b/docs/api/index.md index 75b3375..a58a4b0 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -58,7 +58,7 @@ https://test.api.fastnear.com ## Need a workflow? -Use [FastNear API Examples](/api/examples) for plain-language flows like account summaries, key-to-account resolution, and asset-specific follow-up. +Use [FastNear API Examples](/api/examples) for worked examples like account summaries, key-to-account resolution, and asset-specific follow-up. ## Troubleshooting diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index 1b1daec..6534288 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -2,21 +2,19 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: KV FastData Examples -description: Plain-language workflows for checking exact storage keys, following indexed write history, and confirming current state with RPC. +description: Task-first KV FastData examples for scoped writes, key history, and exact state checks. displayed_sidebar: kvFastDataSidebar page_actions: - markdown --- -## Worked investigation +## Example ### Inspect one predecessor’s indexed writes, then narrow to the key that changed -Use this investigation when you know the predecessor first and the real question is “what did this predecessor write, which row is interesting, and what happened to that key afterward?” -
- Strategy + Flow

Start from predecessor scope, narrow to one exact key only after it earns your attention, then use RPC only for the final exact check.

@@ -26,28 +24,9 @@ Use this investigation when you know the predecessor first and the real question
-**Goal** - -- Explain what one predecessor wrote, which exact key became the focus, how that key changed, and whether you even need a canonical `view_state` check at the end. - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Latest indexed rows by scope | KV FastData [`all-by-predecessor`](/fastdata/kv/all-by-predecessor) | Start with the predecessor’s current indexed writes across the contracts it touched | Answers the scope-first question before you pretend the exact key is already known | -| Indexed key history | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) or [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Pull the history for the exact key or keep the predecessor-wide write story broader for one more step | Shows whether the interesting row is stable, recent, or part of a larger write pattern | -| Exact state check | RPC [`view_state`](/rpc/contract/view-state) | Confirm the current on-chain state once the indexed pattern is clear | Separates indexed storage history from the exact state the chain would return now | - -**What a useful answer should include** - -- which predecessor scope you started from -- which exact key became the real focus -- how that key changed in history -- whether a final `view_state` check is still necessary - ### Predecessor-scope shell walkthrough -Use this when one predecessor is already known and you want to move cleanly from “what did this predecessor write?” to “how did this exact key get here?” - -**What you're doing** +**Flow** - Read the latest indexed rows for one predecessor across the contracts it touched. - Lift one interesting `current_account_id` plus exact `key` with `jq`. @@ -96,7 +75,7 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KE }' ``` -**Why this next step?** +**When to pivot** The first lookup answers the scope-first question: what does this predecessor write right now? Narrowing from that feed to one exact key answers the more specific question: how did this row get here? If the write pattern is still broader than one key, stay on [History by Predecessor](/fastdata/kv/history-by-predecessor) a little longer before you switch to exact-key history or RPC. diff --git a/docs/fastdata/kv/index.md b/docs/fastdata/kv/index.md index 9d99bdf..ed4267c 100644 --- a/docs/fastdata/kv/index.md +++ b/docs/fastdata/kv/index.md @@ -82,7 +82,7 @@ Use [FastNear API](/api) for higher-level account views, [NEAR Data API](/nearda ## Need a workflow? -Use [KV FastData Examples](/fastdata/kv/examples) for plain-language flows like exact-key lookups, key-history investigation, predecessor-scoped inspection, and canonical RPC follow-up. +Use [KV FastData Examples](/fastdata/kv/examples) for worked examples like exact-key lookups, key-history investigation, predecessor-scoped inspection, and canonical RPC follow-up. ## Default workflow diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index 22948ab..863da07 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -2,23 +2,19 @@ sidebar_label: Examples slug: /neardata/examples title: NEAR Data Examples -description: Plain-language workflows for recent contract-touch monitoring, optimistic confirmation, and shard-local change inspection. +description: Task-first NEAR Data examples for live monitoring, optimistic checks, and shard-local proof. displayed_sidebar: nearDataApiSidebar page_actions: - markdown --- -NEAR Data is strongest when the real question is about recent chain activity: did a contract show up in the newest block family, did an optimistic signal survive finality, and which shard actually carried the change? - -## Worked investigations +## Examples ### Did my contract get touched in the latest finalized block? -Use this when your app, bot, or support tool needs one fast answer about a live contract. We will check `intents.near`, but the same summary works for any contract account. -
- Strategy + Flow

Let NEAR Data answer the monitoring question first, then keep one tx hash or receipt ID for the next surface only if you need it.

@@ -28,7 +24,7 @@ Use this when your app, bot, or support tool needs one fast answer about a live
-This can honestly return `touched: false` in a quiet block. That is still a useful answer: nothing in the newest finalized block currently needs deeper tracing. +`touched: false` is a complete answer for a quiet block. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz @@ -82,7 +78,7 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | contract_touch_summary "$TARGET_CONTRACT" ``` -Read the answer like this: +Read it as: - `touched: false` means the newest finalized block did not mention or mutate the contract in any of the monitored ways. - `sample_tx_hash` means you already have a good `/tx` anchor for the next question. @@ -90,11 +86,9 @@ Read the answer like this: ### Did I see activity optimistically, and did it survive finality? -Use this when you want an early signal for a live contract, but the stable answer still needs finalized confirmation. -
- Strategy + Flow

Use the same contract-touch vocabulary on both surfaces so the comparison stays honest.

@@ -168,11 +162,9 @@ That is the practical split: ### Which shard actually changed my contract in this block? -Use this when a recent block already showed contract activity and you now want the shard-local proof of where the change actually landed. -
- Strategy + Flow

Use the full block to find the winning shard, then let block-shard prove the actual mutation.

diff --git a/docs/neardata/index.md b/docs/neardata/index.md index 3ae361a..5e5c1bc 100644 --- a/docs/neardata/index.md +++ b/docs/neardata/index.md @@ -49,7 +49,7 @@ https://testnet.neardata.xyz ## Need a workflow? -Use [NEAR Data API Examples](/neardata/examples) for compact workflows like contract-touch detection, optimistic-versus-finalized comparison, and shard-local change inspection. +Use [NEAR Data API Examples](/neardata/examples) for worked examples like contract-touch detection, optimistic-versus-finalized comparison, and shard-local change inspection. ## Troubleshooting diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index 0117379..f5cabf1 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /rpc/examples title: RPC Examples -description: Plain-language workflows for using FastNear RPC docs for exact state checks, block inspection, contract views, and transaction submission. +description: Task-first RPC examples for state checks, block inspection, contract reads, and transaction submission. displayed_sidebar: rpcSidebar page_actions: - markdown @@ -10,17 +10,15 @@ page_actions: # RPC Examples -Use this page when you already know the answer lives in RPC and you want the shortest path to it. The goal is not to memorize every method. It is to start with the right RPC read or write, stop as soon as the response answers the question, and only switch to a higher-level API when that would save time. +Start with the RPC method that answers the question. Submit with `broadcast_tx_async`, track with `tx`, and widen only when you need receipt trees, raw state, or shard-level tracing. ## Transaction Submission and Tracking -Start here when the real question is not just “how do I send this?” but “which RPC endpoint should I use, and how do I track the transaction all the way to done?” - ### Submit a transaction, then track it from hash to final execution -Use this when the user story is simple: “I have a signed transaction. Which endpoint do I call first, and what should I poll after I get the hash?” Not every tx question wants the same RPC method. The practical pattern is to submit fast, then track deliberately. +Need the default RPC submission flow? Submit with `broadcast_tx_async`, then poll `tx`. Use `EXPERIMENTAL_tx_status` only when you need the receipt tree. -This walkthrough is intentionally pinned and historical. It uses one real mainnet transaction that wrote a NEAR Social follow edge: +Pinned mainnet transaction: - transaction hash: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - signer: `mike.near` @@ -28,11 +26,9 @@ This walkthrough is intentionally pinned and historical. It uses one real mainne - included block height: `79574923` - receipt execution block for the SocialDB write: `79574924` -Because this transaction is already old and finalized, you cannot literally replay its true pending window. That is fine. The point here is to teach the right submission and tracking pattern, then inspect one pinned transaction with the same tools. -
- Strategy + Flow

Submit fast, poll the simpler status path first, and only drop into the receipt tree when the headline status stops being enough.

@@ -42,7 +38,7 @@ Because this transaction is already old and finalized, you cannot literally repl
-**What you're deciding** +**Decision points** - which submission endpoint to reach for first - what to poll after you have a tx hash @@ -78,13 +74,11 @@ flowchart LR | `EXECUTED_OPTIMISTIC` | execution has happened with optimistic finality | `tx` or `send_tx` | | `FINAL` | all relevant execution has completed and finalized | `tx` by default, `EXPERIMENTAL_tx_status` when you need more detail | -The key practical distinction is simple: - - use `broadcast_tx_async` when the tx hash is enough to keep going - use `tx` as the normal tracking loop - use `EXPERIMENTAL_tx_status` when the next question is about the receipt tree rather than the headline status -**What you're doing** +**Flow** - Show what a live submission would look like with `broadcast_tx_async`. - Poll the pinned tx with `tx` at two thresholds: `INCLUDED_FINAL` and `FINAL`. @@ -202,7 +196,7 @@ curl -s "$RPC_URL" \ This is where you go when “did it finish?” turns into “show me the receipt tree and the full async execution story.” -5. Optional: pivot to Transactions API only when you want the readable story surface. +5. Pivot to Transactions API only when you want the readable story surface. ```bash curl -s "$TX_BASE_URL/v0/transactions" \ @@ -233,15 +227,11 @@ That last step is intentionally optional. The RPC truth is already enough for su ## Account and Key Mechanics -Start here when the question is about exact permissions, exact key state, or one contract-level write flow. - ### Audit and remove old Near Social function-call keys -Use this when you know an account has accumulated older `social.near` function-call keys and you want to inspect them, choose one intentionally, and remove it with raw RPC submission. -
- Strategy + Flow

Use exact key reads to narrow the target first, then sign exactly one delete.

@@ -251,7 +241,7 @@ Use this when you know an account has accumulated older `social.near` function-c
-**What you're doing** +**Flow** - Use RPC itself to list every access key on the account. - Narrow that list to function-call keys scoped to `social.near`. @@ -328,7 +318,7 @@ curl -s "$RPC_URL" \ | jq '{nonce: .result.nonce, permission: .result.permission}' ``` -3. Optional: pull recent function-call activity for the account to decide whether you want to investigate more before cleanup. +3. Pull recent function-call activity for the account only if you want more context before cleanup. ```bash curl -s "$TX_BASE_URL/v0/account" \ @@ -486,17 +476,15 @@ else fi ``` -**Why this next step?** +**When to pivot** Re-running `view_access_key_list` closes the loop on the same RPC method you used for discovery. If the delete succeeded there, you do not need an indexed API to prove the cleanup. ### Which transaction added this `social.near` function-call key, and who authorized it? -Use this when you can already see a live access key on the account, but you want to trace it back to the `AddKey` transaction that created it and identify which public key actually authorized that change. -
- Strategy + Flow

Start from the live key, then walk backward only as far as you need.

@@ -506,7 +494,7 @@ Use this when you can already see a live access key on the account, but you want
-**What you're doing** +**Flow** - Read the exact key first with RPC and keep its current nonce as the clue. - Convert that nonce into a tight block-height window for the likely `AddKey` receipt. @@ -677,7 +665,7 @@ With the sample `mike.near` key above, the match is delegated: - `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` - `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` -4. Optional: if you need the exact `AddKey` receipt block too, pivot one more time by receipt ID. +4. If you need the exact `AddKey` receipt block too, pivot one more time by receipt ID. ```bash ADD_KEY_RECEIPT_ID="$( @@ -704,17 +692,15 @@ curl -s "$TX_BASE_URL/v0/receipt" \ For the sample key above, the exact `AddKey` receipt is `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` in receipt block `112057392`, while the outer transaction landed earlier in block `112057390`. -**Why this next step?** +**When to pivot** Start with exact current key state because it gives you the nonce clue. A tight `/v0/account` window turns that clue into a small candidate set. `/v0/transactions` tells you whether the key was added directly or through delegated authorization. `/v0/receipt` is the optional last step when you need the exact `AddKey` receipt block, not just the outer transaction. ### Register FT storage if needed, then transfer tokens -Use this when the user story is “send fungible tokens safely, but first prove whether the receiver is already registered for storage on that FT contract.” -
- Strategy + Flow

Read storage first, then spend the minimum write calls needed to make the transfer stick.

@@ -735,7 +721,7 @@ Use this when the user story is “send fungible tokens safely, but first prove This walkthrough uses the safe public contract `ft.predeployed.examples.testnet`. Before you start, make sure the sender already holds some `gtNEAR` there. If not, mint a small balance first with the pre-deployed contract guide above and then come back to this flow. -**What you're doing** +**Flow** - Use exact RPC view calls to check whether the receiver already has FT storage on the contract. - If needed, fetch the minimum storage requirement. @@ -1017,26 +1003,22 @@ curl -s "$RPC_URL" \ }' ``` -**Why this next step?** +**When to pivot** This is a good RPC example because every step stays close to the contract itself: first check storage state, then send the minimum required change calls, then verify the post-transfer balance directly on the contract. ## Contract Reads and Raw State -Start here when the question is “does this contract method tell me enough?” versus “should I read the storage directly?” - ### How do I read a contract's raw storage directly? -Use this when the public view method is missing, or when you need to verify the storage layout itself instead of trusting the method alone. - -This walkthrough uses the live public testnet contract `counter.near-examples.testnet`. The number can change over time. That is fine. The point is that you read the raw storage first, then confirm that the contract's public view method agrees: +This walkthrough uses the live public testnet contract `counter.near-examples.testnet`. The number can change over time. The useful part is the shape of the read: - `view_state` reads the raw `STATE` entry directly from contract storage - `call_function get_num` asks the contract for the same current number through its public view API
- Strategy + Flow

Read the storage the hard way first, then let the contract confirm the same answer through its view method.

@@ -1046,7 +1028,7 @@ This walkthrough uses the live public testnet contract `counter.near-examples.te
-The mental model matters more than the counter itself: +Keep the distinction straight: - `view_state` is a direct storage read from the trie - `call_function` executes a read-only method on the contract @@ -1063,7 +1045,7 @@ flowchart LR X --> A["Same current counter value"] ``` -**What you're doing** +**Flow** - Read the raw `STATE` key from contract storage. - Decode the returned bytes into the current signed counter value. @@ -1195,18 +1177,14 @@ If `agrees_now` is `true`, you have proved the point of the example: - `view_state` answered the question by reading storage directly - `call_function get_num` answered the same question by running the contract’s public read method -**Why this next step?** +**When to pivot** Use `view_state` when the real question is about exact storage, missing view methods, or verifying a known key family. Use `call_function` when you want the contract’s public read API. If the next question becomes historical instead of “what is it right now?”, that is the moment to widen into [KV FastData API](/fastdata/kv). ## Chunk and Shard Tracing -Start here when the question is no longer just “did this transaction succeed?” but “which shard-local chunk actually executed each leg of the work?” - ### Trace a generated transfer receipt from one shard chunk to another -Use this when the contract call itself was only the start of the story. In this pinned mainnet case, the signed transaction starts on shard `11`, then a generated `Transfer` receipt finishes on shard `6`. That cross-shard handoff is exactly why chunk-level inspection matters. - This walkthrough is pinned to: - transaction `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` from `7419369993.tg` to `game.hot.tg` calling `l2_claim` @@ -1217,7 +1195,7 @@ This walkthrough is pinned to:
- Strategy + Flow

Recover the receipt chain first, inspect the generated receipt directly, then map each leg back to the shard chunk that actually carried it.

@@ -1236,7 +1214,7 @@ flowchart LR C --> D["Chunk EPau...
block 194623172
shard 6
receipt executes"] ``` -**What you're doing** +**Flow** - Recover the receipt chain from the transaction first. - Inspect the generated `Transfer` receipt body directly. @@ -1441,7 +1419,7 @@ This confirms the cross-shard hop: - that chunk lives on shard `6`, not shard `11` - the signed transaction started on one shard, but the later receipt finished on another -**Why this next step?** +**When to pivot** Use [`Chunk by Block and Shard`](/rpc/protocol/chunk-by-block-shard) when you know the block and shard coordinates and want to ask “what did this shard execute in this block?” Use [`Chunk by Hash`](/rpc/protocol/chunk-by-hash) when another tool has already handed you the exact chunk hash. Use [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) and [`EXPERIMENTAL_receipt`](/rpc/transaction/experimental-receipt) when the real question is receipt-driven tracing. If you also need state changes and produced receipts, widen to [Block Shard](/neardata/block-shard). @@ -1451,11 +1429,9 @@ These stay on exact SocialDB reads and on-chain readiness checks until the quest ### Can this account still publish to NEAR Social right now? -Use this when the user story is “I’m about to publish a profile change, widget update, or graph write under `mike.near`, and I want a plain go/no-go answer before I open wallet signing.” -
- Strategy + Flow

Ask social.near for the two things that matter before you sign anything.

@@ -1465,7 +1441,7 @@ Use this when the user story is “I’m about to publish a profile change, widg
-This is the same question real NEAR Social clients have to answer before they try a write: +Required checks: - does the target account already have storage on `social.near`? - if it does, is there still room left in that storage? @@ -1475,7 +1451,7 @@ This is the same question real NEAR Social clients have to answer before they tr - [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) -**What you're doing** +**Flow** - Check that the signer account itself exists and can pay gas. - Ask `social.near` how much storage the target account has left. @@ -1647,17 +1623,15 @@ jq -n \ If that final object says `ready_to_publish_now: true`, RPC has already answered the question. If it says `false`, you know whether the blocker is storage, delegated permission, or both. -**Why this next step?** +**When to pivot** This keeps the whole question on exact on-chain reads. `social.near` itself answers whether the target account has room left and whether a delegated signer is already allowed to write. That is a better NEAR Social readiness check than guessing from wallet state alone. ### What does `mob.near/widget/Profile` actually contain right now? -Use this when the question is simple: “show me the live source for `mob.near/widget/Profile`, tell me when that widget key was last written, and keep me on exact RPC reads.” -
- Strategy + Flow

Stay on exact SocialDB reads, and only widen into history if the question turns forensic.

@@ -1671,7 +1645,7 @@ Use this when the question is simple: “show me the live source for `mob.near/w - [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) -**What you're doing** +**Flow** - Ask `social.near` for the widget catalog under `mob.near`. - Keep the block heights so you know when each widget key last changed. @@ -1786,7 +1760,7 @@ jq -r \ At the time of writing, the live last-write block for `mob.near/widget/Profile` was `86494825`. Keep that block if you later want to prove which transaction wrote this version. -**Why this next step?** +**When to pivot** Sometimes the right RPC answer is just: here is the widget, here is the live source, and here is the block height to keep if provenance matters later. diff --git a/docs/rpc/index.md b/docs/rpc/index.md index 3b56d9d..0857c1b 100644 --- a/docs/rpc/index.md +++ b/docs/rpc/index.md @@ -46,7 +46,7 @@ https://archival-rpc.testnet.fastnear.com ## Need a workflow? -Use [RPC Examples](/rpc/examples) for plain-language flows like exact state checks, block inspection, contract view calls, and send-and-confirm transaction work. +Use [RPC Examples](/rpc/examples) for worked examples like exact state checks, block inspection, contract view calls, and send-and-confirm transaction work. ## Use RPC when diff --git a/docs/snapshots/examples.mdx b/docs/snapshots/examples.mdx index ef2b8aa..9811c9a 100644 --- a/docs/snapshots/examples.mdx +++ b/docs/snapshots/examples.mdx @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /snapshots/examples title: Snapshot Examples -description: Plain-language operator workflows for choosing the right FastNear snapshot recovery path. +description: Task-first snapshot recovery examples for optimized, standard, and archival node recovery. displayed_sidebar: snapshotsSidebars page_actions: - markdown @@ -10,7 +10,7 @@ page_actions: ## Quick start -If the job is simply “bring a mainnet RPC node back fast,” start with one runnable command. +Need a mainnet RPC node back fast? Start with one runnable command. These helpers are maintained by FastNear and are meant to optimize for recovery speed. If your environment requires change review, fetch the script first, inspect it, and only then execute it instead of piping it straight to `bash`. @@ -21,17 +21,13 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash ``` -This is the shortest recovery path on the page. The rest only matters when you need standard RPC or archival hot/cold recovery. - -## Worked investigation +## Example ### Choose and execute the right mainnet recovery path -Use this investigation when an operator says “I need this node back online” and you need to decide whether the right path is optimized `fast-rpc`, standard RPC, or archival hot/cold recovery. -
- Strategy + Flow

Pick the recovery class first, then run the smallest command sequence that matches that class.

@@ -41,29 +37,8 @@ Use this investigation when an operator says “I need this node back online”
-**Goal** - -- Turn a vague recovery request into the right mainnet snapshot path and the minimum command sequence to get moving safely. - -| Path or command | How we use it | Why we use it | -| --- | --- | --- | -| Mainnet optimized `fast-rpc` path | Choose it first when the goal is the fastest high-performance RPC recovery and the node can support the optimized profile | It is the preferred fast recovery path when archival retention is not required | -| Mainnet standard RPC path | Use it when the operator needs a simpler RPC recovery without the optimized profile | It gives a straightforward default recovery route into the normal nearcore data path | -| Latest archival block lookup | Fetch the latest archival snapshot height before archival recovery | Anchors the hot/cold downloads to a specific archival snapshot block | -| Archival hot-data command | Run it first and place the result on NVMe | Hot archival data must land on the fast storage tier to support the node correctly | -| Archival cold-data command | Run it after hot data and place it on the cold storage tier | Completes archival recovery without forcing all archival data onto the expensive hot tier | - -**What a useful answer should include** - -- which recovery path was chosen and why -- which critical env vars matter for the chosen path -- where data should land on disk -- whether the operator should stay in FastNear snapshot docs or move to general nearcore bootstrap docs - ### Optimized mainnet `fast-rpc` command anchor -Use this when you have already decided the target is a high-performance mainnet RPC node and just need the smallest runnable command anchor. - ```bash DATA_PATH=~/.near/data @@ -73,8 +48,6 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ ### Standard mainnet RPC command anchor -Use this when you want the default mainnet RPC recovery path without the optimized `fast-rpc` profile. - ```bash DATA_PATH=~/.near/data @@ -84,9 +57,9 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ ### Mainnet archival recovery shell walkthrough -Use this when you have already decided that archival mainnet is the right path and now need the exact command sequence with one shared block anchor. +For archival recovery, capture one snapshot height and reuse it for both hot and cold data. -**What you're doing** +**Flow** - Fetch the latest archival mainnet snapshot height once. - Store it in `LATEST`. @@ -106,7 +79,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_TYPE=cold-data DATA_PATH="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash ``` -**Why this next step?** +**When to pivot** Hot and cold archival data need to come from the same snapshot cut. Reusing one captured `LATEST` value across both commands keeps the archival dataset internally consistent and makes later nearcore configuration much less surprising. diff --git a/docs/snapshots/index.mdx b/docs/snapshots/index.mdx index a29726a..d76da71 100644 --- a/docs/snapshots/index.mdx +++ b/docs/snapshots/index.mdx @@ -64,7 +64,7 @@ See ne ## Need a workflow? -Use [Snapshot Examples](/snapshots/examples) for operator-oriented flows like choosing between optimized `fast-rpc`, standard RPC recovery, and archival hot/cold snapshot paths. +Use [Snapshot Examples](/snapshots/examples) for worked operator examples like choosing between optimized `fast-rpc`, standard RPC recovery, and archival hot/cold snapshot paths. ## Choose a network diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index 06868f3..d720f4b 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -2,21 +2,19 @@ sidebar_label: Examples slug: /transfers/examples title: Transfers Examples -description: Plain-language workflows for finding transfers, paginating with resume_token, and pivoting into transaction history. +description: Task-first transfer examples for filtering feeds, paging, and pivoting into transaction history. displayed_sidebar: transfersApiSidebar page_actions: - markdown --- -## Worked walkthrough +## Example ### Filter and page a transfer feed for one account -Use this when the user story is “show me the meaningful transfer feed for this account, let me keep paging it cleanly, and only chase one row if that row needs the execution story.” -
- Strategy + Flow

Build the account feed first, then lift one receipt only if one row needs more story.

@@ -30,10 +28,10 @@ Use this when the user story is “show me the meaningful transfer feed for this - mainnet only today -**What you're doing** +**Flow** - Fetch the first page of one filtered transfer feed for a single account. -- Use the feed parameters themselves as the main teaching surface: `account_id`, `direction`, `asset_id`, `min_amount`, `desc`, and `limit`. +- Use the feed parameters themselves as the core levers: `account_id`, `direction`, `asset_id`, `min_amount`, `desc`, and `limit`. - Inspect the returned rows plus `resume_token` before you decide whether any row deserves deeper execution history. - Only if one row does deserve that deeper story, reuse its `receipt_id` in Transactions API. @@ -93,7 +91,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ }' ``` -**Why this next step?** +**When to pivot** The transfer query answers the first question directly: what does this account’s filtered feed look like right now, and how do you keep paging it without losing your place? Only after the feed tells you which row matters should you switch to `receipt_id` and chase execution history in `/tx`. diff --git a/docs/transfers/index.md b/docs/transfers/index.md index 65274f2..90fd054 100644 --- a/docs/transfers/index.md +++ b/docs/transfers/index.md @@ -68,7 +68,7 @@ When that happens, widen to [Transactions API](/tx) or [FastNear API](/api) inst ## Need a workflow? -Use [Transfers API Examples](/transfers/examples) for plain-language flows like narrow transfer searches, `resume_token` pagination, and escalation into broader transaction investigation. +Use [Transfers API Examples](/transfers/examples) for worked examples like narrow transfer searches, `resume_token` pagination, and escalation into broader transaction investigation. ## Troubleshooting diff --git a/docs/tx/examples.md b/docs/tx/examples.md index cc9eb33..65ae13e 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /tx/examples title: Transactions Examples -description: Plain-language transaction investigations for common developer jobs first. +description: Task-first transaction investigations for hashes, receipts, async failures, and callbacks. displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -35,21 +35,13 @@ curl -s "$TX_BASE_URL/v0/transactions" \ }' ``` -This is the shortest investigation on the page. Only move to RPC or receipt IDs if this output is not enough. - ## Start Here -These are the smallest useful anchors on the page: start with one tx hash, then one receipt ID, and only go deeper when the simpler story stops being enough. - ### I have one transaction hash. What happened? -Use this investigation when the user story is as plain as it gets: “someone pasted me one transaction hash. I just want to know whether it worked, what it did, and which block it landed in.” - -This is the beginner-to-intermediate on-ramp for the page. Before receipts, promise chains, or forensics, there is one simpler skill every NEAR engineer needs: turn a bare tx hash into one short human story. -
- Strategy + Flow

Start with the readable tx record, then drop into RPC or receipts only if the first answer is not enough.

@@ -59,11 +51,7 @@ This is the beginner-to-intermediate on-ramp for the page. Before receipts, prom
-**Goal** - -- Start from one transaction hash and recover the shortest useful answer: signer, receiver, action type, included block, and whether the transaction handed off into a successful execution path. - -For this pinned example: +Pinned example: - transaction hash: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` - signer: `mike.near` @@ -71,7 +59,7 @@ For this pinned example: - included block height: `194263342` - first receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` -The plain-English answer for this one is simple: `mike.near` submitted a single `Transfer` action to `global-counter.mike.near`, the transaction landed in block `194263342`, and the chain handed it off into one successful receipt. +Short answer: `mike.near` submitted a single `Transfer` action to `global-counter.mike.near`, the transaction landed in block `194263342`, and the chain handed it off into one successful receipt. ```mermaid flowchart LR @@ -87,19 +75,9 @@ flowchart LR | Canonical status follow-up | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Reuse the same tx hash and signer only if you need exact protocol-native status semantics | Useful when the next question becomes “success according to RPC, exactly?” | | Receipt handoff | Transactions API [`POST /v0/receipt`](/tx/receipt) | Reuse the first receipt ID if the next question turns into a receipt-level story | Provides the natural bridge to the next investigation when the transaction hash is no longer the best anchor | -**What a useful answer should include** - -- who signed the transaction -- which account received it -- which action type it carried -- which block included it -- one plain-English sentence that explains the transaction without receipt jargon - #### Transaction hash to human story shell walkthrough -Use this when you want the shortest possible path from one tx hash to one readable answer. - -**What you're doing** +**Flow** - Fetch the transaction by hash and print the main story fields. - Confirm the final status only if you need exact RPC semantics. @@ -183,19 +161,15 @@ curl -s "$TX_BASE_URL/v0/receipt" \ That last step is optional on purpose. If all you wanted was the transaction story, the first step was enough. Keep going only when the receipt itself becomes the new anchor. -**Why this next step?** +**When to pivot** `POST /v0/transactions` is the cleanest starting point when all you have is a tx hash and need one readable answer. RPC is the follow-up for exact status semantics. `POST /v0/receipt` is the handoff when the next question stops being about the transaction as a whole and starts being about one receipt inside it. ### Which receipt emitted this log or event? -Use this investigation when the user story is “I have one tx hash and one log fragment, and I need to know exactly which receipt emitted it.” - -This is a different question from “did the callback run?” later on the page. Here the goal is simpler: attribute one observed log line to one exact receipt ID, one method, and one executor. -
- Strategy + Flow

Fetch the receipt list once, filter by the log fragment, and stop as soon as one receipt owns that log.

@@ -205,10 +179,6 @@ This is a different question from “did the callback run?” later on the page.
-**Goal** - -- Start from one mainnet tx hash plus one log fragment and identify the exact receipt that emitted that log. - For this pinned mainnet example, use: - transaction hash: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` @@ -222,32 +192,9 @@ This transaction is useful because it has two different logged receipts in the s - one `Transfer ...` log on the earlier `ft_transfer_call` receipt - one `Refund ...` log on the later `ft_resolve_transfer` receipt -```mermaid -flowchart LR - T["One tx hash
2KhhB1uD..."] --> L["Read all receipt logs"] - L --> X["Match fragment:
Refund"] - X --> R["Exact receipt
9sLHQpaG..."] - R --> A["Answer:
wrap.near / ft_resolve_transfer"] -``` - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Log attribution | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the tx once and filter its receipts by a log fragment such as `Refund` | Gives the shortest path from one observed log line to the exact receipt that emitted it | -| Optional next pivot | Transactions API [`POST /v0/receipt`](/tx/receipt) | Reuse the matching `receipt_id` only if the receipt itself becomes the next anchor | Keeps the receipt ready for later investigation without making this example longer than needed | - -**What a useful answer should include** - -- which receipt ID emitted the log -- which contract executed that receipt -- which method ran there -- the exact matching log line -- one plain sentence such as “the `Refund` log came from `wrap.near` in the `ft_resolve_transfer` receipt” - #### Log-attribution shell walkthrough -Use this when you already have one tx hash and the next question is “which receipt said that?” - -**What you're doing** +**Flow** - Fetch the transaction once and keep the receipt list locally. - Filter the receipts by one log fragment. @@ -327,19 +274,16 @@ jq '{ That final comparison is useful because it proves the log attribution is not guesswork. This transaction has more than one logged receipt, and the `Refund` fragment belongs to one exact later receipt, not to the transaction as a whole. -**Why this next step?** +**When to pivot** Receipt logs live on receipts, not on some abstract top-level transaction object. `POST /v0/transactions` is enough to attribute one log line to one exact receipt without dropping into a deeper async trace. ### Turn one ugly receipt ID from logs into a human story - -Use this investigation when all you have is one ugly `receipt_id` from logs, traces, or an error report, and you want to turn it into a plain-English answer a teammate can understand. - -If you already have the transaction hash instead of the receipt ID, start with the simpler investigation just above and only drop down to this one when the receipt itself becomes the best anchor. +If you already have the transaction hash instead of the receipt ID, start with the simpler investigation just above.
- Strategy + Flow

Resolve the receipt first, then recover the parent transaction and stop once the story is readable.

@@ -349,11 +293,7 @@ If you already have the transaction hash instead of the receipt ID, start with t
-**Goal** - -- Start from one receipt ID and recover the shortest useful story: who created it, where it executed, which transaction spawned it, and what that transaction was actually trying to do. - -For this pinned example, the “ugly receipt ID from logs” is: +Pinned receipt from logs: - receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` - originating transaction hash: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` @@ -362,29 +302,7 @@ For this pinned example, the “ugly receipt ID from logs” is: - transaction block height: `194263342` - receipt execution block height: `194263343` -The human story behind that one receipt is simple: `mike.near` signed a plain `Transfer` transaction to `global-counter.mike.near`, the network turned it into one action receipt, and that receipt executed successfully in the next block. - -```mermaid -flowchart LR - L["One ugly receipt ID
5GhZcpfK..."] --> R["Lookup receipt"] - R --> T["Recover tx hash
AdgNifPY..."] - T --> S["Read transaction actions"] - S --> H["Human story:
mike.near sent 5 NEAR to global-counter.mike.near"] -``` - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Receipt anchor | Transactions API [`POST /v0/receipt`](/tx/receipt) | Look up the receipt ID first and print the accounts, execution block, success flag, and linked transaction hash | Gives you the shortest path from a raw receipt ID to “what object am I even looking at?” | -| Transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Reuse the recovered transaction hash and print signer, receiver, ordered actions, and included block | Turns the raw receipt into a readable story of what the signer actually submitted | -| Canonical follow-up | RPC [`tx`](/rpc/transaction/tx-status) or [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Confirm protocol-native semantics only if the indexed answer is still not enough | Useful when the question shifts from “tell me the story” to “show me the exact RPC status semantics” | - -**What a useful answer should include** - -- which accounts created and executed the receipt -- which transaction hash the receipt belongs to -- what the transaction actually did -- whether the receipt was the main event or just one step in a larger cascade -- one plain-English sentence that a teammate could read without decoding receipt jargon +Short answer: `mike.near` signed a plain `Transfer` transaction to `global-counter.mike.near`, the network turned it into one action receipt, and that receipt executed successfully in the next block. #### Ugly receipt ID to human story shell walkthrough @@ -468,25 +386,18 @@ For another receipt, keep the same pattern but change the final sentence to matc That is the core trick: you do not need to explain every receipt field. You need to recover just enough context to say what the signer did, where the receipt executed, and whether this receipt was the main event or only one step in a bigger cascade. -**Why this next step?** +**When to pivot** `POST /v0/receipt` tells you what the raw receipt is attached to. `POST /v0/transactions` tells you what the signer was actually trying to do. Once you have those two pieces together, you can usually explain the receipt in one sentence before deciding whether you really need block context, account history, or canonical RPC status. ## Failure and Async -This is where the page stops being simple lookup and starts teaching NEAR execution semantics: atomic batches, later async failures, and whether a callback ever made it back to the originating contract. - -Use this section when you already know the transaction worked across more than one receipt and the next question is about execution shape rather than simple identity lookup. - ### Prove that one failed action reverted the whole batch - -Use this investigation when one transaction tried to create and fund a new account, add a key, and then call a method on that same new account. The final action failed because the fresh account had no contract code. The real question is simple: did the earlier actions stick, or did the whole batch revert? - -On NEAR, the actions inside one transaction batch execute in order inside the same first action receipt. If one action in that receipt fails, the earlier actions in that same batch do not stick. That is different from later async receipts or promise chains, where the first receipt can succeed and some later receipt can still fail independently. +One batch created an account, funded it, added a key, and then called a missing method. The question is whether the earlier actions stuck or the whole batch reverted.
- Strategy + Flow

Prove what the batch tried, which action failed, and whether anything from the earlier actions actually stuck.

@@ -496,16 +407,12 @@ On NEAR, the actions inside one transaction batch execute in order inside the sa
-**Goal** - -- Prove, from one pinned testnet transaction, that the final `FunctionCall` failed and the earlier `CreateAccount`, `Transfer`, and `AddKey` actions did not stick. - **Official references** - [Transaction foundations](/transaction-flow/foundations) - [Runtime execution](/transaction-flow/runtime-execution) -This pinned failure was captured on **April 18, 2026** on testnet: +Pinned testnet failure observed on **April 18, 2026**: - transaction hash: `CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M` - signer account: `temp.mike.testnet` @@ -535,21 +442,11 @@ flowchart LR | Exact failure | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Query the same transaction with `wait_until: "FINAL"` and inspect `status.Failure` | Tells you which action failed and why the whole batch reverted at the protocol level | | Post-state proof | RPC [`query(view_account)`](/rpc/account/view-account) | Query the intended new account after finality | If the created account still does not exist, then the earlier `CreateAccount`, `Transfer`, and `AddKey` from that same batch did not stick either | -One detail is worth calling out before the shell walkthrough: the indexed transaction record still shows `transaction_outcome.outcome.status = SuccessReceiptId`, because the signed transaction successfully became its first action receipt. The proof that the batch reverted comes from the RPC top-level `status.Failure` on that first receipt, plus the post-state check that the intended new account never existed. - -**What a useful answer should include** - -- the exact action order the signer submitted -- which action index failed and why -- the included block height and hash for the batch -- proof that the intended new account still does not exist after finality -- a short conclusion that the earlier `CreateAccount`, `Transfer`, and `AddKey` actions did not stick once the final `FunctionCall` failed +The indexed transaction record still shows `transaction_outcome.outcome.status = SuccessReceiptId`, because the signed transaction did become its first action receipt. The proof that the batch reverted comes from the RPC top-level `status.Failure` on that first receipt plus the post-state check that the intended new account never existed. #### Failed batched transaction shell walkthrough -Use this when you want one concrete failed batch that you can inspect step by step with public FastNear testnet endpoints. - -**What you're doing** +**Flow** - Read the indexed transaction record to recover the intended action batch. - Use RPC transaction status to prove the final `FunctionCall` failed and reverted the batch. @@ -657,19 +554,16 @@ jq '{ That one post-state check is enough here. If `CreateAccount` had stuck, `view_account` would resolve. Because the account still does not exist, the earlier `Transfer` and `AddKey` from the same batched receipt did not stick either. -**Why this next step?** +**When to pivot** For another failed batch, keep the same pattern: read what the transaction tried to do from [`POST /v0/transactions`](/tx/transactions), confirm the exact top-level failure with RPC transaction status, then inspect post-state on the account, key, contract, or other object that would have changed if the earlier actions had stuck. ### Why did this contract call look successful, but a later receipt failed? - -Use this investigation when one contract call logged success, changed its own local state, and even the top-level RPC `status` looks successful, but the app still broke because a later detached cross-contract receipt failed. - -This is the opposite of the failed batch example above. There, one action failed inside the first action receipt, so nothing in that batch stuck. Here, the first contract receipt really did succeed and its state change really did stick. The failure happened later, in a separate receipt. +The first contract receipt succeeded. The failure happened later, in a separate detached receipt.
- Strategy + Flow

First get the human timeline, then prove where the async story split.

@@ -679,16 +573,12 @@ This is the opposite of the failed batch example above. There, one action failed
-**Goal** - -- Prove, from one pinned testnet transaction, that `seq-dr.mike.testnet.kickoff_append(...)` succeeded on its own receipt, then a detached `append(...)` call failed one block later with `CodeDoesNotExist`. - **Official references** - [Transaction foundations](/transaction-flow/foundations) - [Runtime execution](/transaction-flow/runtime-execution) -This pinned async failure was captured on **April 18, 2026** on testnet: +Pinned async testnet failure observed on **April 18, 2026**: - transaction hash: `AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz` - signer account: `temp.mike.testnet` @@ -714,21 +604,11 @@ flowchart LR | Transaction skeleton | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the pinned transaction and print the included block plus the per-receipt timeline | Gives the shortest readable overview of which receipt ran first and which receipt failed later | | Exact status semantics | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Inspect the top-level `status`, the first contract receipt outcome, and the later failed receipt outcome | Proves that top-level success and later descendant failure can coexist in one async story | -One NEAR detail matters here: receipt success is not transitive. `seq-dr.mike.testnet` returned success on its own receipt because `kickoff_append(...)` only logged and detached the next hop. The detached `append(...)` receipt was a separate piece of async work, so its later failure did not change the fact that the router's own receipt had already completed successfully. - -**What a useful answer should include** - -- that the signed transaction successfully handed off into the first router receipt -- that the router receipt itself succeeded and emitted the `dishonest_router:kickoff:late-failure` log -- that the later detached receipt to `asyncfail-in2hwikn.temp.mike.testnet` failed with `CodeDoesNotExist` -- that RPC still reports the outer transaction as `SuccessValue` even though a later detached receipt failed -- one sentence explaining why this is different from a failed batched transaction +Receipt success is not transitive. `seq-dr.mike.testnet` returned success on its own receipt because `kickoff_append(...)` only logged and detached the next hop. The detached `append(...)` receipt was separate async work, so its later failure did not change the fact that the router's own receipt had already completed successfully. #### Later receipt failure shell walkthrough -Use this when the user story is “the contract call looked fine, but something failed later, and I need to prove exactly where the story split.” - -**What you're doing** +**Flow** - Read the transaction and its receipt timeline from the indexed view. - Use RPC transaction status to show that the top-level story still ended in `SuccessValue` even though a later receipt failed. @@ -829,24 +709,16 @@ jq \ Stop here. As of **April 18, 2026**, `seq-dr.mike.testnet` no longer resolves on testnet, so a live router-state proof would no longer be truthful. The indexed receipt timeline plus `EXPERIMENTAL_tx_status` are the preserved historical evidence that still matters. -**Why this next step?** +**When to pivot** When a NEAR app “looked successful” and still broke later, the thing to ask is not just “what was the transaction status?” but “which receipt succeeded, and which later receipt failed?” This example gives you that exact split: indexed receipt timeline for the shape, RPC status for the exact semantics, and no pretend live router-state read after the historical contract disappeared. ### Did my callback run at all? - -Use this investigation when one transaction kicked off downstream work on another contract and the real question is not “did the receiver succeed?” but “did the originating contract ever get its callback receipt back?” - -This is the smallest useful callback proof on the page: - -- start from one tx hash -- identify the downstream receipt on the other contract -- look for the later callback receipt back on the origin contract -- stop once callback existence and callback outcome are proven +Start from the indexed receipt chain. Use RPC only if you need canonical callback semantics.
- Strategy + Flow

Use the indexed receipt list first, then drop to RPC only if you need canonical callback semantics.

@@ -856,11 +728,7 @@ This is the smallest useful callback proof on the page:
-**Goal** - -- Prove, from one pinned mainnet transaction, that `wrap.near` sent an `ft_transfer_call` to `v2.ref-finance.near`, the receiver ran `ft_on_transfer`, and `wrap.near` later got the `ft_resolve_transfer` callback back. - -This pinned mainnet callback example was observed on **April 19, 2026**: +Pinned mainnet callback example observed on **April 19, 2026**: - transaction hash: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` - sender account: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` @@ -881,26 +749,16 @@ flowchart LR C --> R["Refund log on wrap.near"] ``` -One useful NEAR detail shows up here: a downstream failure does not mean the callback vanished. In this case, `v2.ref-finance.near` failed its `ft_on_transfer` receipt, but `wrap.near` still later received `ft_resolve_transfer` and logged the refund. +A downstream failure does not mean the callback vanished. In this case, `v2.ref-finance.near` failed its `ft_on_transfer` receipt, but `wrap.near` still later received `ft_resolve_transfer` and logged the refund. | Surface | Endpoint | How we use it | Why we use it | | --- | --- | --- | --- | | Indexed receipt chain | Transactions API [`POST /v0/transactions`](/tx/transactions) | Start from the tx hash and print only the downstream receiver receipt plus the later callback receipt on the origin contract | Gives the fastest readable answer to “did the callback come back?” | | Canonical receipt confirmation | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Reuse the same tx hash and sender only if you need the callback receipt's canonical status and logs | Useful when the indexed answer is enough for shape but you still want protocol-native proof | -**What a useful answer should include** - -- which contract received the downstream call -- which method ran on that downstream contract -- whether a later receipt returned to the originating contract -- which callback method ran there and in which block -- one plain sentence such as “the receiver failed, but the origin contract still got its callback and settled the transfer” - #### Callback-existence shell walkthrough -Use this when you want one concrete callback proof without turning the page into a full promise-theory exercise. - -**What you're doing** +**Flow** - Fetch the transaction once and narrow the receipt list down to the downstream call plus the callback receipt. - Reuse the callback receipt ID only if you still need canonical RPC confirmation. @@ -1059,7 +917,7 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ # - the callback log shows the refund back to the sender ``` -**Why this next step?** +**When to pivot** For callback questions, the important proof is not “did every receipt succeed?” but “did the origin contract get its callback receipt back, and what happened there?” `POST /v0/transactions` gives the fastest readable answer. RPC is only the optional confirmation layer when you need the callback receipt's canonical outcome and logs. diff --git a/docs/tx/index.md b/docs/tx/index.md index 08336f8..3d661cd 100644 --- a/docs/tx/index.md +++ b/docs/tx/index.md @@ -51,7 +51,7 @@ https://tx.test.fastnear.com ## Need a workflow? -Use [Transactions API Examples](/tx/examples) for plain-language flows like transaction lookups, receipt investigation, account activity, and block-window history. +Use [Transactions API Examples](/tx/examples) for worked examples like transaction lookups, receipt investigation, account activity, and block-window history. ## Troubleshooting diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index 5f05f92..2f778f3 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -2,23 +2,19 @@ sidebar_label: Examples slug: /api/examples title: "Примеры API" -description: "Пошаговые сценарии использования FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга." +description: "Практические примеры FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга." displayed_sidebar: fastnearApiSidebar page_actions: - markdown --- -## Готовые сценарии - -Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. +## Примеры ### Определить аккаунт по публичному ключу, а затем получить сводку по нему -Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» -
- Стратегия + Ход

Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку.

@@ -28,7 +24,7 @@ page_actions:
-**Что вы делаете** +**Ход** - Ищете по публичному ключу один или несколько `account_id`. - Извлекаете первый найденный `account_id` через `jq`. @@ -60,17 +56,15 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. ### Показывает ли этот кошелёк прямой стейкинг, ликвидные стейкинг-токены или и то и другое? -Используйте этот сценарий, когда история звучит так: «покажи, видно ли по этому кошельку прямые позиции в staking pool, ликвидные стейкинг-токены или и то и другое». -
- Стратегия + Ход

Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк.

@@ -91,7 +85,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. -**Что вы делаете** +**Ход** - Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. - Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. @@ -148,17 +142,15 @@ jq -n \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. ### Заархивировать версию BOS-виджета как provenance NFT -Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». -
- Стратегия + Ход

Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы.

@@ -179,7 +171,7 @@ jq -n \ - [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -**Что вы делаете** +**Ход** - Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. - Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. @@ -363,7 +355,7 @@ jq '{ }' /tmp/bos-widget-provenance-token.json ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](/tx/examples). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/index.md index eb8a2ff..5bfb13a 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/index.md @@ -56,7 +56,7 @@ https://test.api.fastnear.com ## Нужен сценарий? -Используйте [примеры FastNear API](/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, определения аккаунта по ключу и перехода к узким представлениям активов. +Используйте [примеры FastNear API](/api/examples) для практических примеров: сводки по аккаунтам, определения аккаунта по ключу и перехода к узким представлениям активов. ## Устранение неполадок diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index 9431498..b36b3d0 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -2,21 +2,19 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: "Примеры KV FastData" -description: "Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC." +description: "Практические примеры для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC." displayed_sidebar: kvFastDataSidebar page_actions: - markdown --- -## Готовое расследование +## Пример ### Посмотреть индексированные записи одного `predecessor_id`, а затем сузиться до ключа, который изменился -Используйте это расследование, когда сначала известен предшественник, а настоящий вопрос звучит так: «что вообще записал этот `predecessor_id`, какая строка здесь действительно интересна и что потом происходило с этим ключом?» -
- Стратегия + Ход

Начните с области по `predecessor_id`, переходите к точному ключу только после того, как он заслужил внимание, а RPC оставляйте на самый конец.

@@ -26,28 +24,8 @@ page_actions:
-**Цель** - -- Объяснить, что записал этот `predecessor_id`, какой точный ключ стал настоящим фокусом, как он менялся и нужен ли вам вообще финальный `view_state`. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Последние индексированные строки по области | KV FastData [`all-by-predecessor`](/fastdata/kv/all-by-predecessor) | Сначала получаем текущие индексированные записи одного `predecessor_id` по затронутым контрактам | Отвечает на вопрос по области раньше, чем вы начинаете притворяться, будто точный ключ уже известен | -| История индексированного ключа | KV FastData [`get-history-key`](/fastdata/kv/get-history-key) или [`history-by-predecessor`](/fastdata/kv/history-by-predecessor) | Забираем историю точного ключа или ещё на один шаг оставляем историю шире, на уровне предшественника | Показывает, стабильно ли интересующее значение, насколько оно недавнее и не входит ли в более широкий паттерн записей | -| Точная проверка состояния | RPC [`view_state`](/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | - -**Что должен включать полезный ответ** - -- с какой области по `predecessor_id` вы начали -- какой точный ключ стал настоящим фокусом -- как этот ключ менялся в истории -- нужен ли вообще финальный `view_state` - ### Shell-сценарий по области предшественника - -Используйте этот сценарий, когда сначала известен один `predecessor_id` и нужно аккуратно перейти от вопроса «что он вообще записал?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» - -**Что вы делаете** +**Ход** - Читаете последние индексированные строки для одного `predecessor_id` по затронутым контрактам. - Поднимаете интересующие `current_account_id` и точный `key` через `jq`. @@ -96,7 +74,7 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KE }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Первый запрос отвечает на вопрос по области: «что этот `predecessor_id` сейчас пишет?». Сужение из этой ленты до одного точного ключа отвечает на более точный вопрос: «как именно эта строка дошла до такого состояния?». Если картина всё ещё шире одного ключа, ещё немного побудьте на [History by Predecessor](/fastdata/kv/history-by-predecessor), а уже потом переходите к точной истории ключа или RPC. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md index 00eb8ae..8832359 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md @@ -82,7 +82,7 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY ## Нужен сценарий? -Используйте [примеры KV FastData](/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. +Используйте [примеры KV FastData](/fastdata/kv/examples) для практических примеров: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. ## Рабочий цикл по умолчанию diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index 62f8083..e014946 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -2,23 +2,21 @@ sidebar_label: Examples slug: /neardata/examples title: "Примеры NEAR Data" -description: "Пошаговые сценарии для мониторинга недавней активности контракта, подтверждения optimistic-наблюдений и проверки изменений на уровне shard." +description: "Практические примеры для мониторинга недавней активности контракта, подтверждения optimistic-наблюдений и проверки изменений на уровне shard." displayed_sidebar: nearDataApiSidebar page_actions: - markdown --- -NEAR Data особенно хороша там, где вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. +Используйте NEAR Data, когда вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. -## Готовые расследования +## Примеры ### Был ли мой контракт затронут в последнем финализированном блоке? -Используйте это, когда приложению, боту или инструменту поддержки нужен один быстрый ответ о живом контракте. Мы будем проверять `intents.near`, но та же сводка работает для любого аккаунта контракта. -
- Стратегия + Ход

Сначала дайте NEAR Data ответить на задачу мониторинга, а уже потом сохраняйте tx hash или receipt ID для следующей поверхности, если это вообще понадобится.

@@ -82,7 +80,7 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | contract_touch_summary "$TARGET_CONTRACT" ``` -Читать ответ стоит так: +Читайте ответ так: - `touched: false` означает, что самый новый финализированный блок не упомянул и не изменил контракт ни одним из отслеживаемых способов. - `sample_tx_hash` означает, что у вас уже есть хороший якорь для следующего шага на `/tx`. @@ -90,11 +88,9 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ ### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? -Используйте это, когда нужен ранний сигнал по живому контракту, но стабильный ответ всё равно должен пройти через финализированное подтверждение. -
- Стратегия + Ход

Используйте один и тот же словарь contract-touch на обеих поверхностях, чтобы сравнение было честным.

@@ -168,11 +164,9 @@ fi ### Какой shard действительно изменил мой контракт в этом блоке? -Используйте это, когда недавний блок уже показал активность контракта, и теперь нужно доказательство на уровне shard того, где именно изменение реально приземлилось. -
- Стратегия + Ход

Сначала используйте весь блок, чтобы найти нужный shard, а затем дайте block-shard доказать само изменение.

diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md index 2de79b0..af49e23 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/index.md @@ -49,7 +49,7 @@ https://testnet.neardata.xyz ## Нужен сценарий? -Используйте [примеры NEAR Data API](/neardata/examples) для компактных сценариев: обнаружения активности контракта, сравнения оптимистичных и финализированных наблюдений, а также проверки изменений на уровне шарда. +Используйте [примеры NEAR Data API](/neardata/examples) для практических примеров: обнаружения активности контракта, сравнения оптимистичных и финализированных наблюдений, а также проверки изменений на уровне шарда. ## Устранение неполадок diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index b7ab0d6..217c0b3 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /rpc/examples title: "Примеры RPC" -description: "Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций." +description: "Практические примеры FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций." displayed_sidebar: rpcSidebar page_actions: - markdown @@ -10,17 +10,15 @@ page_actions: # Примеры RPC -Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. +Начинайте с RPC-метода, который отвечает на вопрос. Отправляйте через `broadcast_tx_async`, отслеживайте через `tx` и расширяйте разбор только когда действительно нужны дерево receipts, raw state или трассировка по шардам. ## Отправка и отслеживание транзакции -Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» - ### Отправить транзакцию и затем проследить её от хеша до финального исполнения -Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. +Нужен стандартный путь отправки через RPC? Отправляйте через `broadcast_tx_async`, затем опрашивайте `tx`. К `EXPERIMENTAL_tx_status` переходите только если нужно дерево receipts. -Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: +Зафиксированная mainnet-транзакция: - хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - signer: `mike.near` @@ -28,11 +26,9 @@ page_actions: - высота блока включения: `79574923` - высота блока исполнения receipt для записи в SocialDB: `79574924` -Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. -
- Стратегия + Ход

Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно.

@@ -42,7 +38,7 @@ page_actions:
-**Что вы здесь решаете** +**Точки выбора** - какой эндпоинт отправки брать первым - что опрашивать после того, как у вас появился tx hash @@ -78,13 +74,13 @@ flowchart LR | `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | | `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | -Практическое различие очень простое: +Используйте методы так: - используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash - используйте `tx` как обычный цикл опроса - используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу -**Что вы делаете** +**Ход** - Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. - Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. @@ -237,11 +233,11 @@ curl -s "$TX_BASE_URL/v0/transactions" \ ### Проверить и удалить старые function-call-ключи Near Social -Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. +Есть старые function-call-ключи для `social.near`? Сначала найдите нужный ключ, потом удалите один конкретный.
- Стратегия + Ход

Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление.

@@ -251,7 +247,7 @@ curl -s "$TX_BASE_URL/v0/transactions" \
-**Что вы делаете** +**Ход** - Через сам RPC получаете полный список access key аккаунта. - Сужаете этот список до function-call-ключей, привязанных к `social.near`. @@ -486,17 +482,17 @@ else fi ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. ### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? -Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. +Ключ уже виден на аккаунте? Вернитесь к транзакции `AddKey`, которая его создала.
- Стратегия + Ход

Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно.

@@ -506,7 +502,7 @@ fi
-**Что вы делаете** +**Ход** - Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. - Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. @@ -704,17 +700,17 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. ### Проверить регистрацию FT storage и затем перевести токены -Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». +Нужно отправить FT безопасно? Сначала проверьте storage registration получателя.
- Стратегия + Ход

Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу.

@@ -735,7 +731,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. -**Что вы делаете** +**Ход** - Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. - При необходимости получаете минимальный размер storage deposit. @@ -1017,7 +1013,7 @@ curl -s "$RPC_URL" \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. @@ -1027,16 +1023,16 @@ curl -s "$RPC_URL" \ ### Как прочитать сырое состояние контракта напрямую? -Используйте этот сценарий, когда у контракта нет нужного view-метода или когда вам нужно проверить саму схему хранения, а не просто довериться ответу метода. +Нужен raw storage, а не только view-метод? Читайте состояние напрямую, затем сравнивайте с view. -В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: сначала вы читаете storage напрямую, а потом подтверждаете, что публичный view-метод контракта даёт тот же ответ: +Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`. Значение может меняться; важна последовательность чтений: сначала raw storage, потом проверка через view-метод. - `view_state` читает сырой ключ `STATE` прямо из storage контракта - `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API
- Стратегия + Ход

Сначала прочитайте storage напрямую, а уже потом дайте контракту подтвердить тот же ответ через view-метод.

@@ -1063,7 +1059,7 @@ flowchart LR X --> A["Одно и то же текущее значение"] ``` -**Что вы делаете** +**Ход** - Читаете сырой ключ `STATE` из storage контракта. - Декодируете возвращённые байты в текущее знаковое значение счётчика. @@ -1195,7 +1191,7 @@ jq -n \ - `view_state` ответил на вопрос, прочитав storage напрямую - `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Используйте `view_state`, когда настоящий вопрос относится к точному storage, отсутствующему view-методу или проверке известного семейства ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](/fastdata/kv). @@ -1205,9 +1201,9 @@ jq -n \ ### Проследить, как сгенерированная `Transfer`-receipt переходит из одного чанка на шарде в другой -Используйте этот сценарий, когда вызов контракта был только началом истории. В этом зафиксированном mainnet-примере подписанная транзакция стартует на шарде `11`, а сгенерированная `Transfer`-receipt заканчивает путь уже на шарде `6`. Именно ради таких cross-shard handoff и имеет смысл смотреть на чанки. +Нужен cross-shard handoff, а не только исходная транзакция? Смотрите чанки. -Этот walkthrough привязан к: +Зафиксированный mainnet-пример: - транзакции `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` от `7419369993.tg` к `game.hot.tg` с вызовом `l2_claim` - исходному чанку `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` на шарде `11` в блоке `194623170` @@ -1217,7 +1213,7 @@ jq -n \
- Стратегия + Ход

Сначала восстановите receipt-цепочку, потом напрямую посмотрите на сгенерированную receipt, а затем привяжите каждый шаг к тому чанку на шарде, который действительно нёс эту работу.

@@ -1236,7 +1232,7 @@ flowchart LR C --> D["Чанк EPau...
блок 194623172
шард 6
receipt исполняется"] ``` -**Что вы делаете** +**Ход** - Сначала восстанавливаете receipt-цепочку из транзакции. - Напрямую смотрите на тело сгенерированной `Transfer`-receipt. @@ -1441,7 +1437,7 @@ curl -s "$RPC_URL" \ - этот чанк живёт на шарде `6`, а не на шарде `11` - подписанная транзакция стартовала на одном шарде, а следующая receipt завершилась уже на другом -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Используйте [Chunk by Block and Shard](/rpc/protocol/chunk-by-block-shard), когда вы знаете координаты блока и шарда и хотите буквально спросить: «что этот шард исполнил в этом блоке?» Используйте [Chunk by Hash](/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. Используйте [EXPERIMENTAL_tx_status](/rpc/transaction/experimental-tx-status) и [EXPERIMENTAL_receipt](/rpc/transaction/experimental-receipt), когда настоящий вопрос относится к трассировке на уровне receipts. Если ещё нужны state changes и produced receipts, расширяйтесь в [Block Shard](/neardata/block-shard). @@ -1451,11 +1447,11 @@ curl -s "$RPC_URL" \ ### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? -Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». +Нужно понять «готово / не готово» до окна подписи? Проверьте storage и права до отправки.
- Стратегия + Ход

Спросите у social.near ровно о двух вещах, которые важны до подписи.

@@ -1475,7 +1471,7 @@ curl -s "$RPC_URL" \ - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -**Что вы делаете** +**Ход** - Проверяете, что аккаунт signer вообще существует и способен оплатить gas. - Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. @@ -1647,17 +1643,17 @@ jq -n \ Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. ### Что прямо сейчас содержит `mob.near/widget/Profile`? -Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». +Нужен живой исходник виджета и последняя запись этого ключа? Оставайтесь на точных RPC-чтениях.
- Стратегия + Ход

Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой.

@@ -1671,7 +1667,7 @@ jq -n \ - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -**Что вы делаете** +**Ход** - Спрашиваете у `social.near` каталог виджетов под `mob.near`. - Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. @@ -1786,7 +1782,7 @@ jq -r \ На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/index.md index a5c4446..34528f8 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/index.md @@ -45,7 +45,7 @@ https://archival-rpc.testnet.fastnear.com ## Нужен сценарий? -Используйте [примеры RPC](/rpc/examples) для простых пошаговых сценариев: точных проверок состояния, анализа блоков, view-вызовов контрактов и отправки транзакций с подтверждением. +Используйте [примеры RPC](/rpc/examples) для практических примеров: точных проверок состояния, анализа блоков, view-вызовов контрактов и отправки транзакций с подтверждением. ## Используйте RPC, когда diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx index e15c556..92c8c57 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /snapshots/examples title: "Примеры Snapshot" -description: "Пошаговые операторские сценарии для выбора правильного пути восстановления через FastNear snapshots." +description: "Практические примеры восстановления для выбора правильного пути через FastNear snapshots." displayed_sidebar: snapshotsSidebars page_actions: - markdown @@ -21,17 +21,13 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash ``` -Это самый короткий путь восстановления на странице. Всё остальное нужно только если вам нужен standard RPC или архивный hot/cold-сценарий. - -## Готовое расследование +## Пример ### Выбрать и выполнить правильный сценарий восстановления mainnet -Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. -
- Стратегия + Ход

Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него.

@@ -41,29 +37,8 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/
-**Цель** - -- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. - -| Путь или команда | Как используем | Зачем используем | -| --- | --- | --- | -| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | -| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | -| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | -| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | -| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | - -**Что должен включать полезный ответ** - -- какой сценарий восстановления выбран и почему -- какие ключевые env vars важны для выбранного пути -- куда на диске должны попасть данные -- должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap - ### Минимальная команда для optimized mainnet `fast-rpc` -Используйте этот якорь, когда уже ясно, что нужен высокопроизводительный mainnet RPC-узел, и требуется только минимальная рабочая команда. - ```bash DATA_PATH=~/.near/data @@ -73,8 +48,6 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ ### Минимальная команда для стандартного mainnet RPC -Используйте этот якорь, когда нужен обычный путь восстановления mainnet RPC без optimized-профиля `fast-rpc`. - ```bash DATA_PATH=~/.near/data @@ -83,10 +56,9 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ ``` ### Shell-сценарий архивного восстановления mainnet +Для архивного восстановления сначала получите одну высоту snapshot и переиспользуйте её для hot- и cold-data. -Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. - -**Что вы делаете** +**Ход** - Один раз получаете последнюю высоту архивного снапшота mainnet. - Сохраняете её в `LATEST`. @@ -106,7 +78,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_TYPE=cold-data DATA_PATH="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. 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 b689a3f..c73d680 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/index.mdx @@ -65,7 +65,7 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash ## Нужен сценарий? -Используйте [примеры снапшотов](/snapshots/examples) для операторских сценариев: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. +Используйте [примеры снапшотов](/snapshots/examples) для практических примеров: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. ## Выберите сеть diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md index bb8cf1e..c003125 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -2,21 +2,19 @@ sidebar_label: Examples slug: /transfers/examples title: "Примеры Transfers API" -description: "Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций." +description: "Практические примеры для поиска переводов, пагинации через resume_token и перехода к истории транзакций." displayed_sidebar: transfersApiSidebar page_actions: - markdown --- -## Готовый сценарий +## Пример ### Отфильтровать и листать ленту переводов одного аккаунта -Используйте этот сценарий, когда история звучит так: «покажи мне осмысленную ленту переводов этого аккаунта, дай мне спокойно листать её дальше, и только потом, если нужно, помоги догнать одну строку до истории исполнения». -
- Стратегия + Ход

Сначала соберите саму ленту аккаунта, а `receipt` поднимайте только тогда, когда одна строка действительно требует истории исполнения.

@@ -30,7 +28,7 @@ page_actions: - только mainnet -**Что вы делаете** +**Ход** - Забираете первую страницу одной отфильтрованной ленты переводов для выбранного аккаунта. - Используете сами параметры ленты как главный учебный материал: `account_id`, `direction`, `asset_id`, `min_amount`, `desc` и `limit`. @@ -93,7 +91,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Запрос переводов напрямую отвечает на первый вопрос: как сейчас выглядит отфильтрованная лента этого аккаунта и как её продолжать без потери места? Только после того как сама лента подскажет, какая строка действительно важна, имеет смысл переходить по `receipt_id` и забирать историю исполнения из `/tx`. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/index.md index f693b41..22d7c72 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/index.md @@ -68,7 +68,7 @@ https://transfers.main.fastnear.com ## Нужен сценарий? -Используйте [Transfers API Examples](/transfers/examples) для простых пошаговых сценариев: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. +Используйте [Transfers API Examples](/transfers/examples) для практических примеров: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. ## Устранение неполадок diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index 5262f15..b2a203f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /tx/examples title: "Примеры Transactions API" -description: "Пошаговые расследования транзакций сначала для типовых задач разработчика." +description: "Практические расследования транзакций для типовых задач разработчика." displayed_sidebar: transactionsApiSidebar page_actions: - markdown @@ -35,21 +35,13 @@ curl -s "$TX_BASE_URL/v0/transactions" \ }' ``` -Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. - ## С чего начать -Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. - ### У меня есть один хеш транзакции. Что вообще произошло? -Используйте это расследование, когда история максимально простая: «мне прислали один хеш транзакции. Я просто хочу понять, сработала ли она, что именно сделала и в какой блок попала». - -Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. -
- Стратегия + Ход

Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно.

@@ -59,11 +51,7 @@ curl -s "$TX_BASE_URL/v0/transactions" \
-**Цель** - -- Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. - -Для этого зафиксированного примера: +Зафиксированный пример: - хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` - signer: `mike.near` @@ -71,7 +59,7 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - высота включающего блока: `194263342` - ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` -Простой человеческий ответ для этого случая такой: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. +Короткий ответ: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. ```mermaid flowchart LR @@ -87,19 +75,9 @@ flowchart LR | Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | | Переход к receipt | Transactions API [`POST /v0/receipt`](/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | -**Что должен включать полезный ответ** - -- кто подписал транзакцию -- какой аккаунт её получил -- какой тип действия она несла -- в какой блок попала -- одно простое предложение, которое объясняет транзакцию без receipt-жаргона - #### Shell-сценарий: от хеша транзакции к человеческой истории -Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. - -**Что вы делаете** +**Ход** - Получаете транзакцию по хешу и печатаете её основные поля. - Подтверждаете финальный статус только если нужны точные RPC-семантики. @@ -183,19 +161,15 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** `POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. ### Какая receipt выдала этот лог или event? -Используйте это расследование, когда история звучит так: «у меня есть один tx hash и один фрагмент лога, и мне нужно точно понять, какая именно receipt его выдала». - -Это другой вопрос, чем более поздний сценарий «дошёл ли callback?». Здесь цель проще: привязать одну наблюдаемую строку лога к одному точному `receipt_id`, одному методу и одному исполнителю. -
- Стратегия + Ход

Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога.

@@ -205,10 +179,6 @@ curl -s "$TX_BASE_URL/v0/receipt" \
-**Цель** - -- Начать с одного mainnet tx hash и одного фрагмента лога и определить точную receipt, которая выдала этот лог. - Для этого зафиксированного mainnet-примера используйте: - хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` @@ -217,7 +187,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - ожидаемый executor: `wrap.near` - ожидаемый метод: `ft_resolve_transfer` -Эта транзакция полезна тем, что в ней есть две разные logged receipt внутри одной истории: +В этой транзакции есть две logged receipt внутри одной истории: - ранний лог `Transfer ...` на receipt с `ft_transfer_call` - более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` @@ -235,19 +205,9 @@ flowchart LR | Атрибуция лога | Transactions API [`POST /v0/transactions`](/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | | Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | -**Что должен включать полезный ответ** - -- какой `receipt_id` выдал лог -- какой контракт исполнил эту receipt -- какой метод там выполнился -- точную строку лога, которая совпала -- одно простое предложение вроде «лог `Refund` пришёл из `wrap.near` в receipt с методом `ft_resolve_transfer`» - #### Shell-сценарий атрибуции лога -Используйте этот сценарий, когда у вас уже есть tx hash и следующий вопрос звучит как «какая receipt это сказала?» - -**Что вы делаете** +**Ход** - Один раз получаете транзакцию и сохраняете список её receipt. - Фильтруете receipt по одному фрагменту лога. @@ -327,19 +287,19 @@ jq '{ Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. ### Превратить один страшный receipt ID из логов в понятную человеческую историю -Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. +Есть только `receipt_id` из логов или трассы? Сначала разрешите сам receipt, затем восстановите родительскую транзакцию. Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем.
- Стратегия + Ход

Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой.

@@ -349,11 +309,7 @@ Receipt-логи живут на уровне receipt, а не на каком-
-**Цель** - -- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. - -Для этого зафиксированного примера «страшный receipt ID из логов» такой: +Зафиксированный receipt из логов: - receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` - хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` @@ -362,7 +318,7 @@ Receipt-логи живут на уровне receipt, а не на каком- - высота блока транзакции: `194263342` - высота блока исполнения receipt: `194263343` -Человеческая история за этим receipt простая: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. +Короткий ответ: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. ```mermaid flowchart LR @@ -378,14 +334,6 @@ flowchart LR | История транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | | Каноническое продолжение | RPC [`tx`](/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | -**Что должен включать полезный ответ** - -- какие аккаунты создали и исполнили квитанцию -- к какой транзакции относится эта квитанция -- что транзакция на самом деле сделала -- была ли квитанция главным событием или только шагом в большом каскаде -- одно предложение простым языком, которое можно без правок вставить коллеге в чат - #### Shell-сценарий: от страшного receipt ID к человеческой истории ```bash @@ -468,7 +416,7 @@ jq -r ' В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** `POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. @@ -476,17 +424,15 @@ jq -r ' Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. -Используйте этот раздел, когда уже понятно, что транзакция жила дольше одной receipt, и следующий вопрос относится уже к форме исполнения, а не к простому поиску объекта. - ### Доказать, что одно неудачное действие сорвало весь пакет -Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? +Нужно проверить, закрепились ли ранние действия в неудачном батче? Используйте этот testnet-пример с `CreateAccount -> Transfer -> AddKey -> FunctionCall`. В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно.
- Стратегия + Ход

Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов.

@@ -496,10 +442,6 @@ jq -r '
-**Цель** - -- На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. - **Официальные ссылки** - [Основы транзакций](/transaction-flow/foundations) @@ -535,21 +477,11 @@ flowchart LR | Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | | Доказательство по состоянию после исполнения | RPC [`query(view_account)`](/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | -Перед shell-сценарием важно отметить одну деталь: индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. - -**Что должен включать полезный ответ** - -- точный порядок действий, который отправил signer -- какой индекс действия упал и почему -- высоту и хеш включающего блока для этого батча -- доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality -- короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` +Индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. #### Shell-сценарий неудачной транзакции с пакетом действий -Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. - -**Что вы делаете** +**Ход** - Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. - Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. @@ -657,19 +589,19 @@ jq '{ Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. ### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? -Используйте это расследование, когда один вызов контракта залогировал успех, изменил своё локальное состояние, и даже верхнеуровневый RPC `status` выглядит успешным, но приложение всё равно сломалось, потому что позже упал отдельный cross-contract receipt. +Нужно доказать, что позже упал отдельный cross-contract receipt, хотя первый вызов выглядел успешным? Используйте этот зафиксированный testnet-пример. Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt.
- Стратегия + Ход

Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась.

@@ -679,10 +611,6 @@ jq '{
-**Цель** - -- Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. - **Официальные ссылки** - [Основы транзакций](/transaction-flow/foundations) @@ -714,21 +642,11 @@ flowchart LR | Каркас транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | | Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | -Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. - -**Что должен включать полезный ответ** - -- что подписанная транзакция успешно передала управление в первую router-receipt -- что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` -- что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` -- что RPC всё ещё показывает верхнеуровневый `SuccessValue`, хотя более поздняя detached-receipt упала -- одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции +Успех receipt не транзитивен. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. #### Shell-сценарий более позднего сбоя receipt -Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». - -**Что вы делаете** +**Ход** - Читаете транзакцию и её таймлайн receipt из индексированного представления. - Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. @@ -829,24 +747,17 @@ jq \ Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. ### Дошёл ли callback вообще? -Используйте это расследование, когда одна транзакция запустила downstream-работу на другом контракте, а настоящий вопрос звучит не как «успешно ли отработал receiver?», а как «вернулся ли callback обратно в исходный контракт?» - -Это самый короткий полезный сценарий про callback на странице: - -- стартуйте с одного tx hash -- найдите downstream-receipt на другом контракте -- найдите более поздний callback-receipt, который вернулся в исходный контракт -- остановитесь, как только доказаны сам факт callback и его результат +Нужно проверить, вернулся ли callback в исходный контракт? Начните с этого mainnet-примера.
- Стратегия + Ход

Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а.

@@ -856,11 +767,7 @@ jq \
-**Цель** - -- Доказать на одном фиксированном mainnet-примере, что `wrap.near` отправил `ft_transfer_call` в `v2.ref-finance.near`, receiver выполнил `ft_on_transfer`, а затем `wrap.near` получил callback `ft_resolve_transfer` обратно. - -Этот фиксированный mainnet-пример с callback был замечен **19 апреля 2026 года**: +Зафиксированный mainnet-пример с callback замечен **19 апреля 2026 года**: - хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` - аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` @@ -888,19 +795,9 @@ flowchart LR | Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | | Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | -**Что должен включать полезный ответ** - -- какой контракт получил downstream-вызов -- какой метод выполнился на downstream-контракте -- вернулся ли более поздний receipt в исходный контракт -- какой callback-метод там выполнился и в каком блоке -- одно простое предложение вроде «receiver упал, но исходный контракт всё равно получил callback и завершил перевод» - #### Shell-сценарий проверки callback-а -Используйте этот сценарий, когда нужен один конкретный proof callback-а без превращения страницы в полный курс по теории promises. - -**Что вы делаете** +**Ход** - Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. - Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. @@ -1059,7 +956,7 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ # - лог callback-а показывает refund обратно отправителю ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/index.md index 34d185b..da4b19a 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/index.md @@ -50,7 +50,7 @@ https://tx.test.fastnear.com ## Нужен сценарий? -Используйте [примеры API транзакций](/tx/examples) для простых пошаговых сценариев: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. +Используйте [примеры API транзакций](/tx/examples) для практических примеров: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. ## Устранение неполадок diff --git a/static/ru/api.md b/static/ru/api.md index e390245..0524cda 100644 --- a/static/ru/api.md +++ b/static/ru/api.md @@ -48,7 +48,7 @@ https://test.api.fastnear.com ## Нужен сценарий? -Используйте [примеры FastNear API](https://docs.fastnear.com/ru/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, определения аккаунта по ключу и перехода к узким представлениям активов. +Используйте [примеры FastNear API](https://docs.fastnear.com/ru/api/examples) для практических примеров: сводки по аккаунтам, определения аккаунта по ключу и перехода к узким представлениям активов. ## Устранение неполадок diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index e0e0e00..588f7dd 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -1,21 +1,17 @@ **Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) -## Готовые сценарии - -Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. +## Примеры ### Определить аккаунт по публичному ключу, а затем получить сводку по нему -Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» - - Стратегия + Ход Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. 03GET /v1/account/.../full в одном ответе показывает балансы, NFT и стейкинг. -**Что вы делаете** +**Ход** - Ищете по публичному ключу один или несколько `account_id`. - Извлекаете первый найденный `account_id` через `jq`. @@ -47,15 +43,13 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. ### Показывает ли этот кошелёк прямой стейкинг, ликвидные стейкинг-токены или и то и другое? -Используйте этот сценарий, когда история звучит так: «покажи, видно ли по этому кошельку прямые позиции в staking pool, ликвидные стейкинг-токены или и то и другое». - - Стратегия + Ход Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. 01GET /v1/account/.../staking находит прямую экспозицию через пулы. @@ -73,7 +67,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. -**Что вы делаете** +**Ход** - Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. - Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. @@ -130,15 +124,13 @@ jq -n \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. ### Заархивировать версию BOS-виджета как provenance NFT -Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». - - Стратегия + Ход Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. @@ -156,7 +148,7 @@ jq -n \ - [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -**Что вы делаете** +**Ход** - Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. - Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. @@ -340,7 +332,7 @@ jq '{ }' /tmp/bos-widget-provenance-token.json ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index e0e0e00..588f7dd 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -1,21 +1,17 @@ **Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) -## Готовые сценарии - -Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. +## Примеры ### Определить аккаунт по публичному ключу, а затем получить сводку по нему -Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» - - Стратегия + Ход Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. 03GET /v1/account/.../full в одном ответе показывает балансы, NFT и стейкинг. -**Что вы делаете** +**Ход** - Ищете по публичному ключу один или несколько `account_id`. - Извлекаете первый найденный `account_id` через `jq`. @@ -47,15 +43,13 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. ### Показывает ли этот кошелёк прямой стейкинг, ликвидные стейкинг-токены или и то и другое? -Используйте этот сценарий, когда история звучит так: «покажи, видно ли по этому кошельку прямые позиции в staking pool, ликвидные стейкинг-токены или и то и другое». - - Стратегия + Ход Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. 01GET /v1/account/.../staking находит прямую экспозицию через пулы. @@ -73,7 +67,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. -**Что вы делаете** +**Ход** - Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. - Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. @@ -130,15 +124,13 @@ jq -n \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. ### Заархивировать версию BOS-виджета как provenance NFT -Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». - - Стратегия + Ход Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. @@ -156,7 +148,7 @@ jq -n \ - [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -**Что вы делаете** +**Ход** - Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. - Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. @@ -340,7 +332,7 @@ jq '{ }' /tmp/bos-widget-provenance-token.json ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). diff --git a/static/ru/api/index.md b/static/ru/api/index.md index e390245..0524cda 100644 --- a/static/ru/api/index.md +++ b/static/ru/api/index.md @@ -48,7 +48,7 @@ https://test.api.fastnear.com ## Нужен сценарий? -Используйте [примеры FastNear API](https://docs.fastnear.com/ru/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, определения аккаунта по ключу и перехода к узким представлениям активов. +Используйте [примеры FastNear API](https://docs.fastnear.com/ru/api/examples) для практических примеров: сводки по аккаунтам, определения аккаунта по ключу и перехода к узким представлениям активов. ## Устранение неполадок diff --git a/static/ru/fastdata/kv.md b/static/ru/fastdata/kv.md index 05bb44e..bfec141 100644 --- a/static/ru/fastdata/kv.md +++ b/static/ru/fastdata/kv.md @@ -74,7 +74,7 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY ## Нужен сценарий? -Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. +Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для практических примеров: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. ## Рабочий цикл по умолчанию diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index a7f89ce..6a47dc1 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -1,40 +1,18 @@ **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) -## Готовое расследование +## Пример ### Посмотреть индексированные записи одного `predecessor_id`, а затем сузиться до ключа, который изменился -Используйте это расследование, когда сначала известен предшественник, а настоящий вопрос звучит так: «что вообще записал этот `predecessor_id`, какая строка здесь действительно интересна и что потом происходило с этим ключом?» - - Стратегия + Ход Начните с области по `predecessor_id`, переходите к точному ключу только после того, как он заслужил внимание, а RPC оставляйте на самый конец. 01all-by-predecessor даёт последние индексированные строки для одного `predecessor_id` по затронутым контрактам. 02get-history-key или history-by-predecessor объясняют, как менялась интересующая строка во времени. 03RPC view_state — это уже необязательное точное чтение, когда нужен именно канонический current state, а не индексированная история. -**Цель** - -- Объяснить, что записал этот `predecessor_id`, какой точный ключ стал настоящим фокусом, как он менялся и нужен ли вам вообще финальный `view_state`. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Последние индексированные строки по области | KV FastData [`all-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) | Сначала получаем текущие индексированные записи одного `predecessor_id` по затронутым контрактам | Отвечает на вопрос по области раньше, чем вы начинаете притворяться, будто точный ключ уже известен | -| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Забираем историю точного ключа или ещё на один шаг оставляем историю шире, на уровне предшественника | Показывает, стабильно ли интересующее значение, насколько оно недавнее и не входит ли в более широкий паттерн записей | -| Точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | - -**Что должен включать полезный ответ** - -- с какой области по `predecessor_id` вы начали -- какой точный ключ стал настоящим фокусом -- как этот ключ менялся в истории -- нужен ли вообще финальный `view_state` - ### Shell-сценарий по области предшественника - -Используйте этот сценарий, когда сначала известен один `predecessor_id` и нужно аккуратно перейти от вопроса «что он вообще записал?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» - -**Что вы делаете** +**Ход** - Читаете последние индексированные строки для одного `predecessor_id` по затронутым контрактам. - Поднимаете интересующие `current_account_id` и точный `key` через `jq`. @@ -83,7 +61,7 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KE }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Первый запрос отвечает на вопрос по области: «что этот `predecessor_id` сейчас пишет?». Сужение из этой ленты до одного точного ключа отвечает на более точный вопрос: «как именно эта строка дошла до такого состояния?». Если картина всё ещё шире одного ключа, ещё немного побудьте на [History by Predecessor](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), а уже потом переходите к точной истории ключа или RPC. diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index a7f89ce..6a47dc1 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -1,40 +1,18 @@ **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) -## Готовое расследование +## Пример ### Посмотреть индексированные записи одного `predecessor_id`, а затем сузиться до ключа, который изменился -Используйте это расследование, когда сначала известен предшественник, а настоящий вопрос звучит так: «что вообще записал этот `predecessor_id`, какая строка здесь действительно интересна и что потом происходило с этим ключом?» - - Стратегия + Ход Начните с области по `predecessor_id`, переходите к точному ключу только после того, как он заслужил внимание, а RPC оставляйте на самый конец. 01all-by-predecessor даёт последние индексированные строки для одного `predecessor_id` по затронутым контрактам. 02get-history-key или history-by-predecessor объясняют, как менялась интересующая строка во времени. 03RPC view_state — это уже необязательное точное чтение, когда нужен именно канонический current state, а не индексированная история. -**Цель** - -- Объяснить, что записал этот `predecessor_id`, какой точный ключ стал настоящим фокусом, как он менялся и нужен ли вам вообще финальный `view_state`. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Последние индексированные строки по области | KV FastData [`all-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) | Сначала получаем текущие индексированные записи одного `predecessor_id` по затронутым контрактам | Отвечает на вопрос по области раньше, чем вы начинаете притворяться, будто точный ключ уже известен | -| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Забираем историю точного ключа или ещё на один шаг оставляем историю шире, на уровне предшественника | Показывает, стабильно ли интересующее значение, насколько оно недавнее и не входит ли в более широкий паттерн записей | -| Точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | - -**Что должен включать полезный ответ** - -- с какой области по `predecessor_id` вы начали -- какой точный ключ стал настоящим фокусом -- как этот ключ менялся в истории -- нужен ли вообще финальный `view_state` - ### Shell-сценарий по области предшественника - -Используйте этот сценарий, когда сначала известен один `predecessor_id` и нужно аккуратно перейти от вопроса «что он вообще записал?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» - -**Что вы делаете** +**Ход** - Читаете последние индексированные строки для одного `predecessor_id` по затронутым контрактам. - Поднимаете интересующие `current_account_id` и точный `key` через `jq`. @@ -83,7 +61,7 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KE }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Первый запрос отвечает на вопрос по области: «что этот `predecessor_id` сейчас пишет?». Сужение из этой ленты до одного точного ключа отвечает на более точный вопрос: «как именно эта строка дошла до такого состояния?». Если картина всё ещё шире одного ключа, ещё немного побудьте на [History by Predecessor](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), а уже потом переходите к точной истории ключа или RPC. diff --git a/static/ru/fastdata/kv/index.md b/static/ru/fastdata/kv/index.md index 05bb44e..bfec141 100644 --- a/static/ru/fastdata/kv/index.md +++ b/static/ru/fastdata/kv/index.md @@ -74,7 +74,7 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY ## Нужен сценарий? -Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. +Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для практических примеров: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. ## Рабочий цикл по умолчанию diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index 0f48233..183a1c2 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -13,10 +13,10 @@ - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Практические примеры для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Недавние чтения по блокам и шардам для мониторинга активности контракта, подтверждения оптимистичных наблюдений и проверки изменений на уровне шарда. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. -- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. +- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Практические примеры FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. - [API переводов](https://docs.fastnear.com/ru/transfers.md): История переводов NEAR и FT-токенов по аккаунтам для продуктовых лент и инструментов расследования. - [Транзакции API](https://docs.fastnear.com/ru/tx.md): Индексированные запросы по транзакциям, квитанциям, истории аккаунтов и истории блоков для FastNear. @@ -29,12 +29,12 @@ ## Другие гайды -- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга. +- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Практические примеры FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для мониторинга недавней активности контракта, подтверждения optimistic-наблюдений и проверки изменений на уровне shard. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Практические примеры для мониторинга недавней активности контракта, подтверждения optimistic-наблюдений и проверки изменений на уровне shard. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. -- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. -- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Практические примеры для поиска переводов, пагинации через resume_token и перехода к истории транзакций. +- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Практические расследования транзакций для типовых задач разработчика. - [Berry Club: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Прочитайте живую доску Berry Club через RPC get_lines, а затем используйте Transactions API, чтобы восстановить одну более раннюю эпоху. - [OutLayer: что сделала эта пара request/resolution?](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы прочитать один caller-side запрос OutLayer, одно более позднее worker-side resolution и обращаться к finish-receipts только когда это действительно нужно. - [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs.md): Один короткий расширенный сценарий, который начинается с читаемого значения SocialDB и восстанавливает транзакцию записи за ним. @@ -42,6 +42,6 @@ ## Снапшоты - [Снапшоты для валидаторов](https://docs.fastnear.com/ru/snapshots.md): Пути загрузки снапшотов FastNear для подъёма и восстановления узлов NEAR. -- [Примеры Snapshot](https://docs.fastnear.com/ru/snapshots/examples.md): Пошаговые операторские сценарии для выбора правильного пути восстановления через FastNear snapshots. +- [Примеры Snapshot](https://docs.fastnear.com/ru/snapshots/examples.md): Практические примеры восстановления для выбора правильного пути через FastNear snapshots. - [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. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 25b6600..ea193c7 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -969,7 +969,7 @@ https://test.api.fastnear.com ## Нужен сценарий? -Используйте [примеры FastNear API](https://docs.fastnear.com/ru/api/examples) для простых пошаговых сценариев: сводки по аккаунтам, определения аккаунта по ключу и перехода к узким представлениям активов. +Используйте [примеры FastNear API](https://docs.fastnear.com/ru/api/examples) для практических примеров: сводки по аккаунтам, определения аккаунта по ключу и перехода к узким представлениям активов. ## Устранение неполадок @@ -994,22 +994,18 @@ https://test.api.fastnear.com **Источник:** [https://docs.fastnear.com/ru/api/examples](https://docs.fastnear.com/ru/api/examples) -## Готовые сценарии - -Читайте эту страницу как короткую лестницу: сначала определите, что это за аккаунт, затем классифицируйте форму кошелька, а потом переходите к более насыщенному сценарию происхождения, если хотите превратить живой BOS-артефакт в отчеканенную запись. +## Примеры ### Определить аккаунт по публичному ключу, а затем получить сводку по нему -Используйте этот сценарий, когда у вас сначала есть только публичный ключ, а следующий практический вопрос пользователя звучит как «какому аккаунту он соответствует?» и сразу после этого «что сейчас видно по этому аккаунту?» - - Стратегия + Ход Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. 03GET /v1/account/.../full в одном ответе показывает балансы, NFT и стейкинг. -**Что вы делаете** +**Ход** - Ищете по публичному ключу один или несколько `account_id`. - Извлекаете первый найденный `account_id` через `jq`. @@ -1041,15 +1037,13 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. ### Показывает ли этот кошелёк прямой стейкинг, ликвидные стейкинг-токены или и то и другое? -Используйте этот сценарий, когда история звучит так: «покажи, видно ли по этому кошельку прямые позиции в staking pool, ликвидные стейкинг-токены или и то и другое». - - Стратегия + Ход Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. 01GET /v1/account/.../staking находит прямую экспозицию через пулы. @@ -1067,7 +1061,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. -**Что вы делаете** +**Ход** - Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. - Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. @@ -1124,15 +1118,13 @@ jq -n \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. ### Заархивировать версию BOS-виджета как provenance NFT -Используйте этот сценарий, когда история звучит так: «этот BOS-виджет — реальный on-chain-артефакт. Хочу выпустить NFT, который фиксирует, какую именно версию я заархивировал». - - Стратегия + Ход Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. @@ -1150,7 +1142,7 @@ jq -n \ - [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -**Что вы делаете** +**Ход** - Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. - Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. @@ -1334,7 +1326,7 @@ jq '{ }' /tmp/bos-widget-provenance-token.json ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). @@ -1595,7 +1587,7 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY ## Нужен сценарий? -Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для простых пошаговых сценариев: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. +Используйте [примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples) для практических примеров: поиска по точному ключу, истории ключей, анализа по `predecessor_id` и перехода к каноническому RPC. ## Рабочий цикл по умолчанию @@ -1638,41 +1630,19 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) -## Готовое расследование +## Пример ### Посмотреть индексированные записи одного `predecessor_id`, а затем сузиться до ключа, который изменился -Используйте это расследование, когда сначала известен предшественник, а настоящий вопрос звучит так: «что вообще записал этот `predecessor_id`, какая строка здесь действительно интересна и что потом происходило с этим ключом?» - - Стратегия + Ход Начните с области по `predecessor_id`, переходите к точному ключу только после того, как он заслужил внимание, а RPC оставляйте на самый конец. 01all-by-predecessor даёт последние индексированные строки для одного `predecessor_id` по затронутым контрактам. 02get-history-key или history-by-predecessor объясняют, как менялась интересующая строка во времени. 03RPC view_state — это уже необязательное точное чтение, когда нужен именно канонический current state, а не индексированная история. -**Цель** - -- Объяснить, что записал этот `predecessor_id`, какой точный ключ стал настоящим фокусом, как он менялся и нужен ли вам вообще финальный `view_state`. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Последние индексированные строки по области | KV FastData [`all-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) | Сначала получаем текущие индексированные записи одного `predecessor_id` по затронутым контрактам | Отвечает на вопрос по области раньше, чем вы начинаете притворяться, будто точный ключ уже известен | -| История индексированного ключа | KV FastData [`get-history-key`](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) или [`history-by-predecessor`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor) | Забираем историю точного ключа или ещё на один шаг оставляем историю шире, на уровне предшественника | Показывает, стабильно ли интересующее значение, насколько оно недавнее и не входит ли в более широкий паттерн записей | -| Точная проверка состояния | RPC [`view_state`](https://docs.fastnear.com/ru/rpc/contract/view-state) | Подтверждаем текущее состояние в цепочке, когда индексированная картина уже понятна | Разводит индексированную историю и точное состояние, которое цепочка вернёт прямо сейчас | - -**Что должен включать полезный ответ** - -- с какой области по `predecessor_id` вы начали -- какой точный ключ стал настоящим фокусом -- как этот ключ менялся в истории -- нужен ли вообще финальный `view_state` - ### Shell-сценарий по области предшественника - -Используйте этот сценарий, когда сначала известен один `predecessor_id` и нужно аккуратно перейти от вопроса «что он вообще записал?» к вопросу «как этот конкретный ключ дошёл до такого состояния?» - -**Что вы делаете** +**Ход** - Читаете последние индексированные строки для одного `predecessor_id` по затронутым контрактам. - Поднимаете интересующие `current_account_id` и точный `key` через `jq`. @@ -1721,7 +1691,7 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KE }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Первый запрос отвечает на вопрос по области: «что этот `predecessor_id` сейчас пишет?». Сужение из этой ленты до одного точного ключа отвечает на более точный вопрос: «как именно эта строка дошла до такого состояния?». Если картина всё ещё шире одного ключа, ещё немного побудьте на [History by Predecessor](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), а уже потом переходите к точной истории ключа или RPC. @@ -2129,7 +2099,7 @@ https://testnet.neardata.xyz ## Нужен сценарий? -Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для компактных сценариев: обнаружения активности контракта, сравнения оптимистичных и финализированных наблюдений, а также проверки изменений на уровне шарда. +Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для практических примеров: обнаружения активности контракта, сравнения оптимистичных и финализированных наблюдений, а также проверки изменений на уровне шарда. ## Устранение неполадок @@ -2154,15 +2124,13 @@ https://testnet.neardata.xyz **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) -NEAR Data особенно хороша там, где вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. +Используйте NEAR Data, когда вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. -## Готовые расследования +## Примеры ### Был ли мой контракт затронут в последнем финализированном блоке? -Используйте это, когда приложению, боту или инструменту поддержки нужен один быстрый ответ о живом контракте. Мы будем проверять `intents.near`, но та же сводка работает для любого аккаунта контракта. - - Стратегия + Ход Сначала дайте NEAR Data ответить на задачу мониторинга, а уже потом сохраняйте tx hash или receipt ID для следующей поверхности, если это вообще понадобится. 01last-block-final находит самую новую финализированную высоту. @@ -2223,7 +2191,7 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | contract_touch_summary "$TARGET_CONTRACT" ``` -Читать ответ стоит так: +Читайте ответ так: - `touched: false` означает, что самый новый финализированный блок не упомянул и не изменил контракт ни одним из отслеживаемых способов. - `sample_tx_hash` означает, что у вас уже есть хороший якорь для следующего шага на `/tx`. @@ -2231,9 +2199,7 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ ### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? -Используйте это, когда нужен ранний сигнал по живому контракту, но стабильный ответ всё равно должен пройти через финализированное подтверждение. - - Стратегия + Ход Используйте один и тот же словарь contract-touch на обеих поверхностях, чтобы сравнение было честным. 01last-block-optimistic находит самую новую optimistic-высоту. @@ -2304,9 +2270,7 @@ fi ### Какой shard действительно изменил мой контракт в этом блоке? -Используйте это, когда недавний блок уже показал активность контракта, и теперь нужно доказательство на уровне shard того, где именно изменение реально приземлилось. - - Стратегия + Ход Сначала используйте весь блок, чтобы найти нужный shard, а затем дайте block-shard доказать само изменение. 01Просканируйте список shard внутри финализированного блока и найдите state_changes по вашему контракту. @@ -2471,7 +2435,7 @@ https://archival-rpc.testnet.fastnear.com ## Нужен сценарий? -Используйте [примеры RPC](https://docs.fastnear.com/ru/rpc/examples) для простых пошаговых сценариев: точных проверок состояния, анализа блоков, view-вызовов контрактов и отправки транзакций с подтверждением. +Используйте [примеры RPC](https://docs.fastnear.com/ru/rpc/examples) для практических примеров: точных проверок состояния, анализа блоков, view-вызовов контрактов и отправки транзакций с подтверждением. ## Используйте RPC, когда @@ -2526,17 +2490,15 @@ https://archival-rpc.testnet.fastnear.com # Примеры RPC -Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. +Начинайте с RPC-метода, который отвечает на вопрос. Отправляйте через `broadcast_tx_async`, отслеживайте через `tx` и расширяйте разбор только когда действительно нужны дерево receipts, raw state или трассировка по шардам. ## Отправка и отслеживание транзакции -Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» - ### Отправить транзакцию и затем проследить её от хеша до финального исполнения -Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. +Нужен стандартный путь отправки через RPC? Отправляйте через `broadcast_tx_async`, затем опрашивайте `tx`. К `EXPERIMENTAL_tx_status` переходите только если нужно дерево receipts. -Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: +Зафиксированная mainnet-транзакция: - хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - signer: `mike.near` @@ -2544,16 +2506,14 @@ https://archival-rpc.testnet.fastnear.com - высота блока включения: `79574923` - высота блока исполнения receipt для записи в SocialDB: `79574924` -Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. - - Стратегия + Ход Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. -**Что вы здесь решаете** +**Точки выбора** - какой эндпоинт отправки брать первым - что опрашивать после того, как у вас появился tx hash @@ -2589,13 +2549,13 @@ flowchart LR | `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | | `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | -Практическое различие очень простое: +Используйте методы так: - используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash - используйте `tx` как обычный цикл опроса - используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу -**Что вы делаете** +**Ход** - Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. - Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. @@ -2748,16 +2708,16 @@ curl -s "$TX_BASE_URL/v0/transactions" \ ### Проверить и удалить старые function-call-ключи Near Social -Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. +Есть старые function-call-ключи для `social.near`? Сначала найдите нужный ключ, потом удалите один конкретный. - Стратегия + Ход Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. -**Что вы делаете** +**Ход** - Через сам RPC получаете полный список access key аккаунта. - Сужаете этот список до function-call-ключей, привязанных к `social.near`. @@ -2991,22 +2951,22 @@ else fi ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. ### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? -Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. +Ключ уже виден на аккаунте? Вернитесь к транзакции `AddKey`, которая его создала. - Стратегия + Ход Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. -**Что вы делаете** +**Ход** - Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. - Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. @@ -3204,15 +3164,15 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. ### Проверить регистрацию FT storage и затем перевести токены -Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». +Нужно отправить FT безопасно? Сначала проверьте storage registration получателя. - Стратегия + Ход Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. @@ -3230,7 +3190,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. -**Что вы делаете** +**Ход** - Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. - При необходимости получаете минимальный размер storage deposit. @@ -3511,7 +3471,7 @@ curl -s "$RPC_URL" \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. @@ -3521,14 +3481,14 @@ curl -s "$RPC_URL" \ ### Как прочитать сырое состояние контракта напрямую? -Используйте этот сценарий, когда у контракта нет нужного view-метода или когда вам нужно проверить саму схему хранения, а не просто довериться ответу метода. +Нужен raw storage, а не только view-метод? Читайте состояние напрямую, затем сравнивайте с view. -В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: сначала вы читаете storage напрямую, а потом подтверждаете, что публичный view-метод контракта даёт тот же ответ: +Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`. Значение может меняться; важна последовательность чтений: сначала raw storage, потом проверка через view-метод. - `view_state` читает сырой ключ `STATE` прямо из storage контракта - `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API - Стратегия + Ход Сначала прочитайте storage напрямую, а уже потом дайте контракту подтвердить тот же ответ через view-метод. 01RPC view_state читает сырой ключ STATE, не запуская код контракта. @@ -3552,7 +3512,7 @@ flowchart LR X --> A["Одно и то же текущее значение"] ``` -**Что вы делаете** +**Ход** - Читаете сырой ключ `STATE` из storage контракта. - Декодируете возвращённые байты в текущее знаковое значение счётчика. @@ -3679,7 +3639,7 @@ jq -n \ - `view_state` ответил на вопрос, прочитав storage напрямую - `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Используйте `view_state`, когда настоящий вопрос относится к точному storage, отсутствующему view-методу или проверке известного семейства ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). @@ -3689,9 +3649,9 @@ jq -n \ ### Проследить, как сгенерированная `Transfer`-receipt переходит из одного чанка на шарде в другой -Используйте этот сценарий, когда вызов контракта был только началом истории. В этом зафиксированном mainnet-примере подписанная транзакция стартует на шарде `11`, а сгенерированная `Transfer`-receipt заканчивает путь уже на шарде `6`. Именно ради таких cross-shard handoff и имеет смысл смотреть на чанки. +Нужен cross-shard handoff, а не только исходная транзакция? Смотрите чанки. -Этот walkthrough привязан к: +Зафиксированный mainnet-пример: - транзакции `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` от `7419369993.tg` к `game.hot.tg` с вызовом `l2_claim` - исходному чанку `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` на шарде `11` в блоке `194623170` @@ -3699,7 +3659,7 @@ jq -n \ - сгенерированной `Transfer`-receipt `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` - конечному чанку `EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU` на шарде `6` в блоке `194623172` - Стратегия + Ход Сначала восстановите receipt-цепочку, потом напрямую посмотрите на сгенерированную receipt, а затем привяжите каждый шаг к тому чанку на шарде, который действительно нёс эту работу. 01RPC EXPERIMENTAL_tx_status быстро показывает граф receipts и в какие следующие блоки перешла работа. @@ -3715,7 +3675,7 @@ flowchart LR C --> D["Чанк EPau...
блок 194623172
шард 6
receipt исполняется"] ``` -**Что вы делаете** +**Ход** - Сначала восстанавливаете receipt-цепочку из транзакции. - Напрямую смотрите на тело сгенерированной `Transfer`-receipt. @@ -3920,7 +3880,7 @@ curl -s "$RPC_URL" \ - этот чанк живёт на шарде `6`, а не на шарде `11` - подписанная транзакция стартовала на одном шарде, а следующая receipt завершилась уже на другом -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Используйте [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда вы знаете координаты блока и шарда и хотите буквально спросить: «что этот шард исполнил в этом блоке?» Используйте [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. Используйте [EXPERIMENTAL_tx_status](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) и [EXPERIMENTAL_receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), когда настоящий вопрос относится к трассировке на уровне receipts. Если ещё нужны state changes и produced receipts, расширяйтесь в [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard). @@ -3930,9 +3890,9 @@ curl -s "$RPC_URL" \ ### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? -Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». +Нужно понять «готово / не готово» до окна подписи? Проверьте storage и права до отправки. - Стратегия + Ход Спросите у social.near ровно о двух вещах, которые важны до подписи. 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. @@ -3949,7 +3909,7 @@ curl -s "$RPC_URL" \ - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -**Что вы делаете** +**Ход** - Проверяете, что аккаунт signer вообще существует и способен оплатить gas. - Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. @@ -4121,15 +4081,15 @@ jq -n \ Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. ### Что прямо сейчас содержит `mob.near/widget/Profile`? -Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». +Нужен живой исходник виджета и последняя запись этого ключа? Оставайтесь на точных RPC-чтениях. - Стратегия + Ход Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. @@ -4140,7 +4100,7 @@ jq -n \ - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -**Что вы делаете** +**Ход** - Спрашиваете у `social.near` каталог виджетов под `mob.near`. - Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. @@ -4255,7 +4215,7 @@ jq -r \ На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. @@ -4447,7 +4407,7 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash ## Нужен сценарий? -Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. +Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для практических примеров: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. ## Выберите сеть @@ -4476,44 +4436,19 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash ``` -Это самый короткий путь восстановления на странице. Всё остальное нужно только если вам нужен standard RPC или архивный hot/cold-сценарий. - -## Готовое расследование +## Пример ### Выбрать и выполнить правильный сценарий восстановления mainnet -Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. - - Стратегия + Ход Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него. 01Сначала решите, нужен ли вам optimized fast-rpc, обычный RPC или архивный режим. 02Если нужен архив, сначала зафиксируйте одну точную высоту snapshot-блока и дальше переиспользуйте только её. 03Выполняйте только команды выбранного пути и не смешивайте optimized, standard и archival шаги в одном сценарии. -**Цель** - -- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. - -| Путь или команда | Как используем | Зачем используем | -| --- | --- | --- | -| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | -| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | -| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | -| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | -| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | - -**Что должен включать полезный ответ** - -- какой сценарий восстановления выбран и почему -- какие ключевые env vars важны для выбранного пути -- куда на диске должны попасть данные -- должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap - ### Минимальная команда для optimized mainnet `fast-rpc` -Используйте этот якорь, когда уже ясно, что нужен высокопроизводительный mainnet RPC-узел, и требуется только минимальная рабочая команда. - ```bash DATA_PATH=~/.near/data @@ -4523,8 +4458,6 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ ### Минимальная команда для стандартного mainnet RPC -Используйте этот якорь, когда нужен обычный путь восстановления mainnet RPC без optimized-профиля `fast-rpc`. - ```bash DATA_PATH=~/.near/data @@ -4533,10 +4466,9 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ ``` ### Shell-сценарий архивного восстановления mainnet +Для архивного восстановления сначала получите одну высоту snapshot и переиспользуйте её для hot- и cold-data. -Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. - -**Что вы делаете** +**Ход** - Один раз получаете последнюю высоту архивного снапшота mainnet. - Сохраняете её в `LATEST`. @@ -4556,7 +4488,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_TYPE=cold-data DATA_PATH="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. @@ -4869,7 +4801,7 @@ https://transfers.main.fastnear.com ## Нужен сценарий? -Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для простых пошаговых сценариев: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. +Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для практических примеров: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. ## Устранение неполадок @@ -4890,13 +4822,11 @@ https://transfers.main.fastnear.com **Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) -## Готовый сценарий +## Пример ### Отфильтровать и листать ленту переводов одного аккаунта -Используйте этот сценарий, когда история звучит так: «покажи мне осмысленную ленту переводов этого аккаунта, дай мне спокойно листать её дальше, и только потом, если нужно, помоги догнать одну строку до истории исполнения». - - Стратегия + Ход Сначала соберите саму ленту аккаунта, а `receipt` поднимайте только тогда, когда одна строка действительно требует истории исполнения. 01POST /v0/transfers даёт первую страницу отфильтрованной ленты одного аккаунта. @@ -4907,7 +4837,7 @@ https://transfers.main.fastnear.com - только mainnet -**Что вы делаете** +**Ход** - Забираете первую страницу одной отфильтрованной ленты переводов для выбранного аккаунта. - Используете сами параметры ленты как главный учебный материал: `account_id`, `direction`, `asset_id`, `min_amount`, `desc` и `limit`. @@ -4970,7 +4900,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Запрос переводов напрямую отвечает на первый вопрос: как сейчас выглядит отфильтрованная лента этого аккаунта и как её продолжать без потери места? Только после того как сама лента подскажет, какая строка действительно важна, имеет смысл переходить по `receipt_id` и забирать историю исполнения из `/tx`. @@ -5098,7 +5028,7 @@ https://tx.test.fastnear.com ## Нужен сценарий? -Используйте [примеры API транзакций](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. +Используйте [примеры API транзакций](https://docs.fastnear.com/ru/tx/examples) для практических примеров: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. ## Устранение неполадок @@ -5150,30 +5080,18 @@ curl -s "$TX_BASE_URL/v0/transactions" \ }' ``` -Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. - ## С чего начать -Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. - ### У меня есть один хеш транзакции. Что вообще произошло? -Используйте это расследование, когда история максимально простая: «мне прислали один хеш транзакции. Я просто хочу понять, сработала ли она, что именно сделала и в какой блок попала». - -Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. - - Стратегия + Ход Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно. 01POST /v0/transactions даёт signer, receiver, типы действий, высоту блока и первую receipt-точку передачи. 02RPC EXPERIMENTAL_tx_status нужен только для точной протокольной семантики успеха. 03POST /v0/receipt имеет смысл только тогда, когда именно первая receipt становится новой опорной точкой. -**Цель** - -- Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. - -Для этого зафиксированного примера: +Зафиксированный пример: - хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` - signer: `mike.near` @@ -5181,7 +5099,7 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - высота включающего блока: `194263342` - ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` -Простой человеческий ответ для этого случая такой: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. +Короткий ответ: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. ```mermaid flowchart LR @@ -5197,19 +5115,9 @@ flowchart LR | Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | | Переход к receipt | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | -**Что должен включать полезный ответ** - -- кто подписал транзакцию -- какой аккаунт её получил -- какой тип действия она несла -- в какой блок попала -- одно простое предложение, которое объясняет транзакцию без receipt-жаргона - #### Shell-сценарий: от хеша транзакции к человеческой истории -Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. - -**Что вы делаете** +**Ход** - Получаете транзакцию по хешу и печатаете её основные поля. - Подтверждаете финальный статус только если нужны точные RPC-семантики. @@ -5293,27 +5201,19 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** `POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. ### Какая receipt выдала этот лог или event? -Используйте это расследование, когда история звучит так: «у меня есть один tx hash и один фрагмент лога, и мне нужно точно понять, какая именно receipt его выдала». - -Это другой вопрос, чем более поздний сценарий «дошёл ли callback?». Здесь цель проще: привязать одну наблюдаемую строку лога к одному точному `receipt_id`, одному методу и одному исполнителю. - - Стратегия + Ход Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога. 01POST /v0/transactions даёт полный индексированный список receipt для одного tx hash, включая receipt-логи. 02jq сужает этот список до receipt, в логах которых встречается нужный вам фрагмент. 03Как только совпадение осталось одно, сохраняйте его receipt_id, executor и имя метода как точный ответ. -**Цель** - -- Начать с одного mainnet tx hash и одного фрагмента лога и определить точную receipt, которая выдала этот лог. - Для этого зафиксированного mainnet-примера используйте: - хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` @@ -5322,7 +5222,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - ожидаемый executor: `wrap.near` - ожидаемый метод: `ft_resolve_transfer` -Эта транзакция полезна тем, что в ней есть две разные logged receipt внутри одной истории: +В этой транзакции есть две logged receipt внутри одной истории: - ранний лог `Transfer ...` на receipt с `ft_transfer_call` - более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` @@ -5340,19 +5240,9 @@ flowchart LR | Атрибуция лога | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | | Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | -**Что должен включать полезный ответ** - -- какой `receipt_id` выдал лог -- какой контракт исполнил эту receipt -- какой метод там выполнился -- точную строку лога, которая совпала -- одно простое предложение вроде «лог `Refund` пришёл из `wrap.near` в receipt с методом `ft_resolve_transfer`» - #### Shell-сценарий атрибуции лога -Используйте этот сценарий, когда у вас уже есть tx hash и следующий вопрос звучит как «какая receipt это сказала?» - -**Что вы делаете** +**Ход** - Один раз получаете транзакцию и сохраняете список её receipt. - Фильтруете receipt по одному фрагменту лога. @@ -5432,28 +5322,24 @@ jq '{ Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. ### Превратить один страшный receipt ID из логов в понятную человеческую историю -Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. +Есть только `receipt_id` из логов или трассы? Сначала разрешите сам receipt, затем восстановите родительскую транзакцию. Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. - Стратегия + Ход Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой. 01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt. 02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий. 03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола». -**Цель** - -- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. - -Для этого зафиксированного примера «страшный receipt ID из логов» такой: +Зафиксированный receipt из логов: - receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` - хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` @@ -5462,7 +5348,7 @@ Receipt-логи живут на уровне receipt, а не на каком- - высота блока транзакции: `194263342` - высота блока исполнения receipt: `194263343` -Человеческая история за этим receipt простая: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. +Короткий ответ: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. ```mermaid flowchart LR @@ -5478,14 +5364,6 @@ flowchart LR | История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | | Каноническое продолжение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | -**Что должен включать полезный ответ** - -- какие аккаунты создали и исполнили квитанцию -- к какой транзакции относится эта квитанция -- что транзакция на самом деле сделала -- была ли квитанция главным событием или только шагом в большом каскаде -- одно предложение простым языком, которое можно без правок вставить коллеге в чат - #### Shell-сценарий: от страшного receipt ID к человеческой истории ```bash @@ -5568,7 +5446,7 @@ jq -r ' В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** `POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. @@ -5576,25 +5454,19 @@ jq -r ' Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. -Используйте этот раздел, когда уже понятно, что транзакция жила дольше одной receipt, и следующий вопрос относится уже к форме исполнения, а не к простому поиску объекта. - ### Доказать, что одно неудачное действие сорвало весь пакет -Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? +Нужно проверить, закрепились ли ранние действия в неудачном батче? Используйте этот testnet-пример с `CreateAccount -> Transfer -> AddKey -> FunctionCall`. В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. - Стратегия + Ход Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов. 01POST /v0/transactions показывает упорядоченный пакет ровно в том виде, в каком его подписал signer. 02RPC EXPERIMENTAL_tx_status показывает падающий FunctionCall и точную причину отказа на уровне протокола. 03RPC view_account по предполагаемому новому аккаунту доказывает, закрепились ли вообще ранние create, fund и add-key действия. -**Цель** - -- На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. - **Официальные ссылки** - [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) @@ -5630,21 +5502,11 @@ flowchart LR | Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | | Доказательство по состоянию после исполнения | RPC [`query(view_account)`](https://docs.fastnear.com/ru/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | -Перед shell-сценарием важно отметить одну деталь: индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. - -**Что должен включать полезный ответ** - -- точный порядок действий, который отправил signer -- какой индекс действия упал и почему -- высоту и хеш включающего блока для этого батча -- доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality -- короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` +Индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. #### Shell-сценарий неудачной транзакции с пакетом действий -Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. - -**Что вы делаете** +**Ход** - Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. - Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. @@ -5752,27 +5614,23 @@ jq '{ Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. ### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? -Используйте это расследование, когда один вызов контракта залогировал успех, изменил своё локальное состояние, и даже верхнеуровневый RPC `status` выглядит успешным, но приложение всё равно сломалось, потому что позже упал отдельный cross-contract receipt. +Нужно доказать, что позже упал отдельный cross-contract receipt, хотя первый вызов выглядел успешным? Используйте этот зафиксированный testnet-пример. Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. - Стратегия + Ход Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась. 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. 03Как только эти два представления сходятся на одном и том же разрезе истории, остановитесь. Этот пример держится за сохранённые исторические свидетельства, а не за живой read состояния роутера. -**Цель** - -- Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. - **Официальные ссылки** - [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) @@ -5804,21 +5662,11 @@ flowchart LR | Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | | Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | -Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. - -**Что должен включать полезный ответ** - -- что подписанная транзакция успешно передала управление в первую router-receipt -- что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` -- что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` -- что RPC всё ещё показывает верхнеуровневый `SuccessValue`, хотя более поздняя detached-receipt упала -- одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции +Успех receipt не транзитивен. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. #### Shell-сценарий более позднего сбоя receipt -Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». - -**Что вы делаете** +**Ход** - Читаете транзакцию и её таймлайн receipt из индексированного представления. - Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. @@ -5919,33 +5767,22 @@ jq \ Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. ### Дошёл ли callback вообще? -Используйте это расследование, когда одна транзакция запустила downstream-работу на другом контракте, а настоящий вопрос звучит не как «успешно ли отработал receiver?», а как «вернулся ли callback обратно в исходный контракт?» - -Это самый короткий полезный сценарий про callback на странице: +Нужно проверить, вернулся ли callback в исходный контракт? Начните с этого mainnet-примера. -- стартуйте с одного tx hash -- найдите downstream-receipt на другом контракте -- найдите более поздний callback-receipt, который вернулся в исходный контракт -- остановитесь, как только доказаны сам факт callback и его результат - - Стратегия + Ход Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а. 01POST /v0/transactions показывает downstream-вызов и более поздний receipt, который возвращается в исходный контракт. 02jq сужает этот список receipt до одного downstream-вызова и одного callback-receipt. 03RPC EXPERIMENTAL_tx_status нужен только как дополнительное подтверждение, если вам важны канонический результат callback-а и его логи. -**Цель** - -- Доказать на одном фиксированном mainnet-примере, что `wrap.near` отправил `ft_transfer_call` в `v2.ref-finance.near`, receiver выполнил `ft_on_transfer`, а затем `wrap.near` получил callback `ft_resolve_transfer` обратно. - -Этот фиксированный mainnet-пример с callback был замечен **19 апреля 2026 года**: +Зафиксированный mainnet-пример с callback замечен **19 апреля 2026 года**: - хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` - аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` @@ -5973,19 +5810,9 @@ flowchart LR | Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | | Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | -**Что должен включать полезный ответ** - -- какой контракт получил downstream-вызов -- какой метод выполнился на downstream-контракте -- вернулся ли более поздний receipt в исходный контракт -- какой callback-метод там выполнился и в каком блоке -- одно простое предложение вроде «receiver упал, но исходный контракт всё равно получил callback и завершил перевод» - #### Shell-сценарий проверки callback-а -Используйте этот сценарий, когда нужен один конкретный proof callback-а без превращения страницы в полный курс по теории promises. - -**Что вы делаете** +**Ход** - Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. - Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. @@ -6144,7 +5971,7 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ # - лог callback-а показывает refund обратно отправителю ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. diff --git a/static/ru/llms.txt b/static/ru/llms.txt index f84fefa..085ce72 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -16,10 +16,10 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Пошаговые сценарии для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Практические примеры для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Недавние чтения по блокам и шардам для мониторинга активности контракта, подтверждения оптимистичных наблюдений и проверки изменений на уровне шарда. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. -- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Пошаговые сценарии использования FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. +- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Практические примеры FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. - [API переводов](https://docs.fastnear.com/ru/transfers.md): История переводов NEAR и FT-токенов по аккаунтам для продуктовых лент и инструментов расследования. - [Транзакции API](https://docs.fastnear.com/ru/tx.md): Индексированные запросы по транзакциям, квитанциям, истории аккаунтов и истории блоков для FastNear. @@ -32,12 +32,12 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп ## Другие гайды -- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Пошаговые сценарии использования FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга. +- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Практические примеры FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Пошаговые сценарии для мониторинга недавней активности контракта, подтверждения optimistic-наблюдений и проверки изменений на уровне shard. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Практические примеры для мониторинга недавней активности контракта, подтверждения optimistic-наблюдений и проверки изменений на уровне shard. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. -- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Пошаговые сценарии для поиска переводов, пагинации через resume_token и перехода к истории транзакций. -- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Пошаговые расследования транзакций сначала для типовых задач разработчика. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Практические примеры для поиска переводов, пагинации через resume_token и перехода к истории транзакций. +- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Практические расследования транзакций для типовых задач разработчика. - [Berry Club: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Прочитайте живую доску Berry Club через RPC get_lines, а затем используйте Transactions API, чтобы восстановить одну более раннюю эпоху. - [OutLayer: что сделала эта пара request/resolution?](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы прочитать один caller-side запрос OutLayer, одно более позднее worker-side resolution и обращаться к finish-receipts только когда это действительно нужно. - [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs.md): Один короткий расширенный сценарий, который начинается с читаемого значения SocialDB и восстанавливает транзакцию записи за ним. @@ -45,7 +45,7 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп ## Снапшоты - [Снапшоты для валидаторов](https://docs.fastnear.com/ru/snapshots.md): Пути загрузки снапшотов FastNear для подъёма и восстановления узлов NEAR. -- [Примеры Snapshot](https://docs.fastnear.com/ru/snapshots/examples.md): Пошаговые операторские сценарии для выбора правильного пути восстановления через FastNear snapshots. +- [Примеры Snapshot](https://docs.fastnear.com/ru/snapshots/examples.md): Практические примеры восстановления для выбора правильного пути через FastNear snapshots. - [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. diff --git a/static/ru/neardata.md b/static/ru/neardata.md index 2361dfc..fb1afdf 100644 --- a/static/ru/neardata.md +++ b/static/ru/neardata.md @@ -41,7 +41,7 @@ https://testnet.neardata.xyz ## Нужен сценарий? -Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для компактных сценариев: обнаружения активности контракта, сравнения оптимистичных и финализированных наблюдений, а также проверки изменений на уровне шарда. +Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для практических примеров: обнаружения активности контракта, сравнения оптимистичных и финализированных наблюдений, а также проверки изменений на уровне шарда. ## Устранение неполадок diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index 22548d4..29eed18 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -1,14 +1,12 @@ **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) -NEAR Data особенно хороша там, где вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. +Используйте NEAR Data, когда вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. -## Готовые расследования +## Примеры ### Был ли мой контракт затронут в последнем финализированном блоке? -Используйте это, когда приложению, боту или инструменту поддержки нужен один быстрый ответ о живом контракте. Мы будем проверять `intents.near`, но та же сводка работает для любого аккаунта контракта. - - Стратегия + Ход Сначала дайте NEAR Data ответить на задачу мониторинга, а уже потом сохраняйте tx hash или receipt ID для следующей поверхности, если это вообще понадобится. 01last-block-final находит самую новую финализированную высоту. @@ -69,7 +67,7 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | contract_touch_summary "$TARGET_CONTRACT" ``` -Читать ответ стоит так: +Читайте ответ так: - `touched: false` означает, что самый новый финализированный блок не упомянул и не изменил контракт ни одним из отслеживаемых способов. - `sample_tx_hash` означает, что у вас уже есть хороший якорь для следующего шага на `/tx`. @@ -77,9 +75,7 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ ### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? -Используйте это, когда нужен ранний сигнал по живому контракту, но стабильный ответ всё равно должен пройти через финализированное подтверждение. - - Стратегия + Ход Используйте один и тот же словарь contract-touch на обеих поверхностях, чтобы сравнение было честным. 01last-block-optimistic находит самую новую optimistic-высоту. @@ -150,9 +146,7 @@ fi ### Какой shard действительно изменил мой контракт в этом блоке? -Используйте это, когда недавний блок уже показал активность контракта, и теперь нужно доказательство на уровне shard того, где именно изменение реально приземлилось. - - Стратегия + Ход Сначала используйте весь блок, чтобы найти нужный shard, а затем дайте block-shard доказать само изменение. 01Просканируйте список shard внутри финализированного блока и найдите state_changes по вашему контракту. diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index 22548d4..29eed18 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -1,14 +1,12 @@ **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) -NEAR Data особенно хороша там, где вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. +Используйте NEAR Data, когда вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. -## Готовые расследования +## Примеры ### Был ли мой контракт затронут в последнем финализированном блоке? -Используйте это, когда приложению, боту или инструменту поддержки нужен один быстрый ответ о живом контракте. Мы будем проверять `intents.near`, но та же сводка работает для любого аккаунта контракта. - - Стратегия + Ход Сначала дайте NEAR Data ответить на задачу мониторинга, а уже потом сохраняйте tx hash или receipt ID для следующей поверхности, если это вообще понадобится. 01last-block-final находит самую новую финализированную высоту. @@ -69,7 +67,7 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ | contract_touch_summary "$TARGET_CONTRACT" ``` -Читать ответ стоит так: +Читайте ответ так: - `touched: false` означает, что самый новый финализированный блок не упомянул и не изменил контракт ни одним из отслеживаемых способов. - `sample_tx_hash` означает, что у вас уже есть хороший якорь для следующего шага на `/tx`. @@ -77,9 +75,7 @@ curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ ### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? -Используйте это, когда нужен ранний сигнал по живому контракту, но стабильный ответ всё равно должен пройти через финализированное подтверждение. - - Стратегия + Ход Используйте один и тот же словарь contract-touch на обеих поверхностях, чтобы сравнение было честным. 01last-block-optimistic находит самую новую optimistic-высоту. @@ -150,9 +146,7 @@ fi ### Какой shard действительно изменил мой контракт в этом блоке? -Используйте это, когда недавний блок уже показал активность контракта, и теперь нужно доказательство на уровне shard того, где именно изменение реально приземлилось. - - Стратегия + Ход Сначала используйте весь блок, чтобы найти нужный shard, а затем дайте block-shard доказать само изменение. 01Просканируйте список shard внутри финализированного блока и найдите state_changes по вашему контракту. diff --git a/static/ru/neardata/index.md b/static/ru/neardata/index.md index 2361dfc..fb1afdf 100644 --- a/static/ru/neardata/index.md +++ b/static/ru/neardata/index.md @@ -41,7 +41,7 @@ https://testnet.neardata.xyz ## Нужен сценарий? -Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для компактных сценариев: обнаружения активности контракта, сравнения оптимистичных и финализированных наблюдений, а также проверки изменений на уровне шарда. +Используйте [примеры NEAR Data API](https://docs.fastnear.com/ru/neardata/examples) для практических примеров: обнаружения активности контракта, сравнения оптимистичных и финализированных наблюдений, а также проверки изменений на уровне шарда. ## Устранение неполадок diff --git a/static/ru/rpc.md b/static/ru/rpc.md index 4940382..b04a1cf 100644 --- a/static/ru/rpc.md +++ b/static/ru/rpc.md @@ -37,7 +37,7 @@ https://archival-rpc.testnet.fastnear.com ## Нужен сценарий? -Используйте [примеры RPC](https://docs.fastnear.com/ru/rpc/examples) для простых пошаговых сценариев: точных проверок состояния, анализа блоков, view-вызовов контрактов и отправки транзакций с подтверждением. +Используйте [примеры RPC](https://docs.fastnear.com/ru/rpc/examples) для практических примеров: точных проверок состояния, анализа блоков, view-вызовов контрактов и отправки транзакций с подтверждением. ## Используйте RPC, когда diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index b8ab3b3..bd24dac 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -2,17 +2,15 @@ # Примеры RPC -Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. +Начинайте с RPC-метода, который отвечает на вопрос. Отправляйте через `broadcast_tx_async`, отслеживайте через `tx` и расширяйте разбор только когда действительно нужны дерево receipts, raw state или трассировка по шардам. ## Отправка и отслеживание транзакции -Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» - ### Отправить транзакцию и затем проследить её от хеша до финального исполнения -Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. +Нужен стандартный путь отправки через RPC? Отправляйте через `broadcast_tx_async`, затем опрашивайте `tx`. К `EXPERIMENTAL_tx_status` переходите только если нужно дерево receipts. -Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: +Зафиксированная mainnet-транзакция: - хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - signer: `mike.near` @@ -20,16 +18,14 @@ - высота блока включения: `79574923` - высота блока исполнения receipt для записи в SocialDB: `79574924` -Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. - - Стратегия + Ход Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. -**Что вы здесь решаете** +**Точки выбора** - какой эндпоинт отправки брать первым - что опрашивать после того, как у вас появился tx hash @@ -65,13 +61,13 @@ flowchart LR | `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | | `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | -Практическое различие очень простое: +Используйте методы так: - используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash - используйте `tx` как обычный цикл опроса - используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу -**Что вы делаете** +**Ход** - Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. - Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. @@ -224,16 +220,16 @@ curl -s "$TX_BASE_URL/v0/transactions" \ ### Проверить и удалить старые function-call-ключи Near Social -Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. +Есть старые function-call-ключи для `social.near`? Сначала найдите нужный ключ, потом удалите один конкретный. - Стратегия + Ход Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. -**Что вы делаете** +**Ход** - Через сам RPC получаете полный список access key аккаунта. - Сужаете этот список до function-call-ключей, привязанных к `social.near`. @@ -467,22 +463,22 @@ else fi ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. ### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? -Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. +Ключ уже виден на аккаунте? Вернитесь к транзакции `AddKey`, которая его создала. - Стратегия + Ход Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. -**Что вы делаете** +**Ход** - Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. - Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. @@ -680,15 +676,15 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. ### Проверить регистрацию FT storage и затем перевести токены -Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». +Нужно отправить FT безопасно? Сначала проверьте storage registration получателя. - Стратегия + Ход Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. @@ -706,7 +702,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. -**Что вы делаете** +**Ход** - Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. - При необходимости получаете минимальный размер storage deposit. @@ -987,7 +983,7 @@ curl -s "$RPC_URL" \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. @@ -997,14 +993,14 @@ curl -s "$RPC_URL" \ ### Как прочитать сырое состояние контракта напрямую? -Используйте этот сценарий, когда у контракта нет нужного view-метода или когда вам нужно проверить саму схему хранения, а не просто довериться ответу метода. +Нужен raw storage, а не только view-метод? Читайте состояние напрямую, затем сравнивайте с view. -В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: сначала вы читаете storage напрямую, а потом подтверждаете, что публичный view-метод контракта даёт тот же ответ: +Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`. Значение может меняться; важна последовательность чтений: сначала raw storage, потом проверка через view-метод. - `view_state` читает сырой ключ `STATE` прямо из storage контракта - `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API - Стратегия + Ход Сначала прочитайте storage напрямую, а уже потом дайте контракту подтвердить тот же ответ через view-метод. 01RPC view_state читает сырой ключ STATE, не запуская код контракта. @@ -1028,7 +1024,7 @@ flowchart LR X --> A["Одно и то же текущее значение"] ``` -**Что вы делаете** +**Ход** - Читаете сырой ключ `STATE` из storage контракта. - Декодируете возвращённые байты в текущее знаковое значение счётчика. @@ -1155,7 +1151,7 @@ jq -n \ - `view_state` ответил на вопрос, прочитав storage напрямую - `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Используйте `view_state`, когда настоящий вопрос относится к точному storage, отсутствующему view-методу или проверке известного семейства ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). @@ -1165,9 +1161,9 @@ jq -n \ ### Проследить, как сгенерированная `Transfer`-receipt переходит из одного чанка на шарде в другой -Используйте этот сценарий, когда вызов контракта был только началом истории. В этом зафиксированном mainnet-примере подписанная транзакция стартует на шарде `11`, а сгенерированная `Transfer`-receipt заканчивает путь уже на шарде `6`. Именно ради таких cross-shard handoff и имеет смысл смотреть на чанки. +Нужен cross-shard handoff, а не только исходная транзакция? Смотрите чанки. -Этот walkthrough привязан к: +Зафиксированный mainnet-пример: - транзакции `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` от `7419369993.tg` к `game.hot.tg` с вызовом `l2_claim` - исходному чанку `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` на шарде `11` в блоке `194623170` @@ -1175,7 +1171,7 @@ jq -n \ - сгенерированной `Transfer`-receipt `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` - конечному чанку `EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU` на шарде `6` в блоке `194623172` - Стратегия + Ход Сначала восстановите receipt-цепочку, потом напрямую посмотрите на сгенерированную receipt, а затем привяжите каждый шаг к тому чанку на шарде, который действительно нёс эту работу. 01RPC EXPERIMENTAL_tx_status быстро показывает граф receipts и в какие следующие блоки перешла работа. @@ -1191,7 +1187,7 @@ flowchart LR C --> D["Чанк EPau...
блок 194623172
шард 6
receipt исполняется"] ``` -**Что вы делаете** +**Ход** - Сначала восстанавливаете receipt-цепочку из транзакции. - Напрямую смотрите на тело сгенерированной `Transfer`-receipt. @@ -1396,7 +1392,7 @@ curl -s "$RPC_URL" \ - этот чанк живёт на шарде `6`, а не на шарде `11` - подписанная транзакция стартовала на одном шарде, а следующая receipt завершилась уже на другом -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Используйте [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда вы знаете координаты блока и шарда и хотите буквально спросить: «что этот шард исполнил в этом блоке?» Используйте [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. Используйте [EXPERIMENTAL_tx_status](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) и [EXPERIMENTAL_receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), когда настоящий вопрос относится к трассировке на уровне receipts. Если ещё нужны state changes и produced receipts, расширяйтесь в [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard). @@ -1406,9 +1402,9 @@ curl -s "$RPC_URL" \ ### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? -Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». +Нужно понять «готово / не готово» до окна подписи? Проверьте storage и права до отправки. - Стратегия + Ход Спросите у social.near ровно о двух вещах, которые важны до подписи. 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. @@ -1425,7 +1421,7 @@ curl -s "$RPC_URL" \ - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -**Что вы делаете** +**Ход** - Проверяете, что аккаунт signer вообще существует и способен оплатить gas. - Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. @@ -1597,15 +1593,15 @@ jq -n \ Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. ### Что прямо сейчас содержит `mob.near/widget/Profile`? -Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». +Нужен живой исходник виджета и последняя запись этого ключа? Оставайтесь на точных RPC-чтениях. - Стратегия + Ход Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. @@ -1616,7 +1612,7 @@ jq -n \ - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -**Что вы делаете** +**Ход** - Спрашиваете у `social.near` каталог виджетов под `mob.near`. - Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. @@ -1731,7 +1727,7 @@ jq -r \ На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index b8ab3b3..bd24dac 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -2,17 +2,15 @@ # Примеры RPC -Используйте эту страницу, когда уже ясно, что ответ надо брать прямо из RPC, и нужен самый короткий путь по документации. Цель не в том, чтобы запомнить каждый метод, а в том, чтобы начать с правильного RPC-запроса, остановиться, как только ответ уже решает задачу, и переходить к более высокоуровневому API только тогда, когда это действительно экономит время. +Начинайте с RPC-метода, который отвечает на вопрос. Отправляйте через `broadcast_tx_async`, отслеживайте через `tx` и расширяйте разбор только когда действительно нужны дерево receipts, raw state или трассировка по шардам. ## Отправка и отслеживание транзакции -Начинайте отсюда, когда настоящий вопрос звучит не просто как «как мне это отправить?», а как «какой RPC-эндпоинт здесь правильный и как довести отслеживание транзакции до полного завершения?» - ### Отправить транзакцию и затем проследить её от хеша до финального исполнения -Используйте этот сценарий, когда история звучит просто: «у меня есть подписанная транзакция. Какой эндпоинт вызвать первым и что потом опрашивать после получения хеша?» Разные вопросы про транзакции требуют разных RPC-методов. Практичный паттерн здесь один: быстро отправить, а потом осознанно отслеживать. +Нужен стандартный путь отправки через RPC? Отправляйте через `broadcast_tx_async`, затем опрашивайте `tx`. К `EXPERIMENTAL_tx_status` переходите только если нужно дерево receipts. -Этот walkthrough специально сделан зафиксированным и историческим. Он использует одну реальную mainnet-транзакцию, которая записала follow edge в NEAR Social: +Зафиксированная mainnet-транзакция: - хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` - signer: `mike.near` @@ -20,16 +18,14 @@ - высота блока включения: `79574923` - высота блока исполнения receipt для записи в SocialDB: `79574924` -Поскольку эта транзакция уже старая и давно финализирована, вы не можете буквально воспроизвести её настоящий интервал до включения. Это нормально. Смысл примера в том, чтобы показать правильный паттерн отправки и отслеживания, а затем посмотреть на одну зафиксированную транзакцию теми же инструментами. - - Стратегия + Ход Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. -**Что вы здесь решаете** +**Точки выбора** - какой эндпоинт отправки брать первым - что опрашивать после того, как у вас появился tx hash @@ -65,13 +61,13 @@ flowchart LR | `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | | `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | -Практическое различие очень простое: +Используйте методы так: - используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash - используйте `tx` как обычный цикл опроса - используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу -**Что вы делаете** +**Ход** - Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. - Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. @@ -224,16 +220,16 @@ curl -s "$TX_BASE_URL/v0/transactions" \ ### Проверить и удалить старые function-call-ключи Near Social -Используйте этот сценарий, когда вы знаете, что на аккаунте накопились старые function-call-ключи для `social.near`, и хотите осмысленно их просмотреть, выбрать один конкретный ключ и удалить его через сырой RPC. +Есть старые function-call-ключи для `social.near`? Сначала найдите нужный ключ, потом удалите один конкретный. - Стратегия + Ход Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. -**Что вы делаете** +**Ход** - Через сам RPC получаете полный список access key аккаунта. - Сужаете этот список до function-call-ключей, привязанных к `social.near`. @@ -467,22 +463,22 @@ else fi ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. ### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? -Используйте этот сценарий, когда ключ уже виден на аккаунте, но вы хотите вернуться назад до транзакции `AddKey`, которая его создала, и понять, каким public key это изменение было реально авторизовано. +Ключ уже виден на аккаунте? Вернитесь к транзакции `AddKey`, которая его создала. - Стратегия + Ход Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. -**Что вы делаете** +**Ход** - Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. - Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. @@ -680,15 +676,15 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. ### Проверить регистрацию FT storage и затем перевести токены -Используйте этот сценарий, когда история звучит так: «безопасно отправить FT-токен, но сначала доказать, зарегистрирован ли получатель для storage на этом FT-контракте». +Нужно отправить FT безопасно? Сначала проверьте storage registration получателя. - Стратегия + Ход Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. @@ -706,7 +702,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. -**Что вы делаете** +**Ход** - Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. - При необходимости получаете минимальный размер storage deposit. @@ -987,7 +983,7 @@ curl -s "$RPC_URL" \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. @@ -997,14 +993,14 @@ curl -s "$RPC_URL" \ ### Как прочитать сырое состояние контракта напрямую? -Используйте этот сценарий, когда у контракта нет нужного view-метода или когда вам нужно проверить саму схему хранения, а не просто довериться ответу метода. +Нужен raw storage, а не только view-метод? Читайте состояние напрямую, затем сравнивайте с view. -В этом walkthrough используется живой публичный testnet-контракт `counter.near-examples.testnet`. Число в нём может меняться со временем. Это нормально. Важен сам принцип: сначала вы читаете storage напрямую, а потом подтверждаете, что публичный view-метод контракта даёт тот же ответ: +Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`. Значение может меняться; важна последовательность чтений: сначала raw storage, потом проверка через view-метод. - `view_state` читает сырой ключ `STATE` прямо из storage контракта - `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API - Стратегия + Ход Сначала прочитайте storage напрямую, а уже потом дайте контракту подтвердить тот же ответ через view-метод. 01RPC view_state читает сырой ключ STATE, не запуская код контракта. @@ -1028,7 +1024,7 @@ flowchart LR X --> A["Одно и то же текущее значение"] ``` -**Что вы делаете** +**Ход** - Читаете сырой ключ `STATE` из storage контракта. - Декодируете возвращённые байты в текущее знаковое значение счётчика. @@ -1155,7 +1151,7 @@ jq -n \ - `view_state` ответил на вопрос, прочитав storage напрямую - `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Используйте `view_state`, когда настоящий вопрос относится к точному storage, отсутствующему view-методу или проверке известного семейства ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). @@ -1165,9 +1161,9 @@ jq -n \ ### Проследить, как сгенерированная `Transfer`-receipt переходит из одного чанка на шарде в другой -Используйте этот сценарий, когда вызов контракта был только началом истории. В этом зафиксированном mainnet-примере подписанная транзакция стартует на шарде `11`, а сгенерированная `Transfer`-receipt заканчивает путь уже на шарде `6`. Именно ради таких cross-shard handoff и имеет смысл смотреть на чанки. +Нужен cross-shard handoff, а не только исходная транзакция? Смотрите чанки. -Этот walkthrough привязан к: +Зафиксированный mainnet-пример: - транзакции `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` от `7419369993.tg` к `game.hot.tg` с вызовом `l2_claim` - исходному чанку `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` на шарде `11` в блоке `194623170` @@ -1175,7 +1171,7 @@ jq -n \ - сгенерированной `Transfer`-receipt `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` - конечному чанку `EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU` на шарде `6` в блоке `194623172` - Стратегия + Ход Сначала восстановите receipt-цепочку, потом напрямую посмотрите на сгенерированную receipt, а затем привяжите каждый шаг к тому чанку на шарде, который действительно нёс эту работу. 01RPC EXPERIMENTAL_tx_status быстро показывает граф receipts и в какие следующие блоки перешла работа. @@ -1191,7 +1187,7 @@ flowchart LR C --> D["Чанк EPau...
блок 194623172
шард 6
receipt исполняется"] ``` -**Что вы делаете** +**Ход** - Сначала восстанавливаете receipt-цепочку из транзакции. - Напрямую смотрите на тело сгенерированной `Transfer`-receipt. @@ -1396,7 +1392,7 @@ curl -s "$RPC_URL" \ - этот чанк живёт на шарде `6`, а не на шарде `11` - подписанная транзакция стартовала на одном шарде, а следующая receipt завершилась уже на другом -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Используйте [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда вы знаете координаты блока и шарда и хотите буквально спросить: «что этот шард исполнил в этом блоке?» Используйте [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. Используйте [EXPERIMENTAL_tx_status](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) и [EXPERIMENTAL_receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), когда настоящий вопрос относится к трассировке на уровне receipts. Если ещё нужны state changes и produced receipts, расширяйтесь в [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard). @@ -1406,9 +1402,9 @@ curl -s "$RPC_URL" \ ### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? -Используйте этот сценарий, когда история звучит так: «я собираюсь опубликовать изменение профиля, обновление виджета или запись в графе под `mike.near` и хочу получить простой ответ “готово / не готово” ещё до открытия окна подписи». +Нужно понять «готово / не готово» до окна подписи? Проверьте storage и права до отправки. - Стратегия + Ход Спросите у social.near ровно о двух вещах, которые важны до подписи. 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. @@ -1425,7 +1421,7 @@ curl -s "$RPC_URL" \ - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -**Что вы делаете** +**Ход** - Проверяете, что аккаунт signer вообще существует и способен оплатить gas. - Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. @@ -1597,15 +1593,15 @@ jq -n \ Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. ### Что прямо сейчас содержит `mob.near/widget/Profile`? -Используйте этот сценарий, когда вопрос простой: «покажи живой исходник `mob.near/widget/Profile`, скажи, когда этот ключ виджета последний раз переписывали, и оставь меня на точных RPC-чтениях». +Нужен живой исходник виджета и последняя запись этого ключа? Оставайтесь на точных RPC-чтениях. - Стратегия + Ход Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. @@ -1616,7 +1612,7 @@ jq -n \ - [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) -**Что вы делаете** +**Ход** - Спрашиваете у `social.near` каталог виджетов под `mob.near`. - Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. @@ -1731,7 +1727,7 @@ jq -r \ На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. diff --git a/static/ru/rpc/index.md b/static/ru/rpc/index.md index 4940382..b04a1cf 100644 --- a/static/ru/rpc/index.md +++ b/static/ru/rpc/index.md @@ -37,7 +37,7 @@ https://archival-rpc.testnet.fastnear.com ## Нужен сценарий? -Используйте [примеры RPC](https://docs.fastnear.com/ru/rpc/examples) для простых пошаговых сценариев: точных проверок состояния, анализа блоков, view-вызовов контрактов и отправки транзакций с подтверждением. +Используйте [примеры RPC](https://docs.fastnear.com/ru/rpc/examples) для практических примеров: точных проверок состояния, анализа блоков, view-вызовов контрактов и отправки транзакций с подтверждением. ## Используйте RPC, когда diff --git a/static/ru/snapshots.md b/static/ru/snapshots.md index 0402df5..895f94a 100644 --- a/static/ru/snapshots.md +++ b/static/ru/snapshots.md @@ -49,7 +49,7 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash ## Нужен сценарий? -Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. +Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для практических примеров: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. ## Выберите сеть diff --git a/static/ru/snapshots/examples.md b/static/ru/snapshots/examples.md index 309f26d..9a660c3 100644 --- a/static/ru/snapshots/examples.md +++ b/static/ru/snapshots/examples.md @@ -13,44 +13,19 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash ``` -Это самый короткий путь восстановления на странице. Всё остальное нужно только если вам нужен standard RPC или архивный hot/cold-сценарий. - -## Готовое расследование +## Пример ### Выбрать и выполнить правильный сценарий восстановления mainnet -Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. - - Стратегия + Ход Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него. 01Сначала решите, нужен ли вам optimized fast-rpc, обычный RPC или архивный режим. 02Если нужен архив, сначала зафиксируйте одну точную высоту snapshot-блока и дальше переиспользуйте только её. 03Выполняйте только команды выбранного пути и не смешивайте optimized, standard и archival шаги в одном сценарии. -**Цель** - -- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. - -| Путь или команда | Как используем | Зачем используем | -| --- | --- | --- | -| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | -| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | -| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | -| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | -| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | - -**Что должен включать полезный ответ** - -- какой сценарий восстановления выбран и почему -- какие ключевые env vars важны для выбранного пути -- куда на диске должны попасть данные -- должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap - ### Минимальная команда для optimized mainnet `fast-rpc` -Используйте этот якорь, когда уже ясно, что нужен высокопроизводительный mainnet RPC-узел, и требуется только минимальная рабочая команда. - ```bash DATA_PATH=~/.near/data @@ -60,8 +35,6 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ ### Минимальная команда для стандартного mainnet RPC -Используйте этот якорь, когда нужен обычный путь восстановления mainnet RPC без optimized-профиля `fast-rpc`. - ```bash DATA_PATH=~/.near/data @@ -70,10 +43,9 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ ``` ### Shell-сценарий архивного восстановления mainnet +Для архивного восстановления сначала получите одну высоту snapshot и переиспользуйте её для hot- и cold-data. -Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. - -**Что вы делаете** +**Ход** - Один раз получаете последнюю высоту архивного снапшота mainnet. - Сохраняете её в `LATEST`. @@ -93,7 +65,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_TYPE=cold-data DATA_PATH="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. diff --git a/static/ru/snapshots/examples/index.md b/static/ru/snapshots/examples/index.md index 309f26d..9a660c3 100644 --- a/static/ru/snapshots/examples/index.md +++ b/static/ru/snapshots/examples/index.md @@ -13,44 +13,19 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash ``` -Это самый короткий путь восстановления на странице. Всё остальное нужно только если вам нужен standard RPC или архивный hot/cold-сценарий. - -## Готовое расследование +## Пример ### Выбрать и выполнить правильный сценарий восстановления mainnet -Используйте это расследование, когда оператор говорит «мне нужно вернуть этот узел в онлайн» и нужно понять, правильный ли путь — optimized `fast-rpc`, обычный RPC или архивное восстановление с hot/cold-данными. - - Стратегия + Ход Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него. 01Сначала решите, нужен ли вам optimized fast-rpc, обычный RPC или архивный режим. 02Если нужен архив, сначала зафиксируйте одну точную высоту snapshot-блока и дальше переиспользуйте только её. 03Выполняйте только команды выбранного пути и не смешивайте optimized, standard и archival шаги в одном сценарии. -**Цель** - -- Превратить расплывчатый запрос на восстановление в правильный сценарий снапшота mainnet и минимальную последовательность команд, с которой уже можно безопасно стартовать. - -| Путь или команда | Как используем | Зачем используем | -| --- | --- | --- | -| Mainnet optimized `fast-rpc` | Выбираем его первым, когда цель — максимально быстрое восстановление высокопроизводительного RPC, а узел подходит для optimized profile | Это предпочтительный путь быстрого восстановления, если архивное хранение не требуется | -| Стандартный RPC в mainnet | Используем его, когда нужен более простой сценарий восстановления RPC без optimized profile | Даёт прямой стандартный путь восстановления в обычный каталог данных nearcore | -| Получение последней высоты архивного снапшота | Получаем последнюю высоту архивного снапшота перед архивным восстановлением | Даёт конкретный блок снапшота как опору для загрузки hot/cold-данных | -| Команда загрузки hot-данных | Запускаем её первой и размещаем результат на NVMe | Горячие архивные данные должны лежать на быстром уровне хранения, чтобы узел работал корректно | -| Команда загрузки cold-данных | Запускаем её после hot-данных и размещаем на холодном уровне хранения | Завершает архивное восстановление без необходимости держать весь архив на дорогом hot-уровне | - -**Что должен включать полезный ответ** - -- какой сценарий восстановления выбран и почему -- какие ключевые env vars важны для выбранного пути -- куда на диске должны попасть данные -- должен ли оператор оставаться в FastNear snapshot docs или переходить к общим гайдам nearcore по bootstrap - ### Минимальная команда для optimized mainnet `fast-rpc` -Используйте этот якорь, когда уже ясно, что нужен высокопроизводительный mainnet RPC-узел, и требуется только минимальная рабочая команда. - ```bash DATA_PATH=~/.near/data @@ -60,8 +35,6 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ ### Минимальная команда для стандартного mainnet RPC -Используйте этот якорь, когда нужен обычный путь восстановления mainnet RPC без optimized-профиля `fast-rpc`. - ```bash DATA_PATH=~/.near/data @@ -70,10 +43,9 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ ``` ### Shell-сценарий архивного восстановления mainnet +Для архивного восстановления сначала получите одну высоту snapshot и переиспользуйте её для hot- и cold-data. -Используйте этот сценарий, когда вы уже решили, что нужен именно архивный путь для mainnet, и теперь нужна точная последовательность команд с одной общей опорной высотой блока. - -**Что вы делаете** +**Ход** - Один раз получаете последнюю высоту архивного снапшота mainnet. - Сохраняете её в `LATEST`. @@ -93,7 +65,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_TYPE=cold-data DATA_PATH="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. diff --git a/static/ru/snapshots/index.md b/static/ru/snapshots/index.md index 0402df5..895f94a 100644 --- a/static/ru/snapshots/index.md +++ b/static/ru/snapshots/index.md @@ -49,7 +49,7 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash ## Нужен сценарий? -Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для операторских сценариев: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. +Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для практических примеров: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. ## Выберите сеть diff --git a/static/ru/transfers.md b/static/ru/transfers.md index 6c36fff..4d23ab9 100644 --- a/static/ru/transfers.md +++ b/static/ru/transfers.md @@ -60,7 +60,7 @@ https://transfers.main.fastnear.com ## Нужен сценарий? -Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для простых пошаговых сценариев: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. +Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для практических примеров: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. ## Устранение неполадок diff --git a/static/ru/transfers/examples.md b/static/ru/transfers/examples.md index 463acd3..0ecebee 100644 --- a/static/ru/transfers/examples.md +++ b/static/ru/transfers/examples.md @@ -1,12 +1,10 @@ **Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) -## Готовый сценарий +## Пример ### Отфильтровать и листать ленту переводов одного аккаунта -Используйте этот сценарий, когда история звучит так: «покажи мне осмысленную ленту переводов этого аккаунта, дай мне спокойно листать её дальше, и только потом, если нужно, помоги догнать одну строку до истории исполнения». - - Стратегия + Ход Сначала соберите саму ленту аккаунта, а `receipt` поднимайте только тогда, когда одна строка действительно требует истории исполнения. 01POST /v0/transfers даёт первую страницу отфильтрованной ленты одного аккаунта. @@ -17,7 +15,7 @@ - только mainnet -**Что вы делаете** +**Ход** - Забираете первую страницу одной отфильтрованной ленты переводов для выбранного аккаунта. - Используете сами параметры ленты как главный учебный материал: `account_id`, `direction`, `asset_id`, `min_amount`, `desc` и `limit`. @@ -80,7 +78,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Запрос переводов напрямую отвечает на первый вопрос: как сейчас выглядит отфильтрованная лента этого аккаунта и как её продолжать без потери места? Только после того как сама лента подскажет, какая строка действительно важна, имеет смысл переходить по `receipt_id` и забирать историю исполнения из `/tx`. diff --git a/static/ru/transfers/examples/index.md b/static/ru/transfers/examples/index.md index 463acd3..0ecebee 100644 --- a/static/ru/transfers/examples/index.md +++ b/static/ru/transfers/examples/index.md @@ -1,12 +1,10 @@ **Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) -## Готовый сценарий +## Пример ### Отфильтровать и листать ленту переводов одного аккаунта -Используйте этот сценарий, когда история звучит так: «покажи мне осмысленную ленту переводов этого аккаунта, дай мне спокойно листать её дальше, и только потом, если нужно, помоги догнать одну строку до истории исполнения». - - Стратегия + Ход Сначала соберите саму ленту аккаунта, а `receipt` поднимайте только тогда, когда одна строка действительно требует истории исполнения. 01POST /v0/transfers даёт первую страницу отфильтрованной ленты одного аккаунта. @@ -17,7 +15,7 @@ - только mainnet -**Что вы делаете** +**Ход** - Забираете первую страницу одной отфильтрованной ленты переводов для выбранного аккаунта. - Используете сами параметры ленты как главный учебный материал: `account_id`, `direction`, `asset_id`, `min_amount`, `desc` и `limit`. @@ -80,7 +78,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ }' ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Запрос переводов напрямую отвечает на первый вопрос: как сейчас выглядит отфильтрованная лента этого аккаунта и как её продолжать без потери места? Только после того как сама лента подскажет, какая строка действительно важна, имеет смысл переходить по `receipt_id` и забирать историю исполнения из `/tx`. diff --git a/static/ru/transfers/index.md b/static/ru/transfers/index.md index 6c36fff..4d23ab9 100644 --- a/static/ru/transfers/index.md +++ b/static/ru/transfers/index.md @@ -60,7 +60,7 @@ https://transfers.main.fastnear.com ## Нужен сценарий? -Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для простых пошаговых сценариев: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. +Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для практических примеров: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. ## Устранение неполадок diff --git a/static/ru/tx.md b/static/ru/tx.md index 1941084..f1aa96e 100644 --- a/static/ru/tx.md +++ b/static/ru/tx.md @@ -42,7 +42,7 @@ https://tx.test.fastnear.com ## Нужен сценарий? -Используйте [примеры API транзакций](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. +Используйте [примеры API транзакций](https://docs.fastnear.com/ru/tx/examples) для практических примеров: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. ## Устранение неполадок diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index 95b5e4b..9d8cc69 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -27,30 +27,18 @@ curl -s "$TX_BASE_URL/v0/transactions" \ }' ``` -Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. - ## С чего начать -Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. - ### У меня есть один хеш транзакции. Что вообще произошло? -Используйте это расследование, когда история максимально простая: «мне прислали один хеш транзакции. Я просто хочу понять, сработала ли она, что именно сделала и в какой блок попала». - -Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. - - Стратегия + Ход Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно. 01POST /v0/transactions даёт signer, receiver, типы действий, высоту блока и первую receipt-точку передачи. 02RPC EXPERIMENTAL_tx_status нужен только для точной протокольной семантики успеха. 03POST /v0/receipt имеет смысл только тогда, когда именно первая receipt становится новой опорной точкой. -**Цель** - -- Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. - -Для этого зафиксированного примера: +Зафиксированный пример: - хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` - signer: `mike.near` @@ -58,7 +46,7 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - высота включающего блока: `194263342` - ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` -Простой человеческий ответ для этого случая такой: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. +Короткий ответ: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. ```mermaid flowchart LR @@ -74,19 +62,9 @@ flowchart LR | Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | | Переход к receipt | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | -**Что должен включать полезный ответ** - -- кто подписал транзакцию -- какой аккаунт её получил -- какой тип действия она несла -- в какой блок попала -- одно простое предложение, которое объясняет транзакцию без receipt-жаргона - #### Shell-сценарий: от хеша транзакции к человеческой истории -Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. - -**Что вы делаете** +**Ход** - Получаете транзакцию по хешу и печатаете её основные поля. - Подтверждаете финальный статус только если нужны точные RPC-семантики. @@ -170,27 +148,19 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** `POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. ### Какая receipt выдала этот лог или event? -Используйте это расследование, когда история звучит так: «у меня есть один tx hash и один фрагмент лога, и мне нужно точно понять, какая именно receipt его выдала». - -Это другой вопрос, чем более поздний сценарий «дошёл ли callback?». Здесь цель проще: привязать одну наблюдаемую строку лога к одному точному `receipt_id`, одному методу и одному исполнителю. - - Стратегия + Ход Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога. 01POST /v0/transactions даёт полный индексированный список receipt для одного tx hash, включая receipt-логи. 02jq сужает этот список до receipt, в логах которых встречается нужный вам фрагмент. 03Как только совпадение осталось одно, сохраняйте его receipt_id, executor и имя метода как точный ответ. -**Цель** - -- Начать с одного mainnet tx hash и одного фрагмента лога и определить точную receipt, которая выдала этот лог. - Для этого зафиксированного mainnet-примера используйте: - хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` @@ -199,7 +169,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - ожидаемый executor: `wrap.near` - ожидаемый метод: `ft_resolve_transfer` -Эта транзакция полезна тем, что в ней есть две разные logged receipt внутри одной истории: +В этой транзакции есть две logged receipt внутри одной истории: - ранний лог `Transfer ...` на receipt с `ft_transfer_call` - более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` @@ -217,19 +187,9 @@ flowchart LR | Атрибуция лога | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | | Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | -**Что должен включать полезный ответ** - -- какой `receipt_id` выдал лог -- какой контракт исполнил эту receipt -- какой метод там выполнился -- точную строку лога, которая совпала -- одно простое предложение вроде «лог `Refund` пришёл из `wrap.near` в receipt с методом `ft_resolve_transfer`» - #### Shell-сценарий атрибуции лога -Используйте этот сценарий, когда у вас уже есть tx hash и следующий вопрос звучит как «какая receipt это сказала?» - -**Что вы делаете** +**Ход** - Один раз получаете транзакцию и сохраняете список её receipt. - Фильтруете receipt по одному фрагменту лога. @@ -309,28 +269,24 @@ jq '{ Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. ### Превратить один страшный receipt ID из логов в понятную человеческую историю -Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. +Есть только `receipt_id` из логов или трассы? Сначала разрешите сам receipt, затем восстановите родительскую транзакцию. Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. - Стратегия + Ход Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой. 01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt. 02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий. 03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола». -**Цель** - -- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. - -Для этого зафиксированного примера «страшный receipt ID из логов» такой: +Зафиксированный receipt из логов: - receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` - хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` @@ -339,7 +295,7 @@ Receipt-логи живут на уровне receipt, а не на каком- - высота блока транзакции: `194263342` - высота блока исполнения receipt: `194263343` -Человеческая история за этим receipt простая: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. +Короткий ответ: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. ```mermaid flowchart LR @@ -355,14 +311,6 @@ flowchart LR | История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | | Каноническое продолжение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | -**Что должен включать полезный ответ** - -- какие аккаунты создали и исполнили квитанцию -- к какой транзакции относится эта квитанция -- что транзакция на самом деле сделала -- была ли квитанция главным событием или только шагом в большом каскаде -- одно предложение простым языком, которое можно без правок вставить коллеге в чат - #### Shell-сценарий: от страшного receipt ID к человеческой истории ```bash @@ -445,7 +393,7 @@ jq -r ' В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** `POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. @@ -453,25 +401,19 @@ jq -r ' Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. -Используйте этот раздел, когда уже понятно, что транзакция жила дольше одной receipt, и следующий вопрос относится уже к форме исполнения, а не к простому поиску объекта. - ### Доказать, что одно неудачное действие сорвало весь пакет -Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? +Нужно проверить, закрепились ли ранние действия в неудачном батче? Используйте этот testnet-пример с `CreateAccount -> Transfer -> AddKey -> FunctionCall`. В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. - Стратегия + Ход Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов. 01POST /v0/transactions показывает упорядоченный пакет ровно в том виде, в каком его подписал signer. 02RPC EXPERIMENTAL_tx_status показывает падающий FunctionCall и точную причину отказа на уровне протокола. 03RPC view_account по предполагаемому новому аккаунту доказывает, закрепились ли вообще ранние create, fund и add-key действия. -**Цель** - -- На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. - **Официальные ссылки** - [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) @@ -507,21 +449,11 @@ flowchart LR | Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | | Доказательство по состоянию после исполнения | RPC [`query(view_account)`](https://docs.fastnear.com/ru/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | -Перед shell-сценарием важно отметить одну деталь: индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. - -**Что должен включать полезный ответ** - -- точный порядок действий, который отправил signer -- какой индекс действия упал и почему -- высоту и хеш включающего блока для этого батча -- доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality -- короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` +Индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. #### Shell-сценарий неудачной транзакции с пакетом действий -Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. - -**Что вы делаете** +**Ход** - Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. - Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. @@ -629,27 +561,23 @@ jq '{ Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. ### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? -Используйте это расследование, когда один вызов контракта залогировал успех, изменил своё локальное состояние, и даже верхнеуровневый RPC `status` выглядит успешным, но приложение всё равно сломалось, потому что позже упал отдельный cross-contract receipt. +Нужно доказать, что позже упал отдельный cross-contract receipt, хотя первый вызов выглядел успешным? Используйте этот зафиксированный testnet-пример. Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. - Стратегия + Ход Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась. 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. 03Как только эти два представления сходятся на одном и том же разрезе истории, остановитесь. Этот пример держится за сохранённые исторические свидетельства, а не за живой read состояния роутера. -**Цель** - -- Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. - **Официальные ссылки** - [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) @@ -681,21 +609,11 @@ flowchart LR | Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | | Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | -Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. - -**Что должен включать полезный ответ** - -- что подписанная транзакция успешно передала управление в первую router-receipt -- что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` -- что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` -- что RPC всё ещё показывает верхнеуровневый `SuccessValue`, хотя более поздняя detached-receipt упала -- одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции +Успех receipt не транзитивен. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. #### Shell-сценарий более позднего сбоя receipt -Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». - -**Что вы делаете** +**Ход** - Читаете транзакцию и её таймлайн receipt из индексированного представления. - Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. @@ -796,33 +714,22 @@ jq \ Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. ### Дошёл ли callback вообще? -Используйте это расследование, когда одна транзакция запустила downstream-работу на другом контракте, а настоящий вопрос звучит не как «успешно ли отработал receiver?», а как «вернулся ли callback обратно в исходный контракт?» - -Это самый короткий полезный сценарий про callback на странице: +Нужно проверить, вернулся ли callback в исходный контракт? Начните с этого mainnet-примера. -- стартуйте с одного tx hash -- найдите downstream-receipt на другом контракте -- найдите более поздний callback-receipt, который вернулся в исходный контракт -- остановитесь, как только доказаны сам факт callback и его результат - - Стратегия + Ход Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а. 01POST /v0/transactions показывает downstream-вызов и более поздний receipt, который возвращается в исходный контракт. 02jq сужает этот список receipt до одного downstream-вызова и одного callback-receipt. 03RPC EXPERIMENTAL_tx_status нужен только как дополнительное подтверждение, если вам важны канонический результат callback-а и его логи. -**Цель** - -- Доказать на одном фиксированном mainnet-примере, что `wrap.near` отправил `ft_transfer_call` в `v2.ref-finance.near`, receiver выполнил `ft_on_transfer`, а затем `wrap.near` получил callback `ft_resolve_transfer` обратно. - -Этот фиксированный mainnet-пример с callback был замечен **19 апреля 2026 года**: +Зафиксированный mainnet-пример с callback замечен **19 апреля 2026 года**: - хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` - аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` @@ -850,19 +757,9 @@ flowchart LR | Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | | Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | -**Что должен включать полезный ответ** - -- какой контракт получил downstream-вызов -- какой метод выполнился на downstream-контракте -- вернулся ли более поздний receipt в исходный контракт -- какой callback-метод там выполнился и в каком блоке -- одно простое предложение вроде «receiver упал, но исходный контракт всё равно получил callback и завершил перевод» - #### Shell-сценарий проверки callback-а -Используйте этот сценарий, когда нужен один конкретный proof callback-а без превращения страницы в полный курс по теории promises. - -**Что вы делаете** +**Ход** - Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. - Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. @@ -1021,7 +918,7 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ # - лог callback-а показывает refund обратно отправителю ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index 95b5e4b..9d8cc69 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -27,30 +27,18 @@ curl -s "$TX_BASE_URL/v0/transactions" \ }' ``` -Это самое короткое расследование на странице. Переходите к RPC или к receipt ID только если этого вывода уже мало. - ## С чего начать -Здесь собраны самые маленькие полезные якоря на странице: сначала один tx hash, потом один receipt ID, и только затем более глубокая форензика. - ### У меня есть один хеш транзакции. Что вообще произошло? -Используйте это расследование, когда история максимально простая: «мне прислали один хеш транзакции. Я просто хочу понять, сработала ли она, что именно сделала и в какой блок попала». - -Это и есть входной пример beginner-to-intermediate для этой страницы. До receipt, promise-цепочек и форензики есть один более базовый навык, который нужен любому NEAR-инженеру: превратить голый tx hash в одну короткую человеческую историю. - - Стратегия + Ход Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно. 01POST /v0/transactions даёт signer, receiver, типы действий, высоту блока и первую receipt-точку передачи. 02RPC EXPERIMENTAL_tx_status нужен только для точной протокольной семантики успеха. 03POST /v0/receipt имеет смысл только тогда, когда именно первая receipt становится новой опорной точкой. -**Цель** - -- Начать с одного хеша транзакции и получить самый короткий полезный ответ: signer, receiver, тип действия, включающий блок и факт, что транзакция действительно ушла в успешный путь исполнения. - -Для этого зафиксированного примера: +Зафиксированный пример: - хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` - signer: `mike.near` @@ -58,7 +46,7 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - высота включающего блока: `194263342` - ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` -Простой человеческий ответ для этого случая такой: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. +Короткий ответ: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. ```mermaid flowchart LR @@ -74,19 +62,9 @@ flowchart LR | Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | | Переход к receipt | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | -**Что должен включать полезный ответ** - -- кто подписал транзакцию -- какой аккаунт её получил -- какой тип действия она несла -- в какой блок попала -- одно простое предложение, которое объясняет транзакцию без receipt-жаргона - #### Shell-сценарий: от хеша транзакции к человеческой истории -Используйте этот сценарий, когда нужен самый короткий путь от одного tx hash к одному читаемому ответу. - -**Что вы делаете** +**Ход** - Получаете транзакцию по хешу и печатаете её основные поля. - Подтверждаете финальный статус только если нужны точные RPC-семантики. @@ -170,27 +148,19 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** `POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. ### Какая receipt выдала этот лог или event? -Используйте это расследование, когда история звучит так: «у меня есть один tx hash и один фрагмент лога, и мне нужно точно понять, какая именно receipt его выдала». - -Это другой вопрос, чем более поздний сценарий «дошёл ли callback?». Здесь цель проще: привязать одну наблюдаемую строку лога к одному точному `receipt_id`, одному методу и одному исполнителю. - - Стратегия + Ход Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога. 01POST /v0/transactions даёт полный индексированный список receipt для одного tx hash, включая receipt-логи. 02jq сужает этот список до receipt, в логах которых встречается нужный вам фрагмент. 03Как только совпадение осталось одно, сохраняйте его receipt_id, executor и имя метода как точный ответ. -**Цель** - -- Начать с одного mainnet tx hash и одного фрагмента лога и определить точную receipt, которая выдала этот лог. - Для этого зафиксированного mainnet-примера используйте: - хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` @@ -199,7 +169,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - ожидаемый executor: `wrap.near` - ожидаемый метод: `ft_resolve_transfer` -Эта транзакция полезна тем, что в ней есть две разные logged receipt внутри одной истории: +В этой транзакции есть две logged receipt внутри одной истории: - ранний лог `Transfer ...` на receipt с `ft_transfer_call` - более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` @@ -217,19 +187,9 @@ flowchart LR | Атрибуция лога | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | | Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | -**Что должен включать полезный ответ** - -- какой `receipt_id` выдал лог -- какой контракт исполнил эту receipt -- какой метод там выполнился -- точную строку лога, которая совпала -- одно простое предложение вроде «лог `Refund` пришёл из `wrap.near` в receipt с методом `ft_resolve_transfer`» - #### Shell-сценарий атрибуции лога -Используйте этот сценарий, когда у вас уже есть tx hash и следующий вопрос звучит как «какая receipt это сказала?» - -**Что вы делаете** +**Ход** - Один раз получаете транзакцию и сохраняете список её receipt. - Фильтруете receipt по одному фрагменту лога. @@ -309,28 +269,24 @@ jq '{ Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. ### Превратить один страшный receipt ID из логов в понятную человеческую историю -Используйте это расследование, когда у вас на руках только один страшный `receipt_id` из логов, трассы или отчёта об ошибке, а нужно превратить его в простой ответ, который поймёт коллега без расшифровки receipt-полей. +Есть только `receipt_id` из логов или трассы? Сначала разрешите сам receipt, затем восстановите родительскую транзакцию. Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. - Стратегия + Ход Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой. 01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt. 02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий. 03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола». -**Цель** - -- Начать с одного receipt ID и восстановить самую короткую полезную историю: кто его создал, где он исполнился, какая транзакция его породила и что эта транзакция вообще пыталась сделать. - -Для этого зафиксированного примера «страшный receipt ID из логов» такой: +Зафиксированный receipt из логов: - receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` - хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` @@ -339,7 +295,7 @@ Receipt-логи живут на уровне receipt, а не на каком- - высота блока транзакции: `194263342` - высота блока исполнения receipt: `194263343` -Человеческая история за этим receipt простая: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. +Короткий ответ: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. ```mermaid flowchart LR @@ -355,14 +311,6 @@ flowchart LR | История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | | Каноническое продолжение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | -**Что должен включать полезный ответ** - -- какие аккаунты создали и исполнили квитанцию -- к какой транзакции относится эта квитанция -- что транзакция на самом деле сделала -- была ли квитанция главным событием или только шагом в большом каскаде -- одно предложение простым языком, которое можно без правок вставить коллеге в чат - #### Shell-сценарий: от страшного receipt ID к человеческой истории ```bash @@ -445,7 +393,7 @@ jq -r ' В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** `POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. @@ -453,25 +401,19 @@ jq -r ' Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. -Используйте этот раздел, когда уже понятно, что транзакция жила дольше одной receipt, и следующий вопрос относится уже к форме исполнения, а не к простому поиску объекта. - ### Доказать, что одно неудачное действие сорвало весь пакет -Используйте это расследование, когда одна транзакция с несколькими действиями пыталась создать и пополнить новый аккаунт, добавить на него ключ, а затем вызвать метод на этом же новом аккаунте. Финальное действие упало, потому что у свежего аккаунта не было кода контракта. Настоящий вопрос здесь простой: закрепились ли ранние действия или весь пакет не сработал целиком? +Нужно проверить, закрепились ли ранние действия в неудачном батче? Используйте этот testnet-пример с `CreateAccount -> Transfer -> AddKey -> FunctionCall`. В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. - Стратегия + Ход Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов. 01POST /v0/transactions показывает упорядоченный пакет ровно в том виде, в каком его подписал signer. 02RPC EXPERIMENTAL_tx_status показывает падающий FunctionCall и точную причину отказа на уровне протокола. 03RPC view_account по предполагаемому новому аккаунту доказывает, закрепились ли вообще ранние create, fund и add-key действия. -**Цель** - -- На примере одной зафиксированной транзакции из testnet доказать, что финальный `FunctionCall` упал, а ранние действия `CreateAccount`, `Transfer` и `AddKey` не закрепились. - **Официальные ссылки** - [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) @@ -507,21 +449,11 @@ flowchart LR | Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | | Доказательство по состоянию после исполнения | RPC [`query(view_account)`](https://docs.fastnear.com/ru/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | -Перед shell-сценарием важно отметить одну деталь: индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. - -**Что должен включать полезный ответ** - -- точный порядок действий, который отправил signer -- какой индекс действия упал и почему -- высоту и хеш включающего блока для этого батча -- доказательство, что предполагаемый новый аккаунт всё ещё не существует после finality -- короткий вывод, что ранние `CreateAccount`, `Transfer` и `AddKey` не закрепились после падения финального `FunctionCall` +Индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. #### Shell-сценарий неудачной транзакции с пакетом действий -Используйте этот сценарий, когда нужен один конкретный неудачный пакет действий, который можно разобрать по шагам через публичные FastNear testnet-эндпоинты. - -**Что вы делаете** +**Ход** - Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. - Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. @@ -629,27 +561,23 @@ jq '{ Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. ### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? -Используйте это расследование, когда один вызов контракта залогировал успех, изменил своё локальное состояние, и даже верхнеуровневый RPC `status` выглядит успешным, но приложение всё равно сломалось, потому что позже упал отдельный cross-contract receipt. +Нужно доказать, что позже упал отдельный cross-contract receipt, хотя первый вызов выглядел успешным? Используйте этот зафиксированный testnet-пример. Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. - Стратегия + Ход Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась. 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. 03Как только эти два представления сходятся на одном и том же разрезе истории, остановитесь. Этот пример держится за сохранённые исторические свидетельства, а не за живой read состояния роутера. -**Цель** - -- Доказать по одной зафиксированной testnet-транзакции, что `seq-dr.mike.testnet.kickoff_append(...)` успешно отработал на своей собственной receipt, а потом отдельный detached-вызов `append(...)` упал через один блок с `CodeDoesNotExist`. - **Официальные ссылки** - [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) @@ -681,21 +609,11 @@ flowchart LR | Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | | Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | -Здесь важна одна NEAR-деталь: успех receipt не является транзитивным. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. - -**Что должен включать полезный ответ** - -- что подписанная транзакция успешно передала управление в первую router-receipt -- что сама router-receipt завершилась успешно и выдала лог `dishonest_router:kickoff:late-failure` -- что более поздняя detached-receipt в `asyncfail-in2hwikn.temp.mike.testnet` упала с `CodeDoesNotExist` -- что RPC всё ещё показывает верхнеуровневый `SuccessValue`, хотя более поздняя detached-receipt упала -- одно предложение, которое объясняет, почему это отличается от неудачной батч-транзакции +Успех receipt не транзитивен. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. #### Shell-сценарий более позднего сбоя receipt -Используйте этот сценарий, когда история звучит так: «вызов контракта выглядел нормальным, но потом что-то упало, и мне надо точно доказать, где история разошлась». - -**Что вы делаете** +**Ход** - Читаете транзакцию и её таймлайн receipt из индексированного представления. - Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. @@ -796,33 +714,22 @@ jq \ Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. ### Дошёл ли callback вообще? -Используйте это расследование, когда одна транзакция запустила downstream-работу на другом контракте, а настоящий вопрос звучит не как «успешно ли отработал receiver?», а как «вернулся ли callback обратно в исходный контракт?» - -Это самый короткий полезный сценарий про callback на странице: +Нужно проверить, вернулся ли callback в исходный контракт? Начните с этого mainnet-примера. -- стартуйте с одного tx hash -- найдите downstream-receipt на другом контракте -- найдите более поздний callback-receipt, который вернулся в исходный контракт -- остановитесь, как только доказаны сам факт callback и его результат - - Стратегия + Ход Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а. 01POST /v0/transactions показывает downstream-вызов и более поздний receipt, который возвращается в исходный контракт. 02jq сужает этот список receipt до одного downstream-вызова и одного callback-receipt. 03RPC EXPERIMENTAL_tx_status нужен только как дополнительное подтверждение, если вам важны канонический результат callback-а и его логи. -**Цель** - -- Доказать на одном фиксированном mainnet-примере, что `wrap.near` отправил `ft_transfer_call` в `v2.ref-finance.near`, receiver выполнил `ft_on_transfer`, а затем `wrap.near` получил callback `ft_resolve_transfer` обратно. - -Этот фиксированный mainnet-пример с callback был замечен **19 апреля 2026 года**: +Зафиксированный mainnet-пример с callback замечен **19 апреля 2026 года**: - хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` - аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` @@ -850,19 +757,9 @@ flowchart LR | Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | | Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | -**Что должен включать полезный ответ** - -- какой контракт получил downstream-вызов -- какой метод выполнился на downstream-контракте -- вернулся ли более поздний receipt в исходный контракт -- какой callback-метод там выполнился и в каком блоке -- одно простое предложение вроде «receiver упал, но исходный контракт всё равно получил callback и завершил перевод» - #### Shell-сценарий проверки callback-а -Используйте этот сценарий, когда нужен один конкретный proof callback-а без превращения страницы в полный курс по теории promises. - -**Что вы делаете** +**Ход** - Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. - Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. @@ -1021,7 +918,7 @@ jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ # - лог callback-а показывает refund обратно отправителю ``` -**Зачем нужен следующий шаг?** +**Когда переходить дальше** Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. diff --git a/static/ru/tx/index.md b/static/ru/tx/index.md index 1941084..f1aa96e 100644 --- a/static/ru/tx/index.md +++ b/static/ru/tx/index.md @@ -42,7 +42,7 @@ https://tx.test.fastnear.com ## Нужен сценарий? -Используйте [примеры API транзакций](https://docs.fastnear.com/ru/tx/examples) для простых пошаговых сценариев: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. +Используйте [примеры API транзакций](https://docs.fastnear.com/ru/tx/examples) для практических примеров: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. ## Устранение неполадок From dd76f31a30530efb30c52151044682ca3f844dba Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sun, 19 Apr 2026 18:52:35 -0700 Subject: [PATCH 25/35] docs: add live tip-block rpc example --- docs/rpc/examples.md | 166 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index f5cabf1..29e2b16 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -225,6 +225,172 @@ That last step is intentionally optional. The RPC truth is already enough for su - Use `send_tx` when you really do want one blocking call to wait up to a chosen threshold. - Use `EXPERIMENTAL_tx_status` when the normal polling loop stops being enough and the receipt tree becomes the real question. +## Tip Block Inspection + +### Describe the first action of the first transaction at the current tip + +Need one plain-English description of what the tip block starts with? Read the true tip with `status`, choose the first non-empty chunk from `block`, then inspect that chunk directly. + +
+
+ Flow +

Use `status` for the live head, `block` for the ordered chunk list, then `chunk` for the first transaction and its first action.

+
+
+

01RPC status gives the node's current head hash.

+

02RPC block gives the chunk list, so you can pick the first chunk whose tx_root is not the empty sentinel.

+

03RPC chunk gives the transactions and actions. Use transactions[0].actions[0] as the exact answer.

+
+
+ +**Flow** + +- Read the live head hash with `status`. +- Fetch that exact block with `block`. +- Scan the block's chunks in returned order and keep the first non-empty chunk. +- Fetch that chunk and read the first transaction's first action. +- Print one human sentence for that action. + +`block` is only enough to choose the chunk. The transaction list lives on `chunk`, so you do not need `tx` for this job. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +EMPTY_TX_ROOT=11111111111111111111111111111111 +``` + +1. Read the node's true tip and keep the latest block hash. + +```bash +BLOCK_HASH="$( + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data '{ + "jsonrpc": "2.0", + "id": "fastnear", + "method": "status", + "params": [] + }' \ + | tee /tmp/tip-status.json \ + | jq -r '.result.sync_info.latest_block_hash' +)" + +jq '{ + latest_block_height: .result.sync_info.latest_block_height, + latest_block_hash: .result.sync_info.latest_block_hash +}' /tmp/tip-status.json +``` + +2. Fetch that block and pick the first non-empty chunk in returned order. + +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_hash "$BLOCK_HASH" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "block", + params: { + block_id: $block_hash + } + }')" \ + | tee /tmp/tip-block.json >/dev/null + +CHUNK_HASH="$( + jq -r --arg empty_tx_root "$EMPTY_TX_ROOT" ' + first( + .result.chunks[] + | select(.tx_root != $empty_tx_root) + | .chunk_hash + ) // empty + ' /tmp/tip-block.json +)" + +jq --arg empty_tx_root "$EMPTY_TX_ROOT" '{ + block: { + height: .result.header.height, + hash: .result.header.hash + }, + first_non_empty_chunk: ( + first( + .result.chunks[] + | select(.tx_root != $empty_tx_root) + | { + chunk_hash, + shard_id, + tx_root + } + ) // null + ) +}' /tmp/tip-block.json + +if [ -z "$CHUNK_HASH" ]; then + echo "tip block had no transactions, rerun on the next block" +fi +``` + +That empty-block branch is intentional. A true tip block can be valid and still have no transactions. + +3. If `CHUNK_HASH` is non-empty, fetch the chosen chunk and print the first transaction plus its first action type. + +```bash +if [ -n "$CHUNK_HASH" ]; then + curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg chunk_hash "$CHUNK_HASH" '{ + jsonrpc: "2.0", + id: "fastnear", + method: "chunk", + params: { + chunk_id: $chunk_hash + } + }')" \ + | tee /tmp/tip-chunk.json >/dev/null + + jq '{ + chunk: { + chunk_hash: .result.header.chunk_hash, + shard_id: .result.header.shard_id, + height_included: .result.header.height_included + }, + first_transaction: { + hash: .result.transactions[0].hash, + signer_id: .result.transactions[0].signer_id, + receiver_id: .result.transactions[0].receiver_id + }, + first_action_type: (.result.transactions[0].actions[0] | keys[0]) + }' /tmp/tip-chunk.json +fi +``` + +4. If the chunk fetch ran, turn that first action into one human sentence. + +```bash +if [ -n "$CHUNK_HASH" ]; then + FIRST_ACTION_TYPE="$(jq -r '.result.transactions[0].actions[0] | keys[0]' /tmp/tip-chunk.json)" + SIGNER_ID="$(jq -r '.result.transactions[0].signer_id' /tmp/tip-chunk.json)" + RECEIVER_ID="$(jq -r '.result.transactions[0].receiver_id' /tmp/tip-chunk.json)" + + if [ "$FIRST_ACTION_TYPE" = "FunctionCall" ]; then + METHOD_NAME="$(jq -r '.result.transactions[0].actions[0].FunctionCall.method_name' /tmp/tip-chunk.json)" + RENDERED_ARGS="$( + jq -r ' + .result.transactions[0].actions[0].FunctionCall.args as $raw + | try ($raw | @base64d | fromjson | tojson) catch $raw + ' /tmp/tip-chunk.json + )" + + printf '%s\n' "$SIGNER_ID called $METHOD_NAME on $RECEIVER_ID with args: $RENDERED_ARGS" + else + printf '%s\n' "$SIGNER_ID sent $FIRST_ACTION_TYPE to $RECEIVER_ID" + fi +fi +``` + +That last step deliberately stays simple: + +- if the first action is `FunctionCall`, print the method plus decoded JSON args when they decode cleanly +- otherwise print the action type, signer, and receiver without adding more protocol interpretation + ## Account and Key Mechanics ### Audit and remove old Near Social function-call keys From d6b02d1198ded9e40ff1331f112daf61d4981ce0 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sun, 19 Apr 2026 19:36:25 -0700 Subject: [PATCH 26/35] docs: simplify rpc tx tracking example --- docs/rpc/examples.md | 151 +++++++++++++------------------------------ 1 file changed, 46 insertions(+), 105 deletions(-) diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index 29e2b16..a9ca0d9 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -10,90 +10,54 @@ page_actions: # RPC Examples -Start with the RPC method that answers the question. Submit with `broadcast_tx_async`, track with `tx`, and widen only when you need receipt trees, raw state, or shard-level tracing. +Start with the RPC method that answers the question. Use `tx` to track inclusion and finality from a tx hash, and widen only when you need receipt trees, raw state, or shard-level tracing. -## Transaction Submission and Tracking +## Transaction Inclusion and Finality -### Submit a transaction, then track it from hash to final execution +### Track a transaction from hash to finality -Need the default RPC submission flow? Submit with `broadcast_tx_async`, then poll `tx`. Use `EXPERIMENTAL_tx_status` only when you need the receipt tree. +Have a tx hash and need to know how far it got? Poll `tx` with the smallest `wait_until` threshold that answers the question. If you are submitting right now, use `broadcast_tx_async` only to get the hash. -Pinned mainnet transaction: +Pinned testnet transaction: -- transaction hash: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- signer: `mike.near` -- receiver: `social.near` -- included block height: `79574923` -- receipt execution block for the SocialDB write: `79574924` +- transaction hash: `CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow` +- signer: `mike.testnet` +- receiver: `mike.testnet` +- first action: `Transfer` of `1` yoctoNEAR +- observed live on: `2026-04-19`
Flow -

Submit fast, poll the simpler status path first, and only drop into the receipt tree when the headline status stops being enough.

+

Get the hash quickly, use `tx` for the actual stage question, and reach for `EXPERIMENTAL_tx_status` only when the receipt tree becomes the question.

-

01RPC broadcast_tx_async is the low-latency submission surface when your client will track separately.

-

02RPC tx is the default polling surface for included, optimistic, and final guarantees.

-

03RPC EXPERIMENTAL_tx_status is the deeper follow-up when you need the receipt tree, not the default loop.

+

01RPC broadcast_tx_async is the quick submit step when your client will track separately.

+

02RPC tx is the default surface for inclusion, optimistic execution, and finality.

+

03RPC EXPERIMENTAL_tx_status is the receipt-inspection follow-up, not the default tracking loop.

-**Decision points** +**Live rule** -- which submission endpoint to reach for first -- what to poll after you have a tx hash -- how `wait_until` thresholds relate to included, optimistic, and final guarantees -- when to stop using `tx` and switch to `EXPERIMENTAL_tx_status` +`wait_until` is a minimum threshold. The node may answer with a later stage if the transaction advanced while it was waiting. -```mermaid -flowchart LR - S["Sign transaction"] --> A["broadcast_tx_async
returns tx hash"] - A --> T["tx polling
INCLUDED_FINAL -> FINAL"] - T --> F["Transaction fully done"] - T -. "only when needed" .-> E["EXPERIMENTAL_tx_status
receipt tree + outcomes"] - F -. "optional readable story" .-> X["POST /v0/transactions"] -``` - -| Method | Use it when | What comes back | Position here | -| --- | --- | --- | --- | -| [`broadcast_tx_async`](/rpc/transaction/broadcast-tx-async) | your client wants to own tracking after submission | just the tx hash | **default submit path** | -| [`send_tx`](/rpc/transaction/send-tx) | you want the node to wait to a chosen threshold for you | tx result up to `wait_until` | blocking alternative | -| [`broadcast_tx_commit`](/rpc/transaction/broadcast-tx-commit) | older code or quick one-call confirmation is the point | execution result with commit-style waiting | legacy convenience | -| [`tx`](/rpc/transaction/tx-status) | you already have the tx hash and want to know how far it got | status plus outcomes at the threshold you asked for | **default tracking path** | -| [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | you need receipt-tree detail or a richer async story | full receipt tree and detailed outcomes | deep follow-up only | - -**Status and wait map** - -`wait_until` values are waiting thresholds, not a permanent lifecycle enum you should treat as the user's one true transaction status. The word `pending` is still useful in human conversation, but here it means “the transaction has been submitted and is not yet included.” +Observed when four `tx` polls for this transaction started at the same moment on `2026-04-19`: -| Phase or threshold | What it means in practice | Best RPC surface | +| `wait_until` | returned `final_execution_status` | Takeaway | | --- | --- | --- | -| pre-inclusion / pending | the client has submitted the tx, but it is not yet anchored in a block | your own submission state plus retry/backoff logic | -| `INCLUDED` | the tx is in a block, but that block may not be final yet | `tx` | -| `INCLUDED_FINAL` | the inclusion block is final | `tx` | -| `EXECUTED_OPTIMISTIC` | execution has happened with optimistic finality | `tx` or `send_tx` | -| `FINAL` | all relevant execution has completed and finalized | `tx` by default, `EXPERIMENTAL_tx_status` when you need more detail | - -- use `broadcast_tx_async` when the tx hash is enough to keep going -- use `tx` as the normal tracking loop -- use `EXPERIMENTAL_tx_status` when the next question is about the receipt tree rather than the headline status - -**Flow** - -- Show what a live submission would look like with `broadcast_tx_async`. -- Poll the pinned tx with `tx` at two thresholds: `INCLUDED_FINAL` and `FINAL`. -- Only after that inspect the same tx with `EXPERIMENTAL_tx_status`. -- Optionally pivot to Transactions API if the human-readable story is what matters next. +| `INCLUDED` | `EXECUTED_OPTIMISTIC` | inclusion was satisfied, and execution had already advanced | +| `EXECUTED_OPTIMISTIC` | `EXECUTED_OPTIMISTIC` | optimistic execution was the first stage observed | +| `INCLUDED_FINAL` | `FINAL` | the transaction advanced past included-final before the response came back | +| `FINAL` | `FINAL` | this is the simple “is it actually done?” threshold | ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near -RECEIVER_ID=social.near +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow +SIGNER_ACCOUNT_ID=mike.testnet ``` -1. If this were a live client flow, submit with `broadcast_tx_async` and keep the returned hash. +1. If you are submitting live, get the hash first. ```bash curl -s "$RPC_URL" \ @@ -104,12 +68,12 @@ curl -s "$RPC_URL" \ "method": "broadcast_tx_async", "params": ["BASE64_SIGNED_TX"] }' \ - | jq . + | jq '{tx_hash: .result}' ``` -In a real app, that response is the moment you stop waiting on submission and start tracking by tx hash. +This is the clean “submit now, track separately” surface. In live testnet runs, `send_tx` with `wait_until: "NONE"` returned only `final_execution_status: "NONE"` and no hash, so it is not the clearest default when the client owns tracking. -2. Poll with `tx` at the first threshold that answers the user question. +2. Use `INCLUDED` when the real question is “did this make it into a block yet?” ```bash curl -s "$RPC_URL" \ @@ -123,23 +87,23 @@ curl -s "$RPC_URL" \ params: { tx_hash: $tx_hash, sender_account_id: $signer_account_id, - wait_until: "INCLUDED_FINAL" + wait_until: "INCLUDED" } }')" \ | jq '{ final_execution_status: .result.final_execution_status, status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status + transaction_outcome_status: .result.transaction_outcome.outcome.status }' ``` What to notice: -- on a live tx, this threshold is useful when you care that the tx is safely included before you claim success to the user -- on this historical tx, it returns immediately because the transaction is long past inclusion -- `transaction_outcome.outcome.status` still tells you that the original action handed off into receipt execution +- `INCLUDED` means “do not answer before inclusion,” not “freeze the reply at included” +- a live reply can already be `EXECUTED_OPTIMISTIC` or `FINAL` +- use this threshold when block inclusion is the question -3. Poll again with `FINAL` when you want the completed transaction story rather than just safe inclusion. +3. Use `FINAL` when the real question is “is it actually done?” ```bash curl -s "$RPC_URL" \ @@ -165,11 +129,11 @@ curl -s "$RPC_URL" \ What to notice: -- for a historical tx, this call also returns immediately -- in a real tracking loop, this is the threshold that answers “is the transaction actually done?” -- for many apps, this is where you stop and move on +- `FINAL` is the simplest success threshold for most clients +- `tx` already gives you `receipts_outcome`, which is enough for most tracking loops +- for a historical transaction like this pinned one, the reply returns immediately because the work is already done -4. Only switch to `EXPERIMENTAL_tx_status` when you need the richer receipt tree. +4. Only widen to `EXPERIMENTAL_tx_status` when you need the receipt tree itself. ```bash curl -s "$RPC_URL" \ @@ -188,42 +152,19 @@ curl -s "$RPC_URL" \ }')" \ | jq '{ final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status, + receipts_count: (.result.receipts | length), receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -This is where you go when “did it finish?” turns into “show me the receipt tree and the full async execution story.” - -5. Pivot to Transactions API only when you want the readable story surface. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transaction_handoff: .transactions[0].transaction_outcome.outcome.status - }' -``` - -That last step is intentionally optional. The RPC truth is already enough for submission and tracking. This is only the human-readable story surface when the next user question becomes “what actually happened?” instead of “how far did the tx get?” +`tx` already answers the stage question. `EXPERIMENTAL_tx_status` is the follow-up when you also need the receipt records that `tx` does not include. -**Recommended pattern** +**Use these defaults** -- Use `broadcast_tx_async` plus `tx` polling when you want the best client control and the fastest feedback. -- Use `send_tx` when you really do want one blocking call to wait up to a chosen threshold. -- Use `EXPERIMENTAL_tx_status` when the normal polling loop stops being enough and the receipt tree becomes the real question. +- Need a hash now: [`broadcast_tx_async`](/rpc/transaction/broadcast-tx-async) +- Need to know whether the transaction is included or final: [`tx`](/rpc/transaction/tx-status) +- Need the receipt tree: [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) +- Need one blocking submission call instead: [`send_tx`](/rpc/transaction/send-tx) ## Tip Block Inspection From fb9d192ebd229e6fe1f4f5277649e1685944bb94 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Mon, 20 Apr 2026 07:54:00 -0700 Subject: [PATCH 27/35] docs: harden examples and land russian parity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrite every examples surface (api, tx, transfers, neardata, fastdata/kv, snapshots, rpc, socialdb-proofs) to one framing sentence + one curl + one narrative sentence tying output to insight. Cut the BOS widget NFT archive scenario (obscure), the chunk tracing scenario (data aged off non-archival RPC window within 2-3 days), and the OutLayer page. Repin tx scenario 5 to mainnet 2KhhB1u... so scenarios 2/5/6 triangulate on one real DeFi failure. Fix cross-cutting transaction_outcome -> execution_outcome jq bug. Replace /tmp/ files with shell vars across scenarios. Mirror the full tightening across i18n/ru/** using the three-tier model: Latin brands (FastNear, mainnet, archival), transliterated common nouns (аккаунт, шард, чанк), native prose (запрос, ответ, метод). Russian corpus now matches English line-for-line (4282 -> 1261, 70.6% reduction). yarn audit:ru-terminology clean, yarn audit:i18n:all clean, yarn build passes both locales with no broken anchors. --- docs/api/examples.md | 409 +- docs/fastdata/kv/examples.md | 149 +- docs/neardata/examples.md | 183 +- docs/rpc/examples.md | 2064 +------ docs/snapshots/examples.mdx | 48 +- docs/transfers/examples.md | 145 +- docs/tx/examples.md | 940 +--- docs/tx/outlayer.mdx | 117 - docs/tx/socialdb-proofs.mdx | 114 +- .../current/api/examples.md | 417 +- .../current/fastdata/kv/examples.md | 154 +- .../current/neardata/examples.md | 191 +- .../current/rpc/examples.md | 2033 +------ .../current/snapshots/examples.mdx | 59 +- .../current/transfers/examples.md | 149 +- .../current/tx/examples.md | 997 +--- .../current/tx/outlayer.mdx | 117 - .../current/tx/socialdb-proofs.mdx | 128 +- static/ru/api/examples.md | 400 +- static/ru/api/examples/index.md | 400 +- static/ru/fastdata/kv/examples.md | 147 +- static/ru/fastdata/kv/examples/index.md | 147 +- static/ru/guides/llms.txt | 15 +- static/ru/llms-full.txt | 4885 ++++------------- static/ru/llms.txt | 15 +- static/ru/neardata/examples.md | 174 +- static/ru/neardata/examples/index.md | 174 +- static/ru/rpc/examples.md | 1984 +------ static/ru/rpc/examples/index.md | 1984 +------ static/ru/snapshots/examples.md | 49 +- static/ru/snapshots/examples/index.md | 49 +- static/ru/structured-data/site-graph.json | 13 - static/ru/transfers/examples.md | 142 +- static/ru/transfers/examples/index.md | 142 +- static/ru/tx/examples.md | 963 +--- static/ru/tx/examples/index.md | 963 +--- static/ru/tx/examples/outlayer.md | 100 - static/ru/tx/examples/outlayer/index.md | 100 - static/ru/tx/socialdb-proofs.md | 128 +- static/ru/tx/socialdb-proofs/index.md | 128 +- 40 files changed, 3086 insertions(+), 18430 deletions(-) delete mode 100644 docs/tx/outlayer.mdx delete mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx delete mode 100644 static/ru/tx/examples/outlayer.md delete mode 100644 static/ru/tx/examples/outlayer/index.md diff --git a/docs/api/examples.md b/docs/api/examples.md index fddf496..3de6366 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /api/examples title: API Examples -description: Task-first FastNear API examples for account lookup, holdings, staking, and BOS provenance. +description: Task-first FastNear API examples for account lookup, holdings, and staking. displayed_sidebar: fastnearApiSidebar page_actions: - markdown @@ -12,423 +12,60 @@ page_actions: ### Resolve a public key, then fetch the account snapshot -
-
- Flow -

Resolve identity first, then reuse the same account ID for one readable wallet snapshot.

-
-
-

01GET /v1/public_key gives the candidate account_id values for the key.

-

02jq lifts the account you actually want to inspect next.

-

03GET /v1/account/.../full answers balances, NFTs, and staking in one response.

-
-
- -**Flow** - -- Resolve the public key to one or more account IDs. -- Extract the first matching account ID with `jq`. -- Reuse that value in the full account snapshot endpoint. +Look up which account a key belongs to, then read that account's holdings in one call. ```bash API_BASE_URL=https://api.fastnear.com -PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' -# Example public key from the docs page model: -# PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' +PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' -ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" +LOOKUP="$(curl -s "$API_BASE_URL/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')")" -ACCOUNT_ID="$( - curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ - | tee /tmp/fastnear-public-key.json \ - | jq -r '.account_ids[0]' -)" +echo "$LOOKUP" | jq '{matched: (.account_ids | length), account_ids}' -jq '{account_ids}' /tmp/fastnear-public-key.json +ACCOUNT_ID="$(echo "$LOOKUP" | jq -r '.account_ids[0]')" curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' + | jq '{account_id, state, tokens: (.tokens|length), nfts: (.nfts|length), pools: (.pools|length)}' ``` -**When to pivot** - -The public-key lookup tells you which account you are dealing with. The full account snapshot is the natural next read when you want balances, NFTs, staking, and pools in one response. If the key maps to multiple accounts instead of one, move to [V1 Public Key Lookup All](/api/v1/public-key-all) or loop through each returned `account_id`. +If `matched` is greater than 1, switch to [V1 Public Key Lookup All](/api/v1/public-key-all) and loop over every returned account. ### Does this wallet show direct staking, liquid staking tokens, or both? -
-
- Flow -

Compare staking positions and FT balances before you interpret the wallet.

-
-
-

01GET /v1/account/.../staking finds direct pool exposure.

-

02GET /v1/account/.../ft finds liquid staking tokens that sit beside or instead of direct pools.

-

03jq turns those two indexed reads into direct_only, liquid_only, or mixed.

-
-
- -**Network** - -- mainnet - -**Official references** - -- [Validator staking](https://docs.near.org/concepts/basics/staking) -- [Using liquid staking](https://docs.near.org/primitives/liquid-staking) - -This example is intentionally observational. It classifies what FastNear can see from staking positions and FT balances today. It does not prove every possible synthetic or off-platform staking exposure. - -**Flow** - -- Read indexed direct staking positions from the account staking endpoint. -- Read indexed FT balances from the account FT endpoint. -- Classify the account into `direct_only`, `liquid_only`, `mixed`, or `no_visible_staking_position`. -- Print the direct pool list and the liquid staking token list that informed the classification. +Direct pool positions live on `/staking`; liquid staking tokens (stNEAR, LiNEAR, etc.) sit on `/ft` like any other FT. Read both, classify the wallet — `root.near` shows up as `mixed`. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID +ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -``` -1. Fetch the direct staking view. +STAKING="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking")" +FT="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft")" -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` - -2. Fetch fungible token balances so you can detect liquid staking positions. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null -``` - -3. Classify the account from those two indexed views. - -```bash jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ + --argjson staking "$STAKING" \ + --argjson ft "$FT" \ --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + ($staking.pools // []) as $direct + | (($ft.tokens // []) | map(select(.contract_id as $id | $providers | index($id)))) as $liquid | { classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) - }' -``` - -**When to pivot** - -If the classification is `direct_only`, the next operational question is usually about unstake and withdraw timing. If it is `liquid_only`, the next question is usually about redeeming or swapping the liquid token. If it is `mixed`, you should treat those as two separate exit paths rather than assuming one flow covers both. - -### Archive a BOS widget version as a provenance NFT - -
-
- Flow -

Read the exact widget first, then mint only after the provenance fields are deterministic.

-
-
-

01GET /v1/account/.../nft checks whether the receiver already holds archive NFTs from this collection.

-

02RPC call_function get on social.near reads the exact widget source and its SocialDB write block.

-

03Hash the source, mint nft_mint on testnet, then verify the provenance fields through nft_tokens_for_owner.

-
-
- -**Networks** - -- mainnet for reading the widget from `social.near` -- testnet for safely minting the provenance NFT on `nft.examples.testnet` - -**Official references** - -- [Pre-deployed NFT contract](https://docs.near.org/tutorials/nfts/js/predeployed-contract) -- [NEP-171 NFT standard](https://docs.near.org/primitives/nft/standard) -- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) - -**Flow** - -- Check whether the receiver already holds NFTs from the archive collection. -- Read one exact BOS widget from `social.near`, including its widget-level SocialDB block. -- Hash the widget source and turn that into provenance metadata. -- Mint a testnet NFT whose metadata records the author, widget path, SocialDB block, and source hash. -- Verify that the minted token still carries those provenance fields. - -Pinned source widget: - -- author account: `mob.near` -- widget path: `mob.near/widget/Profile` -- widget-level SocialDB block: `86494825` - -```bash -API_BASE_URL=https://test.api.fastnear.com -MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com -TESTNET_RPC_URL=https://rpc.testnet.fastnear.com -AUTHOR_ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -DESTINATION_COLLECTION_ID=nft.examples.testnet -RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" -``` - -1. Use FastNear API to see whether the receiver already holds NFTs from the archive collection. - -```bash -curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ - | tee /tmp/provenance-account-nfts.json >/dev/null - -jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_tokens: [ - .tokens[]? - | select(.contract_id == $destination_collection_id) - | { - contract_id, - token_id, - last_update_block_height - } - ] -}' /tmp/provenance-account-nfts.json -``` - -2. Read the exact widget body and widget-level SocialDB block from mainnet. - -```bash -WIDGET_ARGS_BASE64="$( - jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($author_account_id + "/widget/" + $widget_name)], - options: {with_block_height: true} - }' | base64 | tr -d '\n' -)" - -curl -s "$MAINNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget.json >/dev/null - -jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - widget_path: ($author_account_id + "/widget/" + $widget_name), - socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], - source_preview: ( - .[$author_account_id].widget[$widget_name][""] - | split("\n")[0:8] - ) -}' /tmp/bos-widget.json -``` - -3. Hash the widget source and build deterministic provenance metadata. - -```bash -jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][""] -' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx - -WIDGET_BLOCK_HEIGHT="$( - jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][":block"] - ' /tmp/bos-widget.json -)" - -SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" -SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" -TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" - -PROVENANCE_METADATA_JSON="$( - jq -nc \ - --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" \ - --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ - --arg block_height "$WIDGET_BLOCK_HEIGHT" \ - --arg source_sha256 "$SOURCE_SHA256" '{ - title: ("BOS widget archive: " + $widget_path), - description: ("Archived from social.near on mainnet at block " + $block_height), - copies: 1, - extra: ({ - author_account_id: $author_account_id, - widget_name: $widget_name, - widget_path: $widget_path, - source_contract_id: "social.near", - source_network: "mainnet", - socialdb_block_height: ($block_height | tonumber), - source_sha256: $source_sha256 - } | @json) + if ($direct|length)>0 and ($liquid|length)>0 then "mixed" + elif ($direct|length)>0 then "direct_only" + elif ($liquid|length)>0 then "liquid_only" + else "no_visible_staking_position" end, + direct_pools: ($direct | map(.pool_id)), + liquid_tokens: ($liquid | map({contract_id, balance})) }' -)" - -printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' ``` -4. Mint the provenance NFT on testnet. - -```bash -near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ - --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --argjson metadata "$PROVENANCE_METADATA_JSON" '{ - token_id: $token_id, - receiver_id: $receiver_id, - metadata: $metadata - }')" \ - --accountId "$SIGNER_ACCOUNT_ID" \ - --deposit 0.1 \ - --networkId testnet -``` - -5. Verify that the minted NFT carries the provenance fields you expect. - -Poll a few times instead of assuming failure if the token does not appear immediately after the mint transaction returns. - -```bash -NFT_TOKEN_ARGS_BASE64="$( - jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ - | base64 | tr -d '\n' -)" - -for attempt in 1 2 3 4 5; do - curl -s "$TESTNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_token", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget-provenance-token.json >/dev/null - - if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then - break - fi - - sleep 1 -done - -jq '{ - token_id, - owner_id, - title: .metadata.title, - provenance: (.metadata.extra | fromjson) -}' /tmp/bos-widget-provenance-token.json -``` - -**When to pivot** - -FastNear API gives you the quick receiver-side check. Mainnet RPC gives you the exact widget body and SocialDB block. Testnet minting turns that into a durable NFT record. If you later want to prove which historical transaction wrote the widget, hand off to the NEAR Social proof investigations on [Transactions API examples](/tx/examples). - -## Common jobs - -### What does this account actually hold right now? - -**Start here** - -- [V1 Full Account View](/api/v1/account-full) when you want the fastest readable answer to “what is in this account right now?” - -**Next page if needed** - -- [V1 Account FT](/api/v1/account-ft), [V1 Account NFT](/api/v1/account-nft), or [V1 Account Staking](/api/v1/account-staking) if the broad summary is useful but you now want to stay on just one asset family. -- [Transactions API account history](/tx/account) if the next question becomes “how did this account get here?” instead of “what does it hold?” - -**Stop when** - -- The summary already answers the holdings question in one response. - -**Switch when** - -- The user asks for exact account state, access-key semantics, or protocol-native fields. Move to [RPC Reference](/rpc). -- The user asks for activity or execution history rather than current holdings. Move to [Transactions API](/tx). - -### Resolve a public key to one or more accounts - -**Start here** - -- [V1 Public Key Lookup](/api/v1/public-key) when you want the primary account match. -- [V1 Public Key Lookup All](/api/v1/public-key-all) when you need the full set of associated accounts. - -**Next page if needed** - -- [V1 Full Account View](/api/v1/account-full) after resolution if the user immediately wants balances or holdings for the returned accounts. - -**Stop when** - -- You have identified the account or accounts that belong to the key. - -**Switch when** - -- The user starts asking about exact access-key permissions, nonces, or current key state. Move to [View Access Key](/rpc/account/view-access-key) or [View Access Key List](/rpc/account/view-access-key-list). -- The user wants recent activity for the resolved accounts rather than just identity resolution. Move to [Transactions API](/tx). - -### Does this account hold FTs, NFTs, or staking positions? - -**Start here** - -- [V1 Account FT](/api/v1/account-ft) when the question is just about fungible-token balances. -- [V1 Account NFT](/api/v1/account-nft) when the question is specifically about NFT holdings. -- [V1 Account Staking](/api/v1/account-staking) when the question is really about staking positions, not the whole wallet picture. - -**Next page if needed** - -- [V1 Full Account View](/api/v1/account-full) if the user later wants the whole account picture after starting from one asset family. -- [Transactions API account history](/tx/account) if the user stops asking “what does this account hold?” and starts asking how it got there. - -**Stop when** - -- The asset-specific endpoint already answers the holdings question without making you rebuild the whole account picture. - -**Switch when** - -- The indexed view is not enough and the user needs the exact on-chain answer. Move to [RPC Reference](/rpc). -- The question becomes historical or execution-oriented instead of “what does this account hold now?” Move to [Transactions API](/tx). +The classifier only knows what you teach it — extend `LIQUID_PROVIDERS_JSON` as new liquid-staking products ship, and treat the result as observational rather than exhaustive. ## Common mistakes - Leading with the broad account snapshot when the user only asked about one asset family. - Using FastNear API when the user explicitly needs exact RPC fields or permissions. - Staying in account-summary pages after the question turns into transaction history. -- Forgetting that `?network=testnet` works only on compatible pages. ## Related guides diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index 6534288..b7169e3 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -10,151 +10,38 @@ page_actions: ## Example -### Inspect one predecessor’s indexed writes, then narrow to the key that changed +### Inspect one predecessor's indexed writes, then narrow to the key that changed -
-
- Flow -

Start from predecessor scope, narrow to one exact key only after it earns your attention, then use RPC only for the final exact check.

-
-
-

01all-by-predecessor gives the latest indexed rows for one predecessor across the contracts it touched.

-

02get-history-key or history-by-predecessor explains how the interesting row changed over time.

-

03RPC view_state is the optional exact read when you need canonical current state, not just indexed history.

-
-
- -### Predecessor-scope shell walkthrough - -**Flow** - -- Read the latest indexed rows for one predecessor across the contracts it touched. -- Lift one interesting `current_account_id` plus exact `key` with `jq`. -- Reuse those exact values in the documented exact-key history route. -- Only after that decide whether you still need `view_state` for canonical current state. +`all-by-predecessor` returns the latest indexed writes one account made across every contract it touched. Lift an interesting key and replay it through `history` to see how that row changed over time. ```bash KV_BASE_URL=https://kv.main.fastnear.com -PREDECESSOR_ID=james.near +PREDECESSOR_ID=jemartel.near -curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ +FIRST="$(curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ -H 'content-type: application/json' \ - --data '{"include_metadata":true,"limit":10}' \ - | tee /tmp/kv-predecessor.json >/dev/null + --data '{"include_metadata":true,"limit":10}')" -jq '{ +echo "$FIRST" | jq '{ page_token, - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] -}' /tmp/kv-predecessor.json - -CURRENT_ACCOUNT_ID="$(jq -r '.entries[0].current_account_id' /tmp/kv-predecessor.json)" -EXACT_KEY="$(jq -r '.entries[0].key' /tmp/kv-predecessor.json)" -ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" - -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | jq '{ - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] - }' + entries: [.entries[] | {current_account_id, predecessor_id, block_height, key, value, tx_hash}] +}' ``` -**When to pivot** - -The first lookup answers the scope-first question: what does this predecessor write right now? Narrowing from that feed to one exact key answers the more specific question: how did this row get here? If the write pattern is still broader than one key, stay on [History by Predecessor](/fastdata/kv/history-by-predecessor) a little longer before you switch to exact-key history or RPC. - -## Common jobs - -### Start from one predecessor’s writes - -**Start here** - -- [All by Predecessor](/fastdata/kv/all-by-predecessor) when you know who wrote the rows but not yet which exact key matters most. - -**Next page if needed** - -- [GET History by Exact Key](/fastdata/kv/get-history-key) if one row becomes the real focus. -- [History by Predecessor](/fastdata/kv/history-by-predecessor) if the broader predecessor write pattern is still the real question. - -**Stop when** - -- You can explain what this predecessor wrote and whether one row deserves deeper history. - -**Switch when** - -- The user needs canonical current chain state rather than indexed storage history. Move to [View State](/rpc/contract/view-state). - -### Turn one exact key into a change history - -**Start here** - -- [GET History by Exact Key](/fastdata/kv/get-history-key) for path-based history lookup. -- [History by Key](/fastdata/kv/history-by-key) when the fully qualified key route is the better fit. - -**Next page if needed** - -- Revisit [GET Latest by Exact Key](/fastdata/kv/get-latest-key) if you want the current indexed value alongside the history. +For `jemartel.near`, the listing mixes an `account_id` identity assertion on `contextual.near` with a run of `graph/follow/*` additions to the same contract. The `tx_hash` on each row is the direct handoff into [/tx/examples](/tx/examples#i-have-one-transaction-hash-what-happened) if you want the full transaction story behind any write. -**Stop when** +Lift the most recent row and replay it through `history`: -- You can explain how the key changed over time. - -**Switch when** - -- The user asks whether the latest indexed value matches the chain right now. - -### Trace writes from one predecessor - -**Start here** - -- [All by Predecessor](/fastdata/kv/all-by-predecessor) for latest rows across contracts touched by one predecessor. -- [History by Predecessor](/fastdata/kv/history-by-predecessor) when you need the write history over time. - -**Next page if needed** - -- Narrow to an exact key if one row becomes the real focus. - -**Stop when** - -- You can answer what this predecessor changed and where. - -**Switch when** - -- The user stops asking about indexed writes and starts asking about the current chain state. - -### Batch-check several known keys - -**Start here** - -- [Multi Lookup](/fastdata/kv/multi) when you already know a fixed set of fully qualified keys. - -**Next page if needed** - -- Move one interesting key to [GET History by Exact Key](/fastdata/kv/get-history-key) if the batch result raises a historical question. - -**Stop when** - -- The batch response already answers which of the keys matter. +```bash +CURRENT_ACCOUNT_ID="$(echo "$FIRST" | jq -r '.entries[0].current_account_id')" +EXACT_KEY="$(echo "$FIRST" | jq -r '.entries[0].key')" +ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" -**Switch when** +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | jq '{entries: [.entries[] | {block_height, value}]}' +``` -- You no longer have a fixed key list and need to inspect the contract or predecessor more broadly. +For the `account_id` row, `history` returns a single write at block `185965311` with value `"jemartel.near:mainnet"` — the identity assertion stands, stable since that block. KV preserves every write equally: a quiet key shows one row, a busy key shows many — same shape, no summarization. ## Common mistakes diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index 863da07..9f4118a 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -10,26 +10,9 @@ page_actions: ## Examples -### Did my contract get touched in the latest finalized block? - -
-
- Flow -

Let NEAR Data answer the monitoring question first, then keep one tx hash or receipt ID for the next surface only if you need it.

-
-
-

01last-block-final resolves the newest finalized height.

-

02block gives one recent hydrated block document with shard payloads already attached.

-

03Summarize direct txs, incoming receipts, execution outcomes, and state_changes for your contract. Treat state_changes as the strongest signal that the contract was actually changed.

-
-
- -`touched: false` is a complete answer for a quiet block. +Each hydrated NEAR Data block document carries per-shard transactions, receipts, execution outcomes, and state changes. The three scenarios below share one `bash` helper that rolls those four signals into a single summary with handoff fields. Define it once, then pipe blocks through it: ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near - contract_touch_summary() { jq -r --arg contract "$1" ' [ .shards[] | { @@ -64,163 +47,77 @@ contract_touch_summary() { sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) }' } +``` -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" +### Did my contract get touched in the latest finalized block? -printf 'Latest finalized block: %s\n' "$FINAL_LOCATION" +`/v0/last_block/final` 302-redirects to the current finalized block; follow it and pipe straight through the helper. -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + +curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ | contract_touch_summary "$TARGET_CONTRACT" ``` -Read it as: - -- `touched: false` means the newest finalized block did not mention or mutate the contract in any of the monitored ways. -- `sample_tx_hash` means you already have a good `/tx` anchor for the next question. -- `sample_receipt_id` without a tx hash usually means the contract showed up through receipt-driven execution, so NEAR Data already saved you the cheaper monitoring step. +Read `touched: false` as a complete, unambiguous answer for a quiet block. On `true`, the handoff fields (`sample_tx_hash`, `sample_receipt_id`) drop you straight into [/tx/examples](/tx/examples) for the readable story. One call replaces scanning chunks by hand — and note that `touched: true` with `state_changes: 0` is a real shape: a receipt can land in a chunk without producing state mutation in the same block. ### Did I see activity optimistically, and did it survive finality? -
-
- Flow -

Use the same contract-touch vocabulary on both surfaces so the comparison stays honest.

-
-
-

01last-block-optimistic resolves the newest optimistic height.

-

02block-optimistic shows the early signal for the same contract.

-

03block at the same height either confirms the same observation or proves finality has not caught up yet.

-
-
- -If finality has already caught up, the optimistic and finalized summaries may match immediately. That is still useful: it tells you the early signal already survived into stable history. +Optimistic blocks live at `/v0/block_opt/{height}`; once finality catches up (usually within one block, ~1s on mainnet), the same height is also served at `/v0/block/{height}`. Run the helper on both and compare. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near - -contract_touch_summary() { - jq -r --arg contract "$1" ' - [ .shards[] | { - shard_id, - direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) - } - | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) - ] as $rows - | { - height: .block.header.height, - hash: .block.header.hash, - contract: $contract, - touched: (($rows | length) > 0), - shards: ($rows | map(.shard_id)), - evidence: { - direct_txs: (($rows | map(.direct_txs) | add) // 0), - incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), - execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), - state_changes: (($rows | map(.state_changes) | add) // 0) - } - }' -} - OPT_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' + | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" - OPT_HEIGHT="${OPT_LOCATION##*/}" -printf 'Latest optimistic block: %s\n' "$OPT_LOCATION" +echo "Optimistic view at $OPT_HEIGHT:" +curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | contract_touch_summary "$TARGET_CONTRACT" -curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" \ - | tee /tmp/neardata-optimistic-block.json \ - | contract_touch_summary "$TARGET_CONTRACT" - -curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT" \ - | tee /tmp/neardata-final-same-height.json >/dev/null - -if jq -e 'type == "null"' /tmp/neardata-final-same-height.json >/dev/null; then - printf 'Finalized block %s is not available yet; finality has not caught up.\n' "$OPT_HEIGHT" +echo "Finalized view at $OPT_HEIGHT:" +FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" +if [ "$(echo "$FINAL" | jq 'type')" = '"null"' ]; then + echo "finality has not caught up to $OPT_HEIGHT yet" else - printf 'Finalized block %s is already available; compare the stable answer below.\n' "$OPT_HEIGHT" - contract_touch_summary "$TARGET_CONTRACT" < /tmp/neardata-final-same-height.json + echo "$FINAL" | contract_touch_summary "$TARGET_CONTRACT" fi ``` -That is the practical split: - -- optimistic is the early signal that your monitoring loop can react to quickly; -- finalized is the stable answer you can show to users or use for durable automation. +On a healthy network the two summaries match immediately; the value is in the pattern, not the dramatic difference. A monitoring loop that reacts to the optimistic signal knows the same answer is one block away from durable. Use the `finality has not caught up` branch when you really do need to distinguish "seen optimistically" from "confirmed" — during chain stress, that gap widens. ### Which shard actually changed my contract in this block? -
-
- Flow -

Use the full block to find the winning shard, then let block-shard prove the actual mutation.

-
-
-

01Scan the finalized block’s shard list for state_changes on your contract.

-

02Open only the shard that actually changed the contract.

-

03Keep the matching state changes and receipt execution outcomes as your shard-local proof.

-
-
- -At the time of writing, recent finalized block `194727131` gave a clean live `intents.near` example: the contract first appeared as an incoming receipt on shard `8`, then actually executed and changed state on shard `7`. - -If you need a fresher block, reuse the same summary from the first example over a few nearby finalized heights and then plug the winning height into the same `block-shard` call. +Blocks are thin — most finalized blocks show no state mutation for any given contract. Walk back from the finalized head until the helper reports `state_changes > 0`, then open the winning shard with `/v0/block/{height}/shard/{shard_id}` for the actual mutation payload. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near -EXAMPLE_HEIGHT=194727131 - -curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT" \ - | tee /tmp/neardata-block-194727131.json \ - | jq --arg contract "$TARGET_CONTRACT" '[ - .shards[] | { - shard_id, - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) - } - | select(.incoming_receipts + .execution_outcomes + .state_changes > 0) - ]' - -curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT/shard/7" \ +HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" +TARGET_HEIGHT="" +WINNING_SHARD="" + +for OFFSET in 0 1 2 3 4 5 6 7 8 9; do + H=$((HEAD - OFFSET)) + SUMMARY="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" | contract_touch_summary "$TARGET_CONTRACT")" + if [ "$(echo "$SUMMARY" | jq '.evidence.state_changes')" -gt 0 ]; then + TARGET_HEIGHT=$H + WINNING_SHARD="$(echo "$SUMMARY" | jq -r '.shards[0]')" + echo "$SUMMARY" + break + fi +done + +curl -s "$NEARDATA_BASE_URL/v0/block/$TARGET_HEIGHT/shard/$WINNING_SHARD" \ | jq --arg contract "$TARGET_CONTRACT" '{ shard_id, chunk_hash: .chunk.header.chunk_hash, - matching_state_changes: [ - .state_changes[] - | select(.change.account_id? == $contract) - | {type, cause, account_id: .change.account_id} - ][0:2], - matching_execution_outcomes: [ - .receipt_execution_outcomes[] - | select(.execution_outcome.outcome.executor_id == $contract) - | { - receipt_id: .execution_outcome.id, - executor_id: .execution_outcome.outcome.executor_id, - status: .execution_outcome.outcome.status, - predecessor_id: .receipt.predecessor_id - } - ][0:2] + matching_state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause_type: (.cause | keys[0]), account_id: .change.account_id}][0:3], + matching_execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0]), predecessor_id: .receipt.predecessor_id}][0:3] }' ``` -That is the practical rule: - -- use `block` when the first question is “which shard mattered?”; -- use `block-shard` when the real question becomes “show me the actual state-changing shard payload.” +On mainnet, `intents.near` consistently executes on shard 7, so the walk-back typically lands within a few blocks. The shard payload then names the actual state-change types (`account_update`, `data_update`, and the like) and the receipt execution outcomes that produced them — shard-local proof without guessing. Widen the `OFFSET` range for less-active contracts. ## When to widen diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index a9ca0d9..1611496 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -16,1519 +16,262 @@ Start with the RPC method that answers the question. Use `tx` to track inclusion ### Track a transaction from hash to finality -Have a tx hash and need to know how far it got? Poll `tx` with the smallest `wait_until` threshold that answers the question. If you are submitting right now, use `broadcast_tx_async` only to get the hash. - -Pinned testnet transaction: - -- transaction hash: `CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow` -- signer: `mike.testnet` -- receiver: `mike.testnet` -- first action: `Transfer` of `1` yoctoNEAR -- observed live on: `2026-04-19` - -
-
- Flow -

Get the hash quickly, use `tx` for the actual stage question, and reach for `EXPERIMENTAL_tx_status` only when the receipt tree becomes the question.

-
-
-

01RPC broadcast_tx_async is the quick submit step when your client will track separately.

-

02RPC tx is the default surface for inclusion, optimistic execution, and finality.

-

03RPC EXPERIMENTAL_tx_status is the receipt-inspection follow-up, not the default tracking loop.

-
-
- -**Live rule** - -`wait_until` is a minimum threshold. The node may answer with a later stage if the transaction advanced while it was waiting. - -Observed when four `tx` polls for this transaction started at the same moment on `2026-04-19`: - -| `wait_until` | returned `final_execution_status` | Takeaway | -| --- | --- | --- | -| `INCLUDED` | `EXECUTED_OPTIMISTIC` | inclusion was satisfied, and execution had already advanced | -| `EXECUTED_OPTIMISTIC` | `EXECUTED_OPTIMISTIC` | optimistic execution was the first stage observed | -| `INCLUDED_FINAL` | `FINAL` | the transaction advanced past included-final before the response came back | -| `FINAL` | `FINAL` | this is the simple “is it actually done?” threshold | +Have a tx hash? Poll `tx` with the smallest `wait_until` threshold that answers your question. ```bash RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow SIGNER_ACCOUNT_ID=mike.testnet -``` - -1. If you are submitting live, get the hash first. -```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data '{ - "jsonrpc": "2.0", - "id": "fastnear", - "method": "broadcast_tx_async", - "params": ["BASE64_SIGNED_TX"] - }' \ - | jq '{tx_hash: .result}' -``` - -This is the clean “submit now, track separately” surface. In live testnet runs, `send_tx` with `wait_until: "NONE"` returned only `final_execution_status: "NONE"` and no hash, so it is not the clearest default when the client owns tracking. - -2. Use `INCLUDED` when the real question is “did this make it into a block yet?” - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "INCLUDED" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_outcome_status: .result.transaction_outcome.outcome.status - }' -``` - -What to notice: - -- `INCLUDED` means “do not answer before inclusion,” not “freeze the reply at included” -- a live reply can already be `EXECUTED_OPTIMISTIC` or `FINAL` -- use this threshold when block inclusion is the question - -3. Use `FINAL` when the real question is “is it actually done?” - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - receipts_outcome_count: (.result.receipts_outcome | length) - }' -``` - -What to notice: - -- `FINAL` is the simplest success threshold for most clients -- `tx` already gives you `receipts_outcome`, which is enough for most tracking loops -- for a historical transaction like this pinned one, the reply returns immediately because the work is already done - -4. Only widen to `EXPERIMENTAL_tx_status` when you need the receipt tree itself. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" --arg signer_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", id: "fastnear", method: "tx", + params: {tx_hash: $tx_hash, sender_account_id: $signer_id, wait_until: "INCLUDED"} + }')" \ | jq '{ + asked: "INCLUDED", final_execution_status: .result.final_execution_status, - receipts_count: (.result.receipts | length), + status_class: (.result.status | keys[0]), receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -`tx` already answers the stage question. `EXPERIMENTAL_tx_status` is the follow-up when you also need the receipt records that `tx` does not include. +For the pinned historical tx (a 1-yocto self-transfer from `mike.testnet`), the response comes back `FINAL` even though we asked for `INCLUDED`. That's the rule: **`wait_until` is a minimum threshold, not a target.** The node returns whatever stage the tx actually reached — for a historical tx that's always `FINAL`; for one in flight, pick `INCLUDED` when you only need inclusion and want the earliest return, or `FINAL` when the real question is "is it done?" -**Use these defaults** +Two handoffs from here: -- Need a hash now: [`broadcast_tx_async`](/rpc/transaction/broadcast-tx-async) -- Need to know whether the transaction is included or final: [`tx`](/rpc/transaction/tx-status) -- Need the receipt tree: [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) -- Need one blocking submission call instead: [`send_tx`](/rpc/transaction/send-tx) +- **Submitting live?** [`broadcast_tx_async`](/rpc/transaction/broadcast-tx-async) returns the hash as soon as the node accepts the payload — track separately with `tx`. [`send_tx`](/rpc/transaction/send-tx) submits and blocks on your chosen `wait_until` in a single call. +- **Need the receipt tree, not just outcomes?** `tx` already includes `receipts_outcome`; widen to [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) only when you also need the raw receipt records. ## Tip Block Inspection ### Describe the first action of the first transaction at the current tip -Need one plain-English description of what the tip block starts with? Read the true tip with `status`, choose the first non-empty chunk from `block`, then inspect that chunk directly. - -
-
- Flow -

Use `status` for the live head, `block` for the ordered chunk list, then `chunk` for the first transaction and its first action.

-
-
-

01RPC status gives the node's current head hash.

-

02RPC block gives the chunk list, so you can pick the first chunk whose tx_root is not the empty sentinel.

-

03RPC chunk gives the transactions and actions. Use transactions[0].actions[0] as the exact answer.

-
-
- -**Flow** - -- Read the live head hash with `status`. -- Fetch that exact block with `block`. -- Scan the block's chunks in returned order and keep the first non-empty chunk. -- Fetch that chunk and read the first transaction's first action. -- Print one human sentence for that action. - -`block` is only enough to choose the chunk. The transaction list lives on `chunk`, so you do not need `tx` for this job. +Walk `status` → `block` → `chunk`, skipping empty chunks along the way. Most chunks in a tip block are empty — their `tx_root` is the sentinel `11111111111111111111111111111111` — so the selector has to filter. ```bash RPC_URL=https://rpc.mainnet.fastnear.com EMPTY_TX_ROOT=11111111111111111111111111111111 -``` - -1. Read the node's true tip and keep the latest block hash. - -```bash -BLOCK_HASH="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data '{ - "jsonrpc": "2.0", - "id": "fastnear", - "method": "status", - "params": [] - }' \ - | tee /tmp/tip-status.json \ - | jq -r '.result.sync_info.latest_block_hash' -)" - -jq '{ - latest_block_height: .result.sync_info.latest_block_height, - latest_block_hash: .result.sync_info.latest_block_hash -}' /tmp/tip-status.json -``` -2. Fetch that block and pick the first non-empty chunk in returned order. +BLOCK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ + | jq -r '.result.sync_info.latest_block_hash')" -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ +CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ --data "$(jq -nc --arg block_hash "$BLOCK_HASH" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "block", - params: { - block_id: $block_hash - } + jsonrpc:"2.0",id:"fastnear",method:"block",params:{block_id:$block_hash} }')" \ - | tee /tmp/tip-block.json >/dev/null - -CHUNK_HASH="$( - jq -r --arg empty_tx_root "$EMPTY_TX_ROOT" ' - first( - .result.chunks[] - | select(.tx_root != $empty_tx_root) - | .chunk_hash - ) // empty - ' /tmp/tip-block.json -)" - -jq --arg empty_tx_root "$EMPTY_TX_ROOT" '{ - block: { - height: .result.header.height, - hash: .result.header.hash - }, - first_non_empty_chunk: ( - first( - .result.chunks[] - | select(.tx_root != $empty_tx_root) - | { - chunk_hash, - shard_id, - tx_root - } - ) // null - ) -}' /tmp/tip-block.json + | jq -r --arg empty "$EMPTY_TX_ROOT" ' + first(.result.chunks[] | select(.tx_root != $empty) | .chunk_hash) // empty')" if [ -z "$CHUNK_HASH" ]; then - echo "tip block had no transactions, rerun on the next block" -fi -``` - -That empty-block branch is intentional. A true tip block can be valid and still have no transactions. - -3. If `CHUNK_HASH` is non-empty, fetch the chosen chunk and print the first transaction plus its first action type. - -```bash -if [ -n "$CHUNK_HASH" ]; then - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ + echo "tip block had no transactions in any chunk — rerun on the next head" +else + curl -s "$RPC_URL" -H 'content-type: application/json' \ --data "$(jq -nc --arg chunk_hash "$CHUNK_HASH" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - chunk_id: $chunk_hash - } + jsonrpc:"2.0",id:"fastnear",method:"chunk",params:{chunk_id:$chunk_hash} }')" \ - | tee /tmp/tip-chunk.json >/dev/null - - jq '{ - chunk: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_included: .result.header.height_included - }, - first_transaction: { - hash: .result.transactions[0].hash, - signer_id: .result.transactions[0].signer_id, - receiver_id: .result.transactions[0].receiver_id - }, - first_action_type: (.result.transactions[0].actions[0] | keys[0]) - }' /tmp/tip-chunk.json -fi -``` - -4. If the chunk fetch ran, turn that first action into one human sentence. - -```bash -if [ -n "$CHUNK_HASH" ]; then - FIRST_ACTION_TYPE="$(jq -r '.result.transactions[0].actions[0] | keys[0]' /tmp/tip-chunk.json)" - SIGNER_ID="$(jq -r '.result.transactions[0].signer_id' /tmp/tip-chunk.json)" - RECEIVER_ID="$(jq -r '.result.transactions[0].receiver_id' /tmp/tip-chunk.json)" - - if [ "$FIRST_ACTION_TYPE" = "FunctionCall" ]; then - METHOD_NAME="$(jq -r '.result.transactions[0].actions[0].FunctionCall.method_name' /tmp/tip-chunk.json)" - RENDERED_ARGS="$( - jq -r ' - .result.transactions[0].actions[0].FunctionCall.args as $raw - | try ($raw | @base64d | fromjson | tojson) catch $raw - ' /tmp/tip-chunk.json - )" - - printf '%s\n' "$SIGNER_ID called $METHOD_NAME on $RECEIVER_ID with args: $RENDERED_ARGS" - else - printf '%s\n' "$SIGNER_ID sent $FIRST_ACTION_TYPE to $RECEIVER_ID" - fi + | jq '{ + chunk_shard: .result.header.shard_id, + chunk_height: .result.header.height_included, + first_tx: { + hash: .result.transactions[0].hash, + signer_id: .result.transactions[0].signer_id, + receiver_id: .result.transactions[0].receiver_id + }, + first_action: ( + .result.transactions[0].actions[0] as $a + | if ($a | type) == "string" then {kind: $a} + elif $a.FunctionCall then {kind: "FunctionCall", method_name: $a.FunctionCall.method_name} + else {kind: ($a | keys[0])} end + ) + }' fi ``` -That last step deliberately stays simple: - -- if the first action is `FunctionCall`, print the method plus decoded JSON args when they decode cleanly -- otherwise print the action type, signer, and receiver without adding more protocol interpretation +A live run returns the current tip's first chunk, first transaction, and first action — often a `FunctionCall` on a bridge or tg-bot contract (mainnet is active). A tip block can be valid and still have no transactions in any chunk, which is why the empty branch stays; it's the honest answer for a quiet moment on the network. ## Account and Key Mechanics -### Audit and remove old Near Social function-call keys - -
-
- Flow -

Use exact key reads to narrow the target first, then sign exactly one delete.

-
-
-

01RPC view_access_key_list finds only the function-call keys scoped to social.near.

-

02RPC view_access_key double-checks the one key you plan to remove, and POST /v0/account is only for optional account-level context.

-

03RPC send_tx submits the DeleteKey, then RPC view_access_key_list closes the loop.

-
-
- -**Flow** - -- Use RPC itself to list every access key on the account. -- Narrow that list to function-call keys scoped to `social.near`. -- Inspect one candidate key exactly before you delete it. -- Build and sign a `DeleteKey` transaction with a full-access key, then submit it through RPC and verify the key is gone. - -Two caveats matter up front: - -- The deleting key must be a full-access key. A function-call key cannot sign a `DeleteKey` action. -- This flow is about exact key state and cleanup. The optional Transactions API step below gives account-level context, not authoritative per-key “last used” forensics. - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export SOCIAL_RECEIVER_ID=social.near -export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' -export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' -export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' -``` - -1. List all access keys on the account, then narrow to `social.near` function-call keys. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } - }')" \ - | tee /tmp/fastnear-access-keys.json >/dev/null - -jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' - .result.keys[] - | select((.access_key.permission | type) == "object") - | select(.access_key.permission.FunctionCall.receiver_id == $receiver) - | { - public_key, - nonce: .access_key.nonce, - receiver_id: .access_key.permission.FunctionCall.receiver_id, - method_names: .access_key.permission.FunctionCall.method_names, - allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") - } -' /tmp/fastnear-access-keys.json -``` - -Pick one `public_key` from that filtered list and set `DELETE_PUBLIC_KEY` to it. - -2. Inspect the specific candidate key one more time before deleting it. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$DELETE_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } - }')" \ - | jq '{nonce: .result.nonce, permission: .result.permission}' -``` - -3. Pull recent function-call activity for the account only if you want more context before cleanup. - -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - is_function_call: true, - limit: 10 - }')" \ - | jq '{ - account_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_success - } - ] - }' -``` - -That query helps answer “has this account still been doing function-call work recently?”, but it does not prove that a specific access key was the one used. - -4. Sign a `DeleteKey` transaction for `DELETE_PUBLIC_KEY` with a full-access key. +### Audit old Near Social function-call keys -Run this in a directory where `near-api-js@5` is installed. The command reads the environment variables above, fetches the latest nonce for `FULL_ACCESS_PUBLIC_KEY`, fetches a fresh final block hash, signs a `DeleteKey` action, and stores the resulting `signed_tx_base64` in `SIGNED_TX_BASE64`. +Creators accumulate Social function-call keys from every wallet and BOS gateway they've used. `view_access_key_list` returns all of them; one filter narrows to `social.near`, and the **low six digits of the nonce** double as a usage counter — new keys start at `block_height * 10^6` and increment by one per transaction. ```bash -SIGNED_TX_BASE64="$( - node --input-type=module <<'EOF' -import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; - -const { - ACCOUNT_ID, - NETWORK_ID = 'mainnet', - RPC_URL = 'https://rpc.mainnet.fastnear.com', - DELETE_PUBLIC_KEY, - FULL_ACCESS_PUBLIC_KEY, - FULL_ACCESS_PRIVATE_KEY, -} = process.env; - -for (const name of [ - 'ACCOUNT_ID', - 'DELETE_PUBLIC_KEY', - 'FULL_ACCESS_PUBLIC_KEY', - 'FULL_ACCESS_PRIVATE_KEY', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); -const derivedPublicKey = keyPair.getPublicKey().toString(); - -if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { - throw new Error( - `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: ACCOUNT_ID, - public_key: FULL_ACCESS_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const transaction = transactions.createTransaction( - ACCOUNT_ID, - utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), - ACCOUNT_ID, - BigInt(accessKey.nonce) + 1n, - [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -)" -``` - -5. Submit the signed transaction through raw RPC and wait for `FINAL`. +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +RECEIVER_ID=social.near -```bash curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Re-run the access-key list and verify that the deleted key is gone. - -```bash -if curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_access_key_list",account_id:$account_id,finality:"final"} }')" \ - | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' - .result.keys[] - | select(.public_key == $public_key) - ' >/dev/null; then - echo "Key is still present: $DELETE_PUBLIC_KEY" -else - echo "Key deleted: $DELETE_PUBLIC_KEY" -fi + | jq --arg receiver "$RECEIVER_ID" ' + { + total_keys: (.result.keys | length), + social_fcks: [ + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + created_near_block: (.access_key.nonce / 1000000 | floor), + tx_count: (.access_key.nonce % 1000000), + method_names: (.access_key.permission.FunctionCall.method_names | if . == [] then "ANY" else . end), + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } + ] | sort_by(.tx_count) + }' ``` -**When to pivot** +For `mike.near`, this returns dozens of `social.near` function-call keys. Entries with `tx_count: 0` were created and never used — prime candidates for cleanup. `method_names: "ANY"` means the key can call any method on `social.near`; a narrowed list like `["find_grants", "insert_grant", "delete_grant"]` means the key was scoped to a specific dapp's write surface. -Re-running `view_access_key_list` closes the loop on the same RPC method you used for discovery. If the delete succeeded there, you do not need an indexed API to prove the cleanup. +To delete one, sign a `DeleteKey` action with a **full-access** key — a function-call key cannot authorize `DeleteKey` — then submit via [`send_tx`](/rpc/transaction/send-tx). Re-run the same list to confirm the deletion. The signing itself is standard near-api-js territory and not the interesting part of the audit. ### Which transaction added this `social.near` function-call key, and who authorized it? -
-
- Flow -

Start from the live key, then walk backward only as far as you need.

-
-
-

01RPC view_access_key gives the current stored nonce, which is the best historical clue you have.

-

02POST /v0/account turns that nonce into a tight candidate window instead of a whole-account search.

-

03POST /v0/transactions tells you whether the key was added directly or through delegated authorization, and POST /v0/receipt is only for the exact AddKey execution block.

-
-
- -**Flow** - -- Read the exact key first with RPC and keep its current nonce as the clue. -- Convert that nonce into a tight block-height window for the likely `AddKey` receipt. -- Search account history only inside that window instead of scanning the whole account. -- Hydrate the candidate transaction and distinguish three different keys: - - the key that got added - - the top-level signer public key - - the delegated authorizing public key, if the change was wrapped in a `Delegate` action - -Three nonce details matter up front: - -- New access keys start with a nonce derived from block height at roughly `block_height * 1_000_000`, so dividing the current nonce by `1_000_000` gives a useful search window. -- The `AddKey` action payload often shows `access_key.nonce: 0`. That is not the stored nonce you later see from `view_access_key`. -- If the key has been used heavily since creation, widen the search window a bit more. +The same nonce that tracks usage also anchors the `AddKey` in block time: new keys start at roughly `block_height * 10^6`, so dividing the current nonce by a million gives a tight search window. Hydrate the candidates once, and the response carries enough to distinguish a direct `AddKey` from a delegated (meta-tx) authorization — which tells you *which key signed the decision*, not just which account paid gas. ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' - -# Sample live key observed on April 18, 2026: -# export ACCOUNT_ID=mike.near -# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' -``` - -1. Read the exact key first, then turn its current nonce into a search window. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$TARGET_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } - }')" \ - | tee /tmp/key-origin-view.json >/dev/null - -CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" -ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" -SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" -SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" - -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_public_key "$TARGET_PUBLIC_KEY" \ - --argjson current_nonce "$CURRENT_NONCE" \ - --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ - --argjson search_from "$SEARCH_FROM" \ - --argjson search_to "$SEARCH_TO" \ - --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ - account_id: $account_id, - target_public_key: $target_public_key, - current_nonce: $current_nonce, - estimated_receipt_block: $estimated_receipt_block, - search_from_tx_block_height: $search_from, - search_to_tx_block_height: $search_to, - permission: ($permission | fromjson) - }' -``` - -If you use the sample key above, the estimated receipt block should land at `112057392`. +RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=mike.near +TARGET_PUBLIC_KEY=ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs + +CURRENT_NONCE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg public_key "$TARGET_PUBLIC_KEY" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_access_key",account_id:$account_id,public_key:$public_key,finality:"final"} + }')" \ + | jq -r '.result.nonce')" -2. Search account history only inside that block neighborhood. +ADD_KEY_BLOCK=$((CURRENT_NONCE / 1000000)) -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_tx_block_height "$SEARCH_FROM" \ - --argjson to_tx_block_height "$SEARCH_TO" '{ - account_id: $account_id, - is_real_signer: true, - from_tx_block_height: $from_tx_block_height, - to_tx_block_height: $to_tx_block_height, - desc: false, - limit: 50 +TX_HASHES="$(curl -s "$TX_BASE_URL/v0/account" -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" \ + --argjson from $((ADD_KEY_BLOCK - 20)) --argjson to $((ADD_KEY_BLOCK + 5)) '{ + account_id: $account_id, is_real_signer: true, + from_tx_block_height: $from, to_tx_block_height: $to, desc: false, limit: 50 }')" \ - | tee /tmp/key-origin-candidates.json >/dev/null - -jq '{ - txs_count, - candidate_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_signer, - is_real_signer, - is_predecessor, - is_receiver - } - ] -}' /tmp/key-origin-candidates.json -``` - -With the sample `mike.near` key above, this window returns one candidate transaction: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` at outer tx block `112057390`. - -3. Hydrate those candidates and keep only the transaction that actually added your target key. - -```bash -TX_HASHES_JSON="$( - jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json -)" - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ - | tee /tmp/key-origin-transactions.json >/dev/null - -jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "direct", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $tx.transaction.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }), - ($tx.transaction.actions[]? - | .Delegate? - | .delegate_action as $delegate - | $delegate.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) + | jq -c '[.account_txs[].transaction_hash]')" + +curl -s "$TX_BASE_URL/v0/transactions" -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES" '{tx_hashes: $tx_hashes}')" \ + | jq --arg target "$TARGET_PUBLIC_KEY" ' + [ .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? | .AddKey? | select(.public_key == $target) + | {mode: "direct", authorizing_public_key: $tx.transaction.public_key, permission: .access_key.permission}), + ($tx.transaction.actions[]? | .Delegate? | .delegate_action as $d + | $d.actions[]? | .AddKey? | select(.public_key == $target) + | {mode: "delegated", authorizing_public_key: $d.public_key, permission: .access_key.permission}) + ) | { - authorization_mode: "delegated", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $delegate.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - tx_block_hash: $tx.execution_outcome.block_hash, - receiver_id: $tx.transaction.receiver_id - } + . -' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + signer_id: $tx.transaction.signer_id, + receiver_id: $tx.transaction.receiver_id, + add_key_receipt: ([$tx.receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]?; .AddKey.public_key? == $target)) + | {receipt_id: .receipt.receipt_id, receipt_block: .execution_outcome.block_height}][0]) + } + . + ]' ``` -If `authorization_mode` is `direct`, the top-level signer public key and the authorizing public key are the same. If `authorization_mode` is `delegated`, the key that actually authorized the `AddKey` lives inside `Delegate.delegate_action.public_key`. - -With the sample `mike.near` key above, the match is delegated: - -- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` -- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` -- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` -- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` - -4. If you need the exact `AddKey` receipt block too, pivot one more time by receipt ID. - -```bash -ADD_KEY_RECEIPT_ID="$( - jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | .receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) - | .receipt.receipt_id - ' /tmp/key-origin-transactions.json | head -n 1 -)" - -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receipt_block_height: .receipt.block_height, - tx_block_height: .receipt.tx_block_height, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - transaction_hash: .receipt.transaction_hash - }' -``` - -For the sample key above, the exact `AddKey` receipt is `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` in receipt block `112057392`, while the outer transaction landed earlier in block `112057390`. - -**When to pivot** +For `mike.near`'s `ed25519:7GZg…` key (the first `social.near` FCK from the audit above), this resolves to transaction `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` at outer tx block `112057390`. The outer signer is `app.herewallet.near` — HERE Wallet's relayer — and `mode: "delegated"` tells the rest of the story: the relayer paid gas, but the *authorizing* key inside the Delegate is `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu`, a `mike.near` full-access key that signed the underlying `AddKey`. That's the meta-tx distinction the top-level `signer_id` alone would hide. -Start with exact current key state because it gives you the nonce clue. A tight `/v0/account` window turns that clue into a small candidate set. `/v0/transactions` tells you whether the key was added directly or through delegated authorization. `/v0/receipt` is the optional last step when you need the exact `AddKey` receipt block, not just the outer transaction. +`add_key_receipt` completes the picture: the `AddKey` executed in block `112057392`, two blocks after the outer tx, because the Delegate hops from the relayer's shard to the target account's. Widen the `-20/+5` window if the key has been used heavily since creation. ### Register FT storage if needed, then transfer tokens -
-
- Flow -

Read storage first, then spend the minimum write calls needed to make the transfer stick.

-
-
-

01RPC call_function storage_balance_of tells you whether the receiver is already registered.

-

02RPC call_function storage_balance_bounds only matters if you need the exact minimum deposit before writing.

-

03RPC send_tx submits storage_deposit and ft_transfer, then RPC call_function ft_balance_of proves the result.

-
-
- -**Network** - -- testnet - -**Official references** - -- [FT storage and transfer](https://docs.near.org/integrations/fungible-tokens) -- [Pre-deployed FT contract](https://docs.near.org/tutorials/fts/predeployed-contract) - -This walkthrough uses the safe public contract `ft.predeployed.examples.testnet`. Before you start, make sure the sender already holds some `gtNEAR` there. If not, mint a small balance first with the pre-deployed contract guide above and then come back to this flow. - -**Flow** - -- Use exact RPC view calls to check whether the receiver already has FT storage on the contract. -- If needed, fetch the minimum storage requirement. -- Sign and submit `storage_deposit`, then `ft_transfer`. -- Verify the receiver balance with the same contract’s own view method. +NEP-141 tokens require each recipient to pre-register storage on the contract before they can hold a balance. Two view calls answer the registration question authoritatively *before* you send — skipping that check is how `ft_transfer` ends up quietly refunded to the sender. ```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet -export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' -export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' -export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' -``` - -1. Check whether the receiver is already registered on the FT contract. - -```bash -STORAGE_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$STORAGE_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "storage_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/ft-storage-balance.json >/dev/null +RPC_URL=https://rpc.testnet.fastnear.com +TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +RECEIVER_ACCOUNT_ID=mike.testnet -jq '{ - registered: ((.result.result | implode | fromjson) != null), - storage_balance: (.result.result | implode | fromjson) -}' /tmp/ft-storage-balance.json -``` +ACCOUNT_ARGS_B64="$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -2. If the receiver is not registered yet, fetch the minimum storage deposit. +REGISTERED="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_of",args_base64:$args,finality:"final"} + }')" \ + | jq '(.result.result | implode | fromjson) != null')" -```bash -MIN_STORAGE_YOCTO="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_CONTRACT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "storage_balance_bounds", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee /tmp/ft-storage-bounds.json \ - | jq -r '.result.result | implode | fromjson | .min' -)" +MIN_DEPOSIT="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_bounds",args_base64:"e30=",finality:"final"} + }')" \ + | jq -r '.result.result | implode | fromjson | .min')" -printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" +jq -n --argjson registered "$REGISTERED" --arg min "$MIN_DEPOSIT" '{ + registered: $registered, + min_storage_deposit_yocto: $min +}' ``` -3. Define one reusable signer for contract function calls. - -Run this in a directory where `near-api-js@5` is installed. The function below reads the exported shell variables above and turns each function call into a signed payload for raw RPC submission. - -```bash -sign_function_call() { - METHOD_NAME="$1" \ - ARGS_JSON="$2" \ - DEPOSIT_YOCTO="$3" \ - GAS_TGAS="$4" \ - node --input-type=module <<'EOF' -import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; - -const { - NETWORK_ID = 'testnet', - RPC_URL = 'https://rpc.testnet.fastnear.com', - TOKEN_CONTRACT_ID, - SENDER_ACCOUNT_ID, - SENDER_PUBLIC_KEY, - SENDER_PRIVATE_KEY, - METHOD_NAME, - ARGS_JSON, - DEPOSIT_YOCTO = '0', - GAS_TGAS = '100', -} = process.env; - -for (const name of [ - 'TOKEN_CONTRACT_ID', - 'SENDER_ACCOUNT_ID', - 'SENDER_PUBLIC_KEY', - 'SENDER_PRIVATE_KEY', - 'METHOD_NAME', - 'ARGS_JSON', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); -const signer = await InMemorySigner.fromKeyPair( - NETWORK_ID, - SENDER_ACCOUNT_ID, - keyPair -); - -const derivedPublicKey = keyPair.getPublicKey().toString(); -if (derivedPublicKey !== SENDER_PUBLIC_KEY) { - throw new Error( - `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: SENDER_ACCOUNT_ID, - public_key: SENDER_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const action = transactions.functionCall( - METHOD_NAME, - Buffer.from(ARGS_JSON), - BigInt(GAS_TGAS) * 10n ** 12n, - BigInt(DEPOSIT_YOCTO) -); - -const transaction = transactions.createTransaction( - SENDER_ACCOUNT_ID, - utils.PublicKey.fromString(SENDER_PUBLIC_KEY), - TOKEN_CONTRACT_ID, - BigInt(accessKey.nonce) + 1n, - [action], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - SENDER_ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -} -``` +For the pinned testnet contract, `storage_balance_of({account_id: "mike.testnet"})` returns `null` (not registered) and `storage_balance_bounds` returns `{min: "1250000000000000000000", max: "1250000000000000000000"}` — a flat 0.00125 NEAR registration fee. That's the contract's own answer, and it's all the read side you need before you write. -4. If needed, register the receiver for storage first. +The write side is two signed function calls (near-api-js `transactions.functionCall` or any NEAR signer library works identically): -```bash -if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then - SIGNED_TX_BASE64="$( - sign_function_call \ - storage_deposit \ - "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id, - registration_only: true - }')" \ - "$MIN_STORAGE_YOCTO" \ - 100 - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash - }' -fi -``` +- `storage_deposit({account_id: "", registration_only: true})` with deposit `` yocto and 100 Tgas — skip if `registered: true`. +- `ft_transfer({receiver_id: "", amount: "", memo: "..."})` with deposit 1 yocto (required by NEP-141) and 100 Tgas. -5. Transfer the FT after storage is ready. +Submit each signed transaction through [`send_tx`](/rpc/transaction/send-tx) with `wait_until: "FINAL"`. Verify afterward with the contract's own view method — no need for indexed history to prove the transfer stuck: ```bash -SIGNED_TX_BASE64="$( - sign_function_call \ - ft_transfer \ - "$(jq -nc \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ - receiver_id: $receiver_id, - amount: $amount, - memo: "FastNear RPC example" - }')" \ - 1 \ - 100 -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"ft_balance_of",args_base64:$args,finality:"final"} }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Verify the receiver’s FT balance with the contract’s own view method. - -```bash -RECEIVER_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - receiver_balance: (.result.result | implode | fromjson) - }' + | jq '{receiver_balance: (.result.result | implode | fromjson)}' ``` -**When to pivot** - -This is a good RPC example because every step stays close to the contract itself: first check storage state, then send the minimum required change calls, then verify the post-transfer balance directly on the contract. - ## Contract Reads and Raw State ### How do I read a contract's raw storage directly? -This walkthrough uses the live public testnet contract `counter.near-examples.testnet`. The number can change over time. The useful part is the shape of the read: - -- `view_state` reads the raw `STATE` entry directly from contract storage -- `call_function get_num` asks the contract for the same current number through its public view API - -
-
- Flow -

Read the storage the hard way first, then let the contract confirm the same answer through its view method.

-
-
-

01RPC view_state reads the raw STATE entry without running contract code.

-

02Decode the base64 value into bytes, then interpret those bytes with the contract’s known Borsh layout.

-

03RPC call_function get_num is the friendly cross-check that the raw-state read and the view method still agree.

-
-
- -Keep the distinction straight: - -- `view_state` is a direct storage read from the trie -- `call_function` executes a read-only method on the contract -- both can answer the same question, but they do different work to get there - -```mermaid -flowchart LR - S["RPC view_state
prefix STATE"] --> R["Raw STATE bytes"] - R --> D["Decode base64 + Borsh"] - D --> N["Signed counter value"] - C["RPC call_function get_num"] --> J["JSON method result"] - N --> X["Compare"] - J --> X - X --> A["Same current counter value"] -``` - -**Flow** - -- Read the raw `STATE` key from contract storage. -- Decode the returned bytes into the current signed counter value. -- Call `get_num` through the view method and confirm that the method answer matches the raw-state decode. +Two RPC methods answer the same counter question from different layers: `view_state` pulls raw trie bytes without executing code, and `call_function` runs the contract's own view method. When they agree, you've proved the contract's view method matches its stored state. ```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export CONTRACT_ID=counter.near-examples.testnet -export STATE_PREFIX_BASE64=U1RBVEU= -``` - -1. Read the raw contract state first. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$CONTRACT_ID" \ - --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_state", - account_id: $account_id, - prefix_base64: $prefix_base64, - finality: "final" - } - }')" \ - | tee /tmp/counter-view-state.json >/dev/null - -jq '{ - block_height: .result.block_height, - key_base64: .result.values[0].key, - value_base64: .result.values[0].value -}' /tmp/counter-view-state.json - -jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json -``` - -That last command should print `STATE`. This is the key family you already knew ahead of time, so `view_state` can go straight to the raw storage entry without asking the contract to execute any method. - -2. Decode the returned value bytes into the signed counter. - -```bash -RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" - -python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . -import base64 -import json -import sys - -raw = base64.b64decode(sys.argv[1]) - -print(json.dumps({ - "value_base64": sys.argv[1], - "bytes": list(raw), - "hex": raw.hex(), - "signed_i8": int.from_bytes(raw, "little", signed=True), - "unsigned_u8": int.from_bytes(raw, "little", signed=False), -})) -PY -``` - -For this specific contract, one byte is enough because the Rust counter stores `val: i8` inside the contract state. That is why a raw value like `CQ==` decodes to one byte `0x09`, which reads as the signed integer `9`. - -One small signed-value note is worth keeping in your head: if the counter were negative, the same one-byte payload would still decode correctly as a signed two's-complement `i8`. For example, `/w==` is the single byte `0xff`, which means `-1` as `signed_i8`, not `255`. - -The reusable recipe is small: - -- `view_state` gives you base64-encoded raw bytes -- you decode those bytes with the contract’s known storage layout -- for larger contracts, that layout may be more complex, but the idea is the same: bytes first, schema second - -3. Now ask the contract the friendly way and compare. +RPC_URL=https://rpc.testnet.fastnear.com +CONTRACT_ID=counter.near-examples.testnet -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$CONTRACT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_num", - args_base64: "e30=", - finality: "final" - } +RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_state",account_id:$contract,prefix_base64:"U1RBVEU=",finality:"final"} }')" \ - | tee /tmp/counter-call-function.json >/dev/null + | jq -r '.result.values[0].value')" -jq '{ - block_height: .result.block_height, - view_method_value: (.result.result | implode | fromjson) -}' /tmp/counter-call-function.json -``` - -4. Compare both answers directly. +RAW_I8="$(python3 -c "import base64,sys;print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" -```bash -RAW_STATE_NUMBER="$( - python3 - "$RAW_VALUE_BASE64" <<'PY' -import base64 -import sys - -raw = base64.b64decode(sys.argv[1]) -print(int.from_bytes(raw, "little", signed=True)) -PY -)" - -VIEW_METHOD_NUMBER="$( - jq -r '.result.result | implode | fromjson' /tmp/counter-call-function.json -)" - -jq -n \ - --argjson raw_state "$RAW_STATE_NUMBER" \ - --argjson view_method "$VIEW_METHOD_NUMBER" '{ - raw_state: $raw_state, - view_method: $view_method, - agrees_now: ($raw_state == $view_method) - }' -``` - -If `agrees_now` is `true`, you have proved the point of the example: - -- `view_state` answered the question by reading storage directly -- `call_function get_num` answered the same question by running the contract’s public read method - -**When to pivot** - -Use `view_state` when the real question is about exact storage, missing view methods, or verifying a known key family. Use `call_function` when you want the contract’s public read API. If the next question becomes historical instead of “what is it right now?”, that is the moment to widen into [KV FastData API](/fastdata/kv). - -## Chunk and Shard Tracing - -### Trace a generated transfer receipt from one shard chunk to another - -This walkthrough is pinned to: - -- transaction `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` from `7419369993.tg` to `game.hot.tg` calling `l2_claim` -- origin chunk `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` on shard `11` in block `194623170` -- first receipt chunk `FJWpAYzVXbZwqJUbGXELTnnBBkdvc6W8vWkwuUA3Zwz9` on shard `11` in block `194623171` -- generated `Transfer` receipt `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` -- destination chunk `EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU` on shard `6` in block `194623172` - -
-
- Flow -

Recover the receipt chain first, inspect the generated receipt directly, then map each leg back to the shard chunk that actually carried it.

-
-
-

01RPC EXPERIMENTAL_tx_status quickly shows the receipt graph and which later blocks the work moved into.

-

02RPC EXPERIMENTAL_receipt lets you inspect the generated receipt payload directly instead of inferring it from logs alone.

-

03RPC chunk by block-and-shard or by chunk hash proves which shard-local execution unit actually carried each step.

-
-
- -The two experimental methods here are a good fit for advanced tracing: `EXPERIMENTAL_tx_status` finds the receipt graph quickly, and `EXPERIMENTAL_receipt` shows the generated receipt body before you map it back to chunks. - -```mermaid -flowchart LR - A["Tx 8xrc...
block 194623170
chunk Bfyd...
shard 11"] --> B["Receipt AFC2...
block 194623171
chunk FJWp...
shard 11
ft_mint logs"] - B --> C["Generated receipt TtRn...
Transfer 1800930478788300000000 yoctoNEAR"] - C --> D["Chunk EPau...
block 194623172
shard 6
receipt executes"] -``` - -**Flow** - -- Recover the receipt chain from the transaction first. -- Inspect the generated `Transfer` receipt body directly. -- Use chunk coordinates when you know the block and shard. -- Use chunk hash when another tool already handed you the exact destination chunk. - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_HASH=8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP -export SIGNER_ACCOUNT_ID=7419369993.tg -export ORIGIN_BLOCK_HEIGHT=194623170 -export ORIGIN_SHARD_ID=11 -export RECEIPT_BLOCK_HEIGHT=194623171 -export RECEIPT_SHARD_ID=11 -export GENERATED_RECEIPT_ID=TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4 -export DESTINATION_CHUNK_HASH=EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU -``` - -1. Start with `EXPERIMENTAL_tx_status` so you can see the receipt graph before you think about chunks. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: [$tx_hash, $signer_account_id] - }')" \ - | tee /tmp/chunk-trace-status.json >/dev/null - -jq '{ - final_execution_status: .result.final_execution_status, - transaction_handoff: .result.transaction_outcome.outcome.status, - receipts: ( - .result.receipts_outcome - | map({ - receipt_id: .id, - executor_id: .outcome.executor_id, - block_hash, - status: .outcome.status - }) - ) -}' /tmp/chunk-trace-status.json -``` - -What to notice: - -- the signed transaction hands off into receipt `AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y` -- later in the same receipt graph, `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` executes for `7419369993.tg` -- the high-level tx status is already enough to tell you that the real work continued after the original signed transaction - -2. Inspect the generated receipt directly so you can prove that the follow-up object is a real `Transfer` receipt. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$GENERATED_RECEIPT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_receipt", - params: { - receipt_id: $receipt_id - } +METHOD_VALUE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"get_num",args_base64:"e30=",finality:"final"} }')" \ - | tee /tmp/chunk-trace-receipt.json >/dev/null - -jq '{ - predecessor_id: .result.predecessor_id, - receiver_id: .result.receiver_id, - signer_id: .result.receipt.Action.signer_id, - signer_public_key: .result.receipt.Action.signer_public_key, - actions: .result.receipt.Action.actions -}' /tmp/chunk-trace-receipt.json -``` - -This is the point where the shard story becomes concrete: the contract flow generated a `Transfer` action receipt from `system` to `7419369993.tg` with deposit `1800930478788300000000`. - -3. Use chunk by block and shard to locate the original signed transaction on shard `11`. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --argjson block_id "$ORIGIN_BLOCK_HEIGHT" \ - --argjson shard_id "$ORIGIN_SHARD_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - block_id: $block_id, - shard_id: $shard_id - } - }')" \ - | jq --arg tx_hash "$TX_HASH" '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created - }, - matching_transaction: ( - .result.transactions[] - | select(.hash == $tx_hash) - | { - hash, - signer_id, - receiver_id - } - ) - }' -``` - -This is the cleanest use of `chunk` by block and shard: you already know the coordinates, and you want the exact shard-local execution unit that carried the original signed transaction. - -4. Stay on the same route for the next block and watch the first receipt execute on the same shard. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --argjson block_id "$RECEIPT_BLOCK_HEIGHT" \ - --argjson shard_id "$RECEIPT_SHARD_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - block_id: $block_id, - shard_id: $shard_id - } - }')" \ - | jq '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created, - tx_root: .result.header.tx_root, - gas_used: .result.header.gas_used - }, - tx_count: (.result.transactions | length), - receipt_count: (.result.receipts | length), - matching_receipt: ( - .result.receipts[] - | select(.receipt_id == "AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y") - | { - receipt_id, - predecessor_id, - receiver_id - } - ) - }' -``` - -This is the chunk moment that makes the whole concept natural: - -- the chunk has `tx_root = 11111111111111111111111111111111` -- `tx_count` is `0` -- but the shard still burned gas and executed receipt `AFC2...` - -In other words, this shard did real work in that block even though no new signed transaction appeared directly inside the chunk. - -5. Switch to chunk by hash once another tool has already given you the exact destination chunk. + | jq -r '.result.result | implode | fromjson')" -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg chunk_id "$DESTINATION_CHUNK_HASH" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - chunk_id: $chunk_id - } - }')" \ - | jq --arg receipt_id "$GENERATED_RECEIPT_ID" '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created, - tx_root: .result.header.tx_root, - gas_used: .result.header.gas_used - }, - tx_count: (.result.transactions | length), - receipt_count: (.result.receipts | length), - matching_receipt: ( - .result.receipts[] - | select(.receipt_id == $receipt_id) - | { - receipt_id, - predecessor_id, - receiver_id - } - ) - }' +jq -n --arg raw_b64 "$RAW_B64" --argjson raw_i8 "$RAW_I8" --argjson method "$METHOD_VALUE" '{ + raw_state_b64: $raw_b64, + raw_state_decoded: $raw_i8, + view_method_value: $method, + agree: ($raw_i8 == $method) +}' ``` -This confirms the cross-shard hop: +For the live counter, `view_state` at key `STATE` (base64 `U1RBVEU=`) returns `"CQ=="` — one byte `0x09`, decoded as signed i8 to `9`; `get_num` also returns `9`. They agree because the contract stores `val: i8` at that key. The `signed=True` matters: a negative counter would show up as `"/w=="` (byte `0xff` → i8 `-1`, not u8 `255`). -- the generated `Transfer` receipt executes in chunk `EPau...` -- that chunk lives on shard `6`, not shard `11` -- the signed transaction started on one shard, but the later receipt finished on another - -**When to pivot** - -Use [`Chunk by Block and Shard`](/rpc/protocol/chunk-by-block-shard) when you know the block and shard coordinates and want to ask “what did this shard execute in this block?” Use [`Chunk by Hash`](/rpc/protocol/chunk-by-hash) when another tool has already handed you the exact chunk hash. Use [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) and [`EXPERIMENTAL_receipt`](/rpc/transaction/experimental-receipt) when the real question is receipt-driven tracing. If you also need state changes and produced receipts, widen to [Block Shard](/neardata/block-shard). +`view_state` is the right tool when a contract lacks a view method for the data you need, when you want to verify a view method against actual storage, or when you need a key family the contract doesn't expose publicly. For everything else, `call_function` is lower ceremony. If the next question becomes historical rather than current, widen to [KV FastData API](/fastdata/kv). ## NEAR Social and BOS Exact Reads @@ -1536,454 +279,87 @@ These stay on exact SocialDB reads and on-chain readiness checks until the quest ### Can this account still publish to NEAR Social right now? -
-
- Flow -

Ask social.near for the two things that matter before you sign anything.

-
-
-

01RPC view_account makes sure the signer account exists and can actually submit a transaction.

-

02RPC call_function get_account_storage tells you whether the target account has room left on social.near.

-

03RPC call_function is_write_permission_granted only comes into play when a different signer is trying to write on that account’s behalf.

-
-
- -Required checks: - -- does the target account already have storage on `social.near`? -- if it does, is there still room left in that storage? -- if a different signer is trying to write under that account, has write permission already been granted? - -**Official references** - -- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) - -**Flow** - -- Check that the signer account itself exists and can pay gas. -- Ask `social.near` how much storage the target account has left. -- If the signer differs from the target account, ask `social.near` whether that delegated write is already allowed. -- Turn those exact RPC answers into one simple “ready now” or “fix this first” summary. +`social.near` knows two things a wallet UI can only guess at: how much storage each account has left, and whether a delegated signer is allowed to write under it. Two view calls collapse the readiness question to a single boolean. ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mike.near -export SIGNER_ACCOUNT_ID=mike.near -``` +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near # account you're writing under +SIGNER_ACCOUNT_ID=mike.near # account signing the transaction -1. Check the signer account itself first. +STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } +STORAGE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$STORAGE_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"get_account_storage",args_base64:$args,finality:"final"} }')" \ - | tee /tmp/social-publish-signer.json >/dev/null - -jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - signer_account_id: $signer_account_id, - amount: .result.amount, - locked: .result.locked, - storage_usage: .result.storage_usage -}' /tmp/social-publish-signer.json -``` - -If this query fails, you do not have a signer account to work with. If it succeeds, you know the signer exists and can at least pay gas. - -2. Ask `social.near` how much storage is already available for the account you want to write under. - -```bash -SOCIAL_STORAGE_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_account_storage", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-account-storage.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - storage: (.result.result | implode | fromjson), - storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) -}' /tmp/social-account-storage.json -``` + | jq '.result.result | implode | fromjson')" -If `available_bytes` is greater than zero, storage is not the blocker. If this method returns `null` or `available_bytes` is zero, the account needs a `storage_deposit` top-up before a new write can land. - -3. If the signer is different from the target account, check delegated write permission too. - -```bash if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - jq -n --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - signer_matches_target: true, - permission_granted: true, - reason: "owner write" - }' + PERMISSION=true else - WRITE_PERMISSION_ARGS_BASE64="$( - jq -nc \ - --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ - --arg key "$ACCOUNT_ID" '{ - predecessor_id: $predecessor_id, - key: $key - }' | base64 | tr -d '\n' - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - signer_matches_target: false, - permission_granted: (.result.result | implode | fromjson) - }' -fi -``` - -4. Turn the storage and permission checks into one readable answer. - -```bash -AVAILABLE_BYTES="$( - jq -r ' - .result.result - | if length == 0 then "0" - else (implode | fromjson | .available_bytes // 0 | tostring) - end - ' /tmp/social-account-storage.json -)" - -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - PERMISSION_GRANTED=true -else - PERMISSION_GRANTED="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq -r '.result.result | implode | fromjson' - )" + PERM_ARGS_B64="$(jq -nc --arg pred "$SIGNER_ACCOUNT_ID" --arg key "$ACCOUNT_ID" '{predecessor_id:$pred,key:$key}' | base64 | tr -d '\n')" + PERMISSION="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$PERM_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"is_write_permission_granted",args_base64:$args,finality:"final"} + }')" \ + | jq '.result.result | implode | fromjson')" fi -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ - --argjson available_bytes "$AVAILABLE_BYTES" \ - --argjson permission_granted "$PERMISSION_GRANTED" '{ +jq -n --argjson storage "$STORAGE" --argjson permission "$PERMISSION" \ + --arg account_id "$ACCOUNT_ID" --arg signer "$SIGNER_ACCOUNT_ID" '{ account_id: $account_id, - signer_account_id: $signer_account_id, - storage_ready: ($available_bytes > 0), - permission_ready: $permission_granted, - ready_to_publish_now: (($available_bytes > 0) and $permission_granted) + signer_account_id: $signer, + storage: $storage, + permission_granted: $permission, + ready_to_publish: (($storage.available_bytes // 0) > 0 and $permission) }' ``` -If that final object says `ready_to_publish_now: true`, RPC has already answered the question. If it says `false`, you know whether the blocker is storage, delegated permission, or both. - -**When to pivot** - -This keeps the whole question on exact on-chain reads. `social.near` itself answers whether the target account has room left and whether a delegated signer is already allowed to write. That is a better NEAR Social readiness check than guessing from wallet state alone. +For `mike.near` signing under itself, this returns `storage: {used_bytes: 139803, available_bytes: 83891}`, `permission_granted: true` (owner write), and `ready_to_publish: true`. If `storage` comes back `null` or `available_bytes: 0`, the account needs a `storage_deposit` on `social.near` before any new write can stick. If the signer differs from the target, the permission branch asks `is_write_permission_granted({predecessor_id, key})` — the same on-chain answer a dapp sees before writing on a user's behalf. See the [SocialDB API](https://github.com/NearSocial/social-db#api) for the full contract surface. ### What does `mob.near/widget/Profile` actually contain right now? -
-
- Flow -

Stay on exact SocialDB reads, and only widen into history if the question turns forensic.

-
-
-

01RPC call_function keys shows the widget catalog and the last-write blocks under mob.near/widget/*.

-

02RPC call_function get reads the exact source for widget/Profile.

-

03If the next question becomes “which transaction wrote this?”, hand off to the widget proof recipe in /tx/examples.

-
-
- -**Official references** - -- [SocialDB API and contract surface](https://github.com/NearSocial/social-db#api) - -**Flow** - -- Ask `social.near` for the widget catalog under `mob.near`. -- Keep the block heights so you know when each widget key last changed. -- Confirm that `Profile` is really there, then read its exact source through the same contract. -- If the next question becomes “which transaction wrote this widget?”, switch to the NEAR Social proof recipes in [Transactions Examples](/tx/examples). - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mob.near -export WIDGET_NAME=Profile -``` - -1. List the widget catalog and keep the last-write block heights. - -```bash -WIDGET_KEYS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/widget/*")], - options: {return_type: "BlockHeight"} - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "keys", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-widget-keys.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | .[$account_id].widget - | to_entries - | sort_by(.value * -1) - | map({ - widget_name: .key, - last_write_block: .value - }) - | .[0:20] -' /tmp/social-widget-keys.json -``` - -2. Confirm that `Profile` is really in the catalog, then print the exact source stored in SocialDB. - -```bash -WIDGET_GET_ARGS_BASE64="$( - jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-widget-source.json >/dev/null - -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:25] - | join("\n") - ' /tmp/social-widget-source.json -``` - -3. Pull the last-write block for the same widget so you keep one useful historical anchor. +SocialDB stores BOS widgets as `/widget/` keys on `social.near`. One `keys` call with the `BlockHeight` return type returns the catalog plus per-widget last-write anchors; one `get` call returns the exact source. ```bash -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - ' /tmp/social-widget-keys.json \ - | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mob.near +WIDGET_NAME=Profile + +KEYS_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} +}' | base64 | tr -d '\n')" + +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$KEYS_ARGS" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"keys",args_base64:$args,finality:"final"} + }')" \ + | jq --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" ' + .result.result | implode | fromjson | .[$account_id].widget as $map + | { + total_widgets: ($map | length), + most_recently_written: ($map | to_entries | sort_by(-.value) | .[0:5] | map({widget: .key, last_write_block: .value})), + target_last_write_block: $map[$widget] + }' + +GET_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget)] +}' | base64 | tr -d '\n')" + +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$GET_ARGS" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"get",args_base64:$args,finality:"final"} + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" ' + .result.result | implode | fromjson | .[$account_id].widget[$widget] | split("\n")[0:20] | join("\n")' ``` -At the time of writing, the live last-write block for `mob.near/widget/Profile` was `86494825`. Keep that block if you later want to prove which transaction wrote this version. - -**When to pivot** - -Sometimes the right RPC answer is just: here is the widget, here is the live source, and here is the block height to keep if provenance matters later. - -## Common jobs - -### Check exact account or access-key state - -**Start here** - -- [View Account](/rpc/account/view-account) for exact account fields. -- [View Access Key](/rpc/account/view-access-key) or [View Access Key List](/rpc/account/view-access-key-list) for key inspection. - -**Next page if needed** - -- [FastNear API full account view](/api/v1/account-full) if you want a readable holdings summary after checking the exact RPC state. -- [Transactions API account history](/tx/account) if the next question is "what has this account been doing?" - -**Stop when** - -- The RPC fields already answer the state or permission question. - -**Switch when** - -- The user wants balances, NFTs, staking, or another readable account summary. -- The user really wants recent activity history rather than current state. - -### Trace shard-local execution through chunks - -**Start here** - -- Start with the chunk-tracing example above when the real question is “which chunk or shard actually executed this receipt?” -- [Chunk by Block and Shard](/rpc/protocol/chunk-by-block-shard) when you already know the block and shard coordinates. -- [Chunk by Hash](/rpc/protocol/chunk-by-hash) when another tool already handed you the exact chunk hash. - -**Next page if needed** - -- [Experimental Receipt](/rpc/transaction/experimental-receipt) when you need the generated receipt body itself. -- [Block Shard](/neardata/block-shard) when chunk data alone is not enough and you need state changes or produced receipts too. -- [Transactions Examples](/tx/examples) when the question turns into a broader async or callback investigation. - -**Stop when** - -- You can name which chunk and shard carried the work that mattered. - -**Switch when** - -- The user really wants a readable transaction story instead of shard-local execution details. Move to [Transactions API](/tx). - -### Check one exact block or protocol snapshot - -**Start here** - -- [Block by ID](/rpc/block/block-by-id) or [Block by Height](/rpc/block/block-by-height) when you already know which block you care about. -- [Latest Block](/rpc/protocol/latest-block) when the question is “what is the current head right now?” -- [Status](/rpc/protocol/status), [Health](/rpc/protocol/health), or [Network Info](/rpc/protocol/network-info) when the real question is about node or network condition, not transaction history. - -**Next page if needed** - -- [Block Effects](/rpc/block/block-effects) if the block payload tells you what block you are looking at but not what changed in it. -- [Transactions API block history](/tx/block) or [Transactions API block range](/tx/blocks) if the question becomes “what actually happened around this block?” rather than “what does this block payload say?” - -**Stop when** - -- One exact block or protocol response already answers the question directly. - -**Switch when** - -- The user wants to watch fresh blocks arrive rather than inspect one exact snapshot. Move to [NEAR Data API](/neardata). -- The user needs a readable story across many transactions, not just one block payload. Move to [Transactions API](/tx). - -### What does this contract return right now? - -**Start here** - -- Start with the counter example above when the real decision is “should I use `call_function` or `view_state`?” or “can I read this storage directly instead of calling a method?” -- [Call Function](/rpc/contract/call-function) when you already know the view method you want and just need the exact return value. -- [View State](/rpc/contract/view-state) when the real question is about raw contract storage or key prefixes, not a method result. -- [View Code](/rpc/contract/view-code) when the real question is “is there code here at all?” or “which code hash is deployed?” - -**Next page if needed** - -- [FastNear API](/api) if the raw contract answer is technically correct but the user actually wanted a readable holdings or account summary. -- [KV FastData API](/fastdata/kv) if the next question becomes “what did this storage key look like over time?” instead of “what is it right now?” - -**Stop when** - -- The view call, storage read, or code hash already answers the contract question exactly. - -**Switch when** - -- The user wants indexed history or a simpler summary instead of raw contract output. -- The user stops asking “what does it return right now?” and starts asking “what changed over time?” - -### Send and confirm a transaction - -**Start here** - -- Start with the worked example above when the real question is which submission endpoint to use and how to track the transaction through completion. -- [Send Transaction](/rpc/transaction/send-tx) when you want RPC submission with explicit waiting semantics. -- [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) or [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit) when those exact submission modes are the point. -- [Transaction Status](/rpc/transaction/tx-status) to confirm the final result. - -**Next page if needed** - -- [Transactions by Hash](/tx/transactions) for a readable history record after submission. -- [Receipt Lookup](/tx/receipt) when you need to investigate downstream execution or callback flow. -- [Transactions Examples](/tx/examples) when the next question is “one batched action failed, did the earlier actions roll back too?” - -**Stop when** - -- You have the submission result and final status you needed. - -**Switch when** - -- The next question is about receipts, affected accounts, or execution history in a human-friendly order. -- You need a fuller investigation workflow instead of one status check. +For `mob.near`, the catalog shows 264 widgets; `Profile` last wrote at block `86494825` — years ago, stable since — and the source begins with `const accountId = props.accountId ?? context.accountId;`. The `BlockHeight` return type costs nothing extra and turns the key listing into a cheap staleness check. Keep the last-write block if you later want to prove *which transaction* wrote this version — hand it to [Advanced SocialDB write lookup](/tx/socialdb-proofs). ## Common mistakes diff --git a/docs/snapshots/examples.mdx b/docs/snapshots/examples.mdx index 9811c9a..ec32531 100644 --- a/docs/snapshots/examples.mdx +++ b/docs/snapshots/examples.mdx @@ -8,36 +8,13 @@ page_actions: - markdown --- -## Quick start +## Mainnet recovery paths -Need a mainnet RPC node back fast? Start with one runnable command. +Pick one class — optimized `fast-rpc`, standard RPC, or archival — and run only that path's commands. Mixing classes produces inconsistent node data. -These helpers are maintained by FastNear and are meant to optimize for recovery speed. If your environment requires change review, fetch the script first, inspect it, and only then execute it instead of piping it straight to `bash`. +FastNear maintains these helpers for recovery speed. If your environment requires change review, fetch the script and inspect it before running it (instead of piping straight to `bash`). -```bash -DATA_PATH=~/.near/data - -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ - | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash -``` - -## Example - -### Choose and execute the right mainnet recovery path - -
-
- Flow -

Pick the recovery class first, then run the smallest command sequence that matches that class.

-
-
-

01Decide whether the real need is optimized fast-rpc, standard RPC, or archival recovery.

-

02If the answer is archival, capture one exact snapshot block height first and keep reusing it.

-

03Run only the commands for that chosen path, instead of mixing optimized, standard, and archival steps together.

-
-
- -### Optimized mainnet `fast-rpc` command anchor +### Optimized mainnet `fast-rpc` ```bash DATA_PATH=~/.near/data @@ -46,7 +23,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash ``` -### Standard mainnet RPC command anchor +### Standard mainnet RPC ```bash DATA_PATH=~/.near/data @@ -55,15 +32,9 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet bash ``` -### Mainnet archival recovery shell walkthrough - -For archival recovery, capture one snapshot height and reuse it for both hot and cold data. - -**Flow** +### Mainnet archival -- Fetch the latest archival mainnet snapshot height once. -- Store it in `LATEST`. -- Reuse that exact block height for both the hot-data and cold-data downloads. +Archival needs two downloads that must come from the *same* snapshot cut. Capture one `LATEST` value and reuse it for both hot and cold data — mixing heights yields an internally inconsistent dataset and surprises nearcore at config time. ```bash HOT_DATA_PATH=~/.near/data @@ -79,11 +50,6 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_TYPE=cold-data DATA_PATH="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash ``` -**When to pivot** - -Hot and cold archival data need to come from the same snapshot cut. Reusing one captured `LATEST` value across both commands keeps the archival dataset internally consistent and makes later nearcore configuration much less surprising. - - ## Common mistakes - Using snapshot docs when the task is really about reading chain data. diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index d720f4b..2e86f32 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -12,147 +12,44 @@ page_actions: ### Filter and page a transfer feed for one account -
-
- Flow -

Build the account feed first, then lift one receipt only if one row needs more story.

-
-
-

01POST /v0/transfers gives you the first page of one filtered account feed.

-

02jq lifts the returned rows plus resume_token so you can keep paging the same feed.

-

03POST /v0/receipt is only the optional follow-up when one row needs the execution story behind it.

-
-
- -**Network** - -- mainnet only today - -**Flow** - -- Fetch the first page of one filtered transfer feed for a single account. -- Use the feed parameters themselves as the core levers: `account_id`, `direction`, `asset_id`, `min_amount`, `desc`, and `limit`. -- Inspect the returned rows plus `resume_token` before you decide whether any row deserves deeper execution history. -- Only if one row does deserve that deeper story, reuse its `receipt_id` in Transactions API. +`/v0/transfers` returns a filtered feed plus a `resume_token` you replay with *unchanged* filters to keep paging. Each row already carries `human_amount`, `usd_amount`, `transaction_id`, and `receipt_id`, so most audit questions land without a second call. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -ASSET_ID=native:near -MIN_AMOUNT=1000000000000000000000000 +ACCOUNT_ID=root.near -curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ +FEED="$(curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - min_amount: $min_amount, - desc: true, - limit: 10 - }')" \ - | tee /tmp/transfers-feed.json >/dev/null - -jq '{ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + direction: "receiver", + asset_id: "native:near", + min_amount: "1000000000000000000000000", + desc: true, + limit: 10 + }')")" + +echo "$FEED" | jq '{ resume_token, - transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - asset_id, - amount, - human_amount, - usd_amount, - other_account_id, - block_height - } - ] -}' /tmp/transfers-feed.json + transfers: [.transfers[] | {block_height, amount, human_amount, usd_amount, other_account_id, transaction_id, receipt_id}] +}' ``` -Optional: if one feed row still needs its execution anchor, lift that row’s `receipt_id` and pivot once into Transactions API. +For the pinned account this returns recent incoming native-NEAR transfers of at least 1 NEAR — sample rows are native transfers from `escrow.ai.near` with USD already computed. To page, resend the same body with a top-level `resume_token: ""`; changing any other filter invalidates the token. + +When one row needs its execution anchor, take its `receipt_id` straight to `/v0/receipt`: ```bash -RECEIPT_ID="$(jq -r '.transfers[0].receipt_id' /tmp/transfers-feed.json)" +RECEIPT_ID="$(echo "$FEED" | jq -r '.transfers[0].receipt_id')" curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' + | jq '.receipt | {receipt_id, transaction_hash, receiver_id, predecessor_id, tx_block_height, is_success}' ``` -**When to pivot** - -The transfer query answers the first question directly: what does this account’s filtered feed look like right now, and how do you keep paging it without losing your place? Only after the feed tells you which row matters should you switch to `receipt_id` and chase execution history in `/tx`. - -## Common jobs - -### Filter one account’s transfer feed - -**Start here** - -- [Query Transfers](/transfers/query) with the account plus the narrowest stable feed filters: direction, asset, amount, and order. - -**Next page if needed** - -- Keep refining the same feed with asset or amount filters if the first page still contains unrelated rows. - -**Stop when** - -- You can explain what the filtered feed contains and how to keep paging it. - -**Switch when** - -- One specific row now needs its execution story or receipt trail. Move to [Transactions API](/tx). - -### Keep paging through a transfer feed without losing your place - -**Start here** - -- [Query Transfers](/transfers/query) for the first page of recent events, using the tightest stable filters you can. - -**Next page if needed** - -- Reuse the exact returned `resume_token` to fetch the next page with the same filters. -- Keep the filters unchanged while you paginate, or you are no longer looking at the same feed. - -**Stop when** - -- You have enough pages to answer the requested feed, support review, or compliance check. - -**Switch when** - -- The user asks for transaction metadata beyond transfer events. -- The feed needs balances or holdings, not just movement. Move to [FastNear API](/api). - -### Escalate from transfer-only history to full transaction investigation - -**Start here** - -- [Query Transfers](/transfers/query) to identify the specific transfer events that matter. - -**Next page if needed** - -- [Transactions API account history](/tx/account) if the user wants the surrounding execution story for the same account. -- [Transactions by Hash](/tx/transactions) when you already know which transaction to inspect next. - -**Stop when** - -- You have identified the right transfer event and the right next API to open. - -**Switch when** - -- The user explicitly needs receipt-level detail or exact RPC confirmation. Move to [Transactions API](/tx) first, then [RPC Reference](/rpc) if needed. +That's the same handoff covered in [Turn one ugly receipt ID from logs into a human story](/tx/examples#turn-one-ugly-receipt-id-from-logs-into-a-human-story) — one call gets the receipt and its full parent transaction. ## Common mistakes diff --git a/docs/tx/examples.md b/docs/tx/examples.md index 65ae13e..d634c90 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -8,918 +8,211 @@ page_actions: - markdown --- -## Quick start - -Start with one tx hash and ask for the smallest readable answer first. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, - receipt_count: (.transactions[0].receipts | length) - }' -``` - ## Start Here ### I have one transaction hash. What happened? -
-
- Flow -

Start with the readable tx record, then drop into RPC or receipts only if the first answer is not enough.

-
-
-

01POST /v0/transactions gives signer, receiver, action types, block height, and the first receipt handoff.

-

02RPC EXPERIMENTAL_tx_status is only for the exact protocol-side success semantics.

-

03POST /v0/receipt only matters if the first receipt becomes the new anchor.

-
-
- -Pinned example: - -- transaction hash: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- included block height: `194263342` -- first receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` - -Short answer: `mike.near` submitted a single `Transfer` action to `global-counter.mike.near`, the transaction landed in block `194263342`, and the chain handed it off into one successful receipt. - -```mermaid -flowchart LR - H["One tx hash
AdgNifPY..."] --> T["Fetch transaction"] - T --> A["Read signer, receiver, actions, block"] - A --> S["Short human story"] - T -. "if needed later" .-> R["First receipt ID
5GhZcpfK..."] -``` - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Readable transaction story | Transactions API [`POST /v0/transactions`](/tx/transactions) | Start from the tx hash and print signer, receiver, included block, action list, and first receipt handoff | Gives the fastest readable answer to “what did this tx do?” | -| Canonical status follow-up | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Reuse the same tx hash and signer only if you need exact protocol-native status semantics | Useful when the next question becomes “success according to RPC, exactly?” | -| Receipt handoff | Transactions API [`POST /v0/receipt`](/tx/receipt) | Reuse the first receipt ID if the next question turns into a receipt-level story | Provides the natural bridge to the next investigation when the transaction hash is no longer the best anchor | - -#### Transaction hash to human story shell walkthrough - -**Flow** - -- Fetch the transaction by hash and print the main story fields. -- Confirm the final status only if you need exact RPC semantics. -- Keep the first receipt ID only as the optional next step. +Paste the hash into `POST /v0/transactions` and one response usually holds the whole story. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp -SIGNER_ACCOUNT_ID=mike.near -``` - -1. Fetch the transaction and print the basic story. -```bash -FIRST_RECEIPT_ID="$( - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/basic-tx-story.json \ - | jq -r '.transactions[0].transaction_outcome.outcome.status.SuccessReceiptId' -)" - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/basic-tx-story.json - -# Expected action list: ["Transfer"] -# Expected first receipt ID: 5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq -``` - -2. If you need exact RPC status semantics, confirm them with `EXPERIMENTAL_tx_status`. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' -``` - -3. If the next question becomes “what was that first receipt?”, pivot once and stop. - -```bash -curl -s "$TX_BASE_URL/v0/receipt" \ +curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$FIRST_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height, + actions: (.transactions[0].transaction.actions | map(if type == "string" then . else keys[0] end)), + first_receipt_id: .transactions[0].execution_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) }' ``` -That last step is optional on purpose. If all you wanted was the transaction story, the first step was enough. Keep going only when the receipt itself becomes the new anchor. - -**When to pivot** - -`POST /v0/transactions` is the cleanest starting point when all you have is a tx hash and need one readable answer. RPC is the follow-up for exact status semantics. `POST /v0/receipt` is the handoff when the next question stops being about the transaction as a whole and starts being about one receipt inside it. +For the pinned hash, `mike.near` sent a single `Transfer` to `global-counter.mike.near` in block `194263342`, handing off into receipt `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq`. When `receipt_count > 1` or the next question is about receipt-level behavior, jump to [Which receipt emitted this log or event?](#which-receipt-emitted-this-log-or-event) or [`POST /v0/receipt`](/tx/receipt). ### Which receipt emitted this log or event? -
-
- Flow -

Fetch the receipt list once, filter by the log fragment, and stop as soon as one receipt owns that log.

-
-
-

01POST /v0/transactions gives the full indexed receipt list for one tx hash, including receipt logs.

-

02jq filters that list down to receipts whose logs contain the fragment you care about.

-

03Once one receipt matches, keep its receipt_id, executor, and method name as the exact answer.

-
-
- -For this pinned mainnet example, use: - -- transaction hash: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` -- log fragment: `Refund` -- expected matching receipt ID: `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` -- expected executor: `wrap.near` -- expected method: `ft_resolve_transfer` - -This transaction is useful because it has two different logged receipts in the same story: - -- one `Transfer ...` log on the earlier `ft_transfer_call` receipt -- one `Refund ...` log on the later `ft_resolve_transfer` receipt - -#### Log-attribution shell walkthrough - -**Flow** - -- Fetch the transaction once and keep the receipt list locally. -- Filter the receipts by one log fragment. -- Stop as soon as you have one exact `receipt_id`, one executor, and one method name. +List every logged receipt in the transaction with a flag for whether its logs contain your fragment. The match is provable rather than guessed: this pinned tx logs a `Transfer` on one receipt and a `Refund` on another, and only the `Refund` side flips to `true`. ```bash TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL LOG_FRAGMENT=Refund -``` - -1. Fetch the transaction and keep the receipt list. -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/log-attribution-transaction.json >/dev/null -``` - -2. Filter the receipt list down to logs that contain the fragment you care about. - -```bash -jq --arg fragment "$LOG_FRAGMENT" '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id - }, - matching_receipts: [ - .transactions[0].receipts[] - | select(any(.execution_outcome.outcome.logs[]?; contains($fragment))) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - block_height: .execution_outcome.block_height, - logs: .execution_outcome.outcome.logs - } - ] -}' /tmp/log-attribution-transaction.json - -# What to notice: -# - the `Refund` fragment matches exactly one receipt -# - that receipt is 9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w -# - the receipt executed on wrap.near -# - the method name is ft_resolve_transfer -``` - -3. If you want to see the logged receipts side by side, print only the receipts that logged anything. - -```bash -jq '{ - logged_receipts: [ - .transactions[0].receipts[] - | select((.execution_outcome.outcome.logs | length) > 0) - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - logs: .execution_outcome.outcome.logs - } - ] -}' /tmp/log-attribution-transaction.json -``` - -That final comparison is useful because it proves the log attribution is not guesswork. This transaction has more than one logged receipt, and the `Refund` fragment belongs to one exact later receipt, not to the transaction as a whole. - -**When to pivot** - -Receipt logs live on receipts, not on some abstract top-level transaction object. `POST /v0/transactions` is enough to attribute one log line to one exact receipt without dropping into a deeper async trace. + | jq --arg fragment "$LOG_FRAGMENT" ' + [ + .transactions[0].receipts[] + | select((.execution_outcome.outcome.logs | length) > 0) + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0] + | if type == "string" then . else (.FunctionCall.method_name // keys[0]) end), + matches_fragment: any(.execution_outcome.outcome.logs[]?; contains($fragment)), + logs: .execution_outcome.outcome.logs + } + ]' +``` + +The `Refund` fragment attributes to receipt `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` on `wrap.near`, method `ft_resolve_transfer`. Receipt logs live on receipts, not on the transaction, so this single pass is enough — no deeper async trace needed. ### Turn one ugly receipt ID from logs into a human story -If you already have the transaction hash instead of the receipt ID, start with the simpler investigation just above. - -
-
- Flow -

Resolve the receipt first, then recover the parent transaction and stop once the story is readable.

-
-
-

01POST /v0/receipt tells you which transaction and execution block the receipt belongs to.

-

02POST /v0/transactions turns that raw receipt into signer, receiver, and action context.

-

03RPC tx status is optional follow-up only when “human story” turns into “exact protocol semantics.”

-
-
- -Pinned receipt from logs: - -- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` -- originating transaction hash: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- transaction block height: `194263342` -- receipt execution block height: `194263343` -Short answer: `mike.near` signed a plain `Transfer` transaction to `global-counter.mike.near`, the network turned it into one action receipt, and that receipt executed successfully in the next block. - -#### Ugly receipt ID to human story shell walkthrough +`POST /v0/receipt` returns the receipt record **and** its full parent transaction in one response, so a single call covers the whole story — no follow-up `/v0/transactions` fetch needed. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' -``` - -1. Resolve the receipt and figure out what object you are looking at. - -```bash -TX_HASH="$( - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | tee /tmp/receipt-lookup.json \ - | jq -r '.receipt.transaction_hash' -)" - -jq '{ - receipt: { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - receipt_type: .receipt.receipt_type, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash, - tx_block_height: .receipt.tx_block_height - } -}' /tmp/receipt-lookup.json -``` - -2. Reuse the transaction hash and turn the receipt into a readable transaction story. +RECEIPT_ID=5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/receipt-parent-transaction.json >/dev/null - -jq '{ - transaction: { - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transfer_deposit_yocto: ( - .transactions[0].transaction.actions[0].Transfer.deposit // null - ) - }, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/receipt-parent-transaction.json -``` - -3. Turn that into one human sentence. - -```bash -jq -r ' - def zeros($n): - reduce range(0; $n) as $i (""; . + "0"); - def yocto_to_near($yocto): - ($yocto | tostring) as $digits - | if ($digits | length) <= 24 then - ("0." + zeros(24 - ($digits | length)) + $digits) - else - ($digits[0:(($digits | length) - 24)] + "." + $digits[-24:]) - end - | sub("0+$"; "") - | sub("\\.$"; ""); - .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) belongs to tx \($tx.transaction.hash): \($tx.transaction.signer_id) sent \(yocto_to_near($tx.transaction.actions[0].Transfer.deposit)) NEAR to \($tx.transaction.receiver_id). The tx landed in block \($tx.execution_outcome.block_height), and the receipt executed successfully in block \($tx.receipts[0].execution_outcome.block_height)." -' /tmp/receipt-parent-transaction.json + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block: .receipt.block_height, + tx_block: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }, + parent_transaction: { + signer_id: .transaction.transaction.signer_id, + receiver_id: .transaction.transaction.receiver_id, + action_types: (.transaction.transaction.actions | map(if type == "string" then . else keys[0] end)) + } + }' ``` -For another receipt, keep the same pattern but change the final sentence to match the action types you just printed. - -That is the core trick: you do not need to explain every receipt field. You need to recover just enough context to say what the signer did, where the receipt executed, and whether this receipt was the main event or only one step in a bigger cascade. - -**When to pivot** - -`POST /v0/receipt` tells you what the raw receipt is attached to. `POST /v0/transactions` tells you what the signer was actually trying to do. Once you have those two pieces together, you can usually explain the receipt in one sentence before deciding whether you really need block context, account history, or canonical RPC status. +For the pinned receipt, this returns an `Action` receipt from `mike.near` to `global-counter.mike.near` that executed successfully in block `194263343`, one block after its parent tx `AdgNifPY…` landed — a single `Transfer` (5 NEAR, visible as `5000000000000000000000000` yocto in the raw `.transaction.transaction.actions`). If the parent tx becomes the interesting anchor, you already have the hash — reuse it with [I have one transaction hash. What happened?](#i-have-one-transaction-hash-what-happened). ## Failure and Async ### Prove that one failed action reverted the whole batch -One batch created an account, funded it, added a key, and then called a missing method. The question is whether the earlier actions stuck or the whole batch reverted. - -
-
- Flow -

Prove what the batch tried, which action failed, and whether anything from the earlier actions actually stuck.

-
-
-

01POST /v0/transactions shows the ordered batch exactly as the signer submitted it.

-

02RPC EXPERIMENTAL_tx_status shows the failing FunctionCall and the protocol-side failure reason.

-

03RPC view_account on the intended new account proves whether the earlier create, fund, and key-add actions stuck at all.

-
-
- -**Official references** - -- [Transaction foundations](/transaction-flow/foundations) -- [Runtime execution](/transaction-flow/runtime-execution) - -Pinned testnet failure observed on **April 18, 2026**: - -- transaction hash: `CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M` -- signer account: `temp.mike.testnet` -- intended new account: `rollback-mo4vmkig.temp.mike.testnet` -- included block height: `246365118` -- included block hash: `6f5zTKDqQRwrxMywzvxeRvYcCERJmAnatJaqUEtQYUNM` -- ordered actions: `CreateAccount -> Transfer -> AddKey -> FunctionCall` -- failing method: `definitely_missing_method` -- RPC failure: `CodeDoesNotExist` on `rollback-mo4vmkig.temp.mike.testnet` - -```mermaid -flowchart LR - T["One signed transaction"] --> A["CreateAccount"] - A --> B["Transfer 0.01 NEAR"] - B --> C["AddKey"] - C --> D["FunctionCall definitely_missing_method()"] - D --> E["Failure: CodeDoesNotExist"] - E --> R["Whole batch reverts"] - R --> N["No new account"] - R --> K["No new key"] - R --> F["No funded receiver state"] -``` - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Intended batch | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the pinned transaction hash and print the ordered action list, receiver, and included block metadata | Shows exactly what the signer tried to do before you reason about what stuck | -| Exact failure | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Query the same transaction with `wait_until: "FINAL"` and inspect `status.Failure` | Tells you which action failed and why the whole batch reverted at the protocol level | -| Post-state proof | RPC [`query(view_account)`](/rpc/account/view-account) | Query the intended new account after finality | If the created account still does not exist, then the earlier `CreateAccount`, `Transfer`, and `AddKey` from that same batch did not stick either | - -The indexed transaction record still shows `transaction_outcome.outcome.status = SuccessReceiptId`, because the signed transaction did become its first action receipt. The proof that the batch reverted comes from the RPC top-level `status.Failure` on that first receipt plus the post-state check that the intended new account never existed. -#### Failed batched transaction shell walkthrough - -**Flow** - -- Read the indexed transaction record to recover the intended action batch. -- Use RPC transaction status to prove the final `FunctionCall` failed and reverted the batch. -- Use one post-state RPC read to prove the new account never existed after finality. +One batch submitted `CreateAccount → Transfer → AddKey → FunctionCall` and the final call hit a missing method. The indexed tx record already carries the ordered batch *and* the exact receipt-level failure, so one call answers "what was tried and what broke"; a `view_account` check then proves the earlier actions rolled back. ```bash TX_BASE_URL=https://tx.test.fastnear.com RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M -SIGNER_ACCOUNT_ID=temp.mike.testnet NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet -``` -1. Fetch the transaction and print the intended action batch. - -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/failed-batch-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height, - included_block_hash: .transactions[0].execution_outcome.block_hash - }, - batch: { - action_count: (.transactions[0].transaction.actions | length), - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - final_function_call_method_name: ( - .transactions[0].transaction.actions[3].FunctionCall.method_name - ) - }, - first_receipt_handoff: .transactions[0].transaction_outcome.outcome.status -}' /tmp/failed-batch-transaction.json - -# Expected action order: -# 1. CreateAccount -# 2. Transfer -# 3. AddKey -# 4. FunctionCall + | jq '{ + action_types: (.transactions[0].transaction.actions | map(if type == "string" then . else keys[0] end)), + final_method: .transactions[0].transaction.actions[3].FunctionCall.method_name, + tx_handoff: .transactions[0].execution_outcome.outcome.status, + receipt_failure: ( + first( + .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) + | .execution_outcome.outcome.status.Failure.ActionError + ) + ) + }' ``` -2. Query RPC transaction status and inspect the exact top-level failure. +The tx-level status is `SuccessReceiptId` — the transaction successfully handed its batched actions off to a receipt. The failure lives one layer down on that receipt: `index: 3` (the `FunctionCall`), kind `CodeDoesNotExist` on `rollback-mo4vmkig.temp.mike.testnet`. `SuccessReceiptId` on the tx outcome means "handoff worked," not "everything finished" — a real trap if you only look at the tx-level status. -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/failed-batch-rpc-status.json >/dev/null - -jq '{ - final_execution_status: .result.final_execution_status, - failed_action_index: .result.status.Failure.ActionError.index, - failure: .result.status.Failure.ActionError.kind.FunctionCallError.CompilationError.CodeDoesNotExist -}' /tmp/failed-batch-rpc-status.json - -# Expected failed_action_index: 3 -# Expected failure account_id: rollback-mo4vmkig.temp.mike.testnet -``` - -3. Query the intended new account after finality and prove it still does not exist. +Now prove the earlier actions rolled back by asking for the account the batch *tried* to create: ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } + jsonrpc: "2.0", id: "fastnear", method: "query", + params: {request_type: "view_account", account_id: $account_id, finality: "final"} }')" \ - | tee /tmp/failed-batch-view-account.json >/dev/null - -jq '{ - error: .error.cause.name, - message: .error.data, - requested_account_id: .error.cause.info.requested_account_id, - proof_block_height: .error.cause.info.block_height -}' /tmp/failed-batch-view-account.json - -# Expected error: "UNKNOWN_ACCOUNT" + | jq '{error: .error.cause.name, requested_account_id: .error.cause.info.requested_account_id}' ``` -That one post-state check is enough here. If `CreateAccount` had stuck, `view_account` would resolve. Because the account still does not exist, the earlier `Transfer` and `AddKey` from the same batched receipt did not stick either. - -**When to pivot** - -For another failed batch, keep the same pattern: read what the transaction tried to do from [`POST /v0/transactions`](/tx/transactions), confirm the exact top-level failure with RPC transaction status, then inspect post-state on the account, key, contract, or other object that would have changed if the earlier actions had stuck. +`UNKNOWN_ACCOUNT` is the proof. If `CreateAccount` had stuck, `view_account` would resolve; because it does not, the earlier `Transfer` and `AddKey` from the same batched receipt did not stick either. ### Why did this contract call look successful, but a later receipt failed? -The first contract receipt succeeded. The failure happened later, in a separate detached receipt. - -
-
- Flow -

First get the human timeline, then prove where the async story split.

-
-
-

01POST /v0/transactions gives the easiest first pass: which receipt ran first, and which receipt failed later.

-

02RPC EXPERIMENTAL_tx_status proves the important NEAR nuance that top-level success and later descendant failure can both be true.

-

03Once those two views agree on the split, stop. This example stays on preserved historical evidence rather than a live router-state read.

-
-
- -**Official references** - -- [Transaction foundations](/transaction-flow/foundations) -- [Runtime execution](/transaction-flow/runtime-execution) - -Pinned async testnet failure observed on **April 18, 2026**: - -- transaction hash: `AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz` -- signer account: `temp.mike.testnet` -- first contract receiver: `seq-dr.mike.testnet` -- detached target account: `asyncfail-in2hwikn.temp.mike.testnet` -- transaction inclusion block: `246368568` -- successful first receipt: `6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS` at block `246368569` -- later failed receipt: `2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es` at block `246368570` -- first method: `kickoff_append` -- later failed method: `append` -- top-level RPC `status`: `SuccessValue` - -```mermaid -flowchart LR - T["Signed tx
kickoff_append(...)"] --> R["First receipt on seq-dr.mike.testnet
SuccessValue + kickoff log"] - R --> D["Detached cross-contract receipt
append(...)"] - D --> F["Later failure
CodeDoesNotExist"] - T -. "outer transaction still resolves" .-> X["RPC top-level status
SuccessValue"] -``` -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Transaction skeleton | Transactions API [`POST /v0/transactions`](/tx/transactions) | Fetch the pinned transaction and print the included block plus the per-receipt timeline | Gives the shortest readable overview of which receipt ran first and which receipt failed later | -| Exact status semantics | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Inspect the top-level `status`, the first contract receipt outcome, and the later failed receipt outcome | Proves that top-level success and later descendant failure can coexist in one async story | - -Receipt success is not transitive. `seq-dr.mike.testnet` returned success on its own receipt because `kickoff_append(...)` only logged and detached the next hop. The detached `append(...)` receipt was separate async work, so its later failure did not change the fact that the router's own receipt had already completed successfully. - -#### Later receipt failure shell walkthrough - -**Flow** - -- Read the transaction and its receipt timeline from the indexed view. -- Use RPC transaction status to show that the top-level story still ended in `SuccessValue` even though a later receipt failed. -- Stop once those two preserved views agree on the split. +A single tx can end with the outer handoff reporting `SuccessReceiptId` while a descendant receipt quietly fails — that's NEAR's async model, and `/v0/transactions` surfaces the whole timeline in one call. ```bash -TX_BASE_URL=https://tx.test.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com -TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz -SIGNER_ACCOUNT_ID=temp.mike.testnet -FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS -FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es -``` - -1. Fetch the transaction and print the receipt timeline in block order. +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/later-receipt-failure-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - tx_handoff: .transactions[0].transaction_outcome.outcome.status - }, - receipts: [ - .transactions[0].receipts[] - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - block_height: .execution_outcome.block_height, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system_transfer"), - status: .execution_outcome.outcome.status - } - ] -}' /tmp/later-receipt-failure-transaction.json - -# What to notice: -# - the first contract receipt on seq-dr.mike.testnet succeeded in block 246368569 -# - the later append(...) receipt failed in block 246368570 -``` - -2. Query RPC transaction status and compare the top-level story with the later failed receipt. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/later-receipt-failure-rpc.json >/dev/null - -jq \ - --arg first_receipt_id "$FIRST_RECEIPT_ID" \ - --arg failed_receipt_id "$FAILED_RECEIPT_ID" '{ - top_level_status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status, - first_contract_receipt: ( - .result.receipts_outcome[] - | select(.id == $first_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - logs: .outcome.logs, - status: .outcome.status - } - ), - later_failed_receipt: ( - .result.receipts_outcome[] - | select(.id == $failed_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - status: .outcome.status - } - ) - }' /tmp/later-receipt-failure-rpc.json - -# What to notice: -# - top_level_status is still SuccessValue -# - the first contract receipt logged dishonest_router:kickoff:late-failure -# - the later append(...) receipt failed with CodeDoesNotExist + | jq '{ + tx_handoff: .transactions[0].execution_outcome.outcome.status, + outer_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + descendant_failures: [ + .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) + | { + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + block_height: .execution_outcome.block_height, + failure: .execution_outcome.outcome.status.Failure + } + ], + receipt_timeline: [ + .transactions[0].receipts[] + | { + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + status_class: (.execution_outcome.outcome.status | keys[0]) + } + ] + }' ``` -Stop here. As of **April 18, 2026**, `seq-dr.mike.testnet` no longer resolves on testnet, so a live router-state proof would no longer be truthful. The indexed receipt timeline plus `EXPERIMENTAL_tx_status` are the preserved historical evidence that still matters. - -**When to pivot** +For the pinned mainnet tx, `tx_handoff` is `SuccessReceiptId` — the tx kicked off its first receipt cleanly. Read that alone and you'd call it a win. `descendant_failures` tells a second story: `ft_on_transfer` on `v2.ref-finance.near` panicked with `E51: contract paused` — the DEX had been paused when this swap ran, so it couldn't accept the wrapped NEAR. The `receipt_timeline` then shows how the story resolved: wrap.near's callback `ft_resolve_transfer` ran anyway and emitted a `Refund` log returning the wrapped NEAR to the sender. -When a NEAR app “looked successful” and still broke later, the thing to ask is not just “what was the transaction status?” but “which receipt succeeded, and which later receipt failed?” This example gives you that exact split: indexed receipt timeline for the shape, RPC status for the exact semantics, and no pretend live router-state read after the historical contract disappeared. +Receipt success is not transitive. A protocol can hand off cleanly and still see the detached work fail later. If your app "looked successful" but money came back anyway, walk this same timeline — the split is visible on the indexed response without a separate RPC status call. To check specifically that your callback ran, see [Did my callback run at all?](#did-my-callback-run-at-all). ### Did my callback run at all? -Start from the indexed receipt chain. Use RPC only if you need canonical callback semantics. - -
-
- Flow -

Use the indexed receipt list first, then drop to RPC only if you need canonical callback semantics.

-
-
-

01POST /v0/transactions shows the downstream call and the later receipt that returns to the origin contract.

-

02jq narrows that receipt list to one downstream call and one callback receipt.

-

03RPC EXPERIMENTAL_tx_status is optional confirmation when you need the callback receipt's canonical outcome and logs.

-
-
- -Pinned mainnet callback example observed on **April 19, 2026**: - -- transaction hash: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` -- sender account: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` -- origin contract: `wrap.near` -- downstream receiver: `v2.ref-finance.near` -- top-level method: `ft_transfer_call` -- downstream method: `ft_on_transfer` -- callback method: `ft_resolve_transfer` -- transaction block: `194692298` -- downstream receipt block: `194692300` -- callback receipt block: `194692301` - -```mermaid -flowchart LR - T["One mainnet tx
ft_transfer_call on wrap.near"] --> D["Downstream receipt
v2.ref-finance.near.ft_on_transfer"] - D --> F["Receiver failed
E51: contract paused"] - F --> C["Callback receipt back on wrap.near
ft_resolve_transfer"] - C --> R["Refund log on wrap.near"] -``` - -A downstream failure does not mean the callback vanished. In this case, `v2.ref-finance.near` failed its `ft_on_transfer` receipt, but `wrap.near` still later received `ft_resolve_transfer` and logged the refund. - -| Surface | Endpoint | How we use it | Why we use it | -| --- | --- | --- | --- | -| Indexed receipt chain | Transactions API [`POST /v0/transactions`](/tx/transactions) | Start from the tx hash and print only the downstream receiver receipt plus the later callback receipt on the origin contract | Gives the fastest readable answer to “did the callback come back?” | -| Canonical receipt confirmation | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Reuse the same tx hash and sender only if you need the callback receipt's canonical status and logs | Useful when the indexed answer is enough for shape but you still want protocol-native proof | - -#### Callback-existence shell walkthrough -**Flow** - -- Fetch the transaction once and narrow the receipt list down to the downstream call plus the callback receipt. -- Reuse the callback receipt ID only if you still need canonical RPC confirmation. -- Stop once you can say whether the callback came back and what it did. +NEAR cross-contract calls return through a callback receipt on the origin contract. Whether that callback actually ran is a one-line `any(...)` check against the indexed receipt list — and the full refund story falls out of the same response. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL -SENDER_ACCOUNT_ID=7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72 ORIGIN_CONTRACT_ID=wrap.near -DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near -``` - -1. Fetch the transaction and print the downstream receipt plus the callback receipt. +CALLBACK_METHOD=ft_resolve_transfer -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/callback-check-transaction.json >/dev/null -``` - -2. Answer the smallest useful question first: did the callback come back at all? - -```bash -jq --arg origin "$ORIGIN_CONTRACT_ID" ' - [ - .transactions[0].receipts[] - | select( + | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ + top_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + callback_ran: any( + .transactions[0].receipts[]; .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - ] | length > 0 -' /tmp/callback-check-transaction.json -``` - -3. If the answer is `true`, print the downstream receipt plus the callback receipt. - -```bash - -CALLBACK_RECEIPT_ID="$( - jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | .receipt.receipt_id - ) - ' /tmp/callback-check-transaction.json -)" - -jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - tx_block_height: .transactions[0].execution_outcome.block_height - }, - downstream_receipt: ( - first( - .transactions[0].receipts[] - | select(.receipt.receiver_id == $downstream) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - status: .execution_outcome.outcome.status, - block_height: .execution_outcome.block_height - } - ) - ), - callback_receipt: ( - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: .receipt.receipt.Action.actions[0].FunctionCall.method_name, - logs: .execution_outcome.outcome.logs, - status: .execution_outcome.outcome.status, - block_height: .execution_outcome.block_height - } - ) - ), - callback_ran: ( - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | true - ) // false - ) -}' /tmp/callback-check-transaction.json - -# What to notice: -# - the downstream receipt ran ft_on_transfer on v2.ref-finance.near -# - the later callback receipt ran ft_resolve_transfer on wrap.near -# - callback_ran is true even though the downstream receipt failed -``` - -4. If you want the canonical callback outcome and refund log, confirm the same receipt in RPC. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg sender_account_id "$SENDER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $sender_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/callback-check-rpc.json >/dev/null - -jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ - top_level_status: .result.status, - callback_receipt: ( - first( - .result.receipts_outcome[] - | select(.id == $callback_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - logs: .outcome.logs, - status: .outcome.status - } - ) - ) -}' /tmp/callback-check-rpc.json - -# What to notice: -# - the downstream ft_on_transfer receipt failed on v2.ref-finance.near -# - wrap.near still received ft_resolve_transfer afterward -# - the callback log shows the refund back to the sender + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback + ), + receipt_chain: [ + .transactions[0].receipts[] + | { + receiver_id: .receipt.receiver_id, + method: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + block: .execution_outcome.block_height, + status: (.execution_outcome.outcome.status | keys[0]), + logs: .execution_outcome.outcome.logs + } + ] + }' ``` -**When to pivot** - -For callback questions, the important proof is not “did every receipt succeed?” but “did the origin contract get its callback receipt back, and what happened there?” `POST /v0/transactions` gives the fastest readable answer. RPC is only the optional confirmation layer when you need the callback receipt's canonical outcome and logs. +For the pinned tx, `ft_transfer_call` on `wrap.near` hands off to `v2.ref-finance.near`'s `ft_on_transfer`, which **fails**. The callback `ft_resolve_transfer` still runs on `wrap.near` and logs `Refund 7278020378457059679767103 from v2.ref-finance.near to …` back to the sender — so `callback_ran: true` even though the downstream receipt failed. A downstream failure never prevents the origin contract from seeing its callback; that's how NEAR async error handling stays recoverable. The `method: "system"` rows are runtime gas refunds, not contract logic. To attribute one of the logs above to its emitting receipt, see [Which receipt emitted this log or event?](#which-receipt-emitted-this-log-or-event). ## Common mistakes @@ -934,7 +227,6 @@ For callback questions, the important proof is not “did every receipt succeed? - [FastNear API](/api) - [NEAR Data API](/neardata) - [Berry Club: live board and one historical reconstruction path](/tx/examples/berry-club) -- [OutLayer: pair one request tx with one worker resolution](/tx/examples/outlayer) - [Advanced SocialDB write lookup](/tx/socialdb-proofs) - [Choosing the Right Surface](/agents/choosing-surfaces) - [Agent Playbooks](/agents/playbooks) diff --git a/docs/tx/outlayer.mdx b/docs/tx/outlayer.mdx deleted file mode 100644 index 6a7564e..0000000 --- a/docs/tx/outlayer.mdx +++ /dev/null @@ -1,117 +0,0 @@ ---- -sidebar_label: OutLayer -slug: /tx/examples/outlayer -title: "OutLayer: What did this request/resolution pair do?" -description: "Use Transactions API to read one caller-side OutLayer request, one later worker-side resolution, and the finish receipts only when needed." -displayed_sidebar: transactionsApiSidebar -page_actions: - - markdown -keywords: - - OutLayer - - FastNear - - NEAR - - Transactions API - - receipt - - callback ---- - -import Link from '@site/src/components/LocalizedLink'; - -{/* FASTNEAR_AI_DISCOVERY: This walkthrough stays on observable transaction and receipt evidence. It shows how to read one caller-side OutLayer request together with one later worker-side resolution, then inspect the finish receipts only when needed. */} - -# OutLayer: What did this request/resolution pair do? - -Use this walkthrough when the question is: “What did this OutLayer request do, what later resolution belongs to it, and do I need to look at the finish receipts?” - -Stay on public chain evidence only: read the request tx, read the later resolution tx, and only then decide whether the finish receipts matter. - -## Compact shell walkthrough - -This pair worked on April 18, 2026: - -- caller-side request: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` -- worker-side resolution: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` - -### 1. Main answer: hydrate the request tx and the resolution tx together - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -REQUEST_TX_HASH=AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4 -WORKER_TX_HASH=AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs - -curl -sS "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg request_tx_hash "$REQUEST_TX_HASH" --arg worker_tx_hash "$WORKER_TX_HASH" '{ - tx_hashes: [$request_tx_hash, $worker_tx_hash] - }')" \ - | tee /tmp/outlayer-pair.json >/dev/null - -jq '{ - transactions: [ - .transactions[] - | { - hash: .transaction.hash, - signer_id: .transaction.signer_id, - receiver_id: .transaction.receiver_id, - methods: [ - .transaction.actions[] - | .FunctionCall.method_name? - | select(. != null) - ], - first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) - } - ] -}' /tmp/outlayer-pair.json -``` - -This is the core answer: one request tx, one later resolution tx, and readable signer, receiver, method, and log evidence for both. - -### Optional follow-up: What did the finish path do? - -```bash -jq --arg worker_tx_hash "$WORKER_TX_HASH" ' - .transactions[] - | select(.transaction.hash == $worker_tx_hash) - | { - worker_tx_hash: .transaction.hash, - receipts: [ - .receipts[] - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - actions: [.receipt.receipt.Action.actions[] | keys[0]], - logs: .execution_outcome.outcome.logs - } - ] - } -' /tmp/outlayer-pair.json -``` - -Look for the smallest readable finish-path evidence: - -- `FunctionCall` receipts that continue the finish path -- charging logs such as `[[yNEAR charged: "..."]]` -- follow-up `Transfer` receipts that suggest refund or settlement movement - -Do not start here if the real question is still “which two transactions belong together?” - -### Optional: discover the two hashes first - -```bash -curl -sS "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data '{"account_id":"outlayer.near","desc":true,"limit":10}' \ - | jq '{ - txs_count, - recent_hashes: [.account_txs[:10][] | .transaction_hash] - }' -``` - -Use this only when you do not already know the pair. `/v0/account` gives you candidate hashes, and `/v0/transactions` is the surface that turns them into a readable answer. - -## Related guides - -- Transactions API: Account History -- Transactions API: Transactions by Hash -- Transactions API: Receipt by ID diff --git a/docs/tx/socialdb-proofs.mdx b/docs/tx/socialdb-proofs.mdx index d3cdf04..91cdb6a 100644 --- a/docs/tx/socialdb-proofs.mdx +++ b/docs/tx/socialdb-proofs.mdx @@ -34,65 +34,52 @@ For this live anchor: ```bash SOCIAL_API_BASE_URL=https://api.near.social TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com ACCOUNT_ID=mike.near PROFILE_FIELD=profile/name -PROFILE_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-profile-name.json \ - | jq -r --arg account_id "$ACCOUNT_ID" \ - '.[ $account_id ].profile.name[":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ +PROFILE="$(curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')")" + +echo "$PROFILE" | jq --arg account_id "$ACCOUNT_ID" '{ current_name: .[$account_id].profile.name[""], field_block_height: .[$account_id].profile.name[":block"], parent_profile_block_height: .[$account_id].profile[":block"] -}' /tmp/mike-profile-name.json +}' + +PROFILE_BLOCK_HEIGHT="$(echo "$PROFILE" | jq -r --arg account_id "$ACCOUNT_ID" '.[$account_id].profile.name[":block"]')" ``` 2. Reuse that field-level block in FastNear block receipts and recover the receipt plus tx hash. ```bash -PROFILE_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-profile-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ +BLOCK_RECEIPTS="$(curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')")" + +echo "$BLOCK_RECEIPTS" | jq --arg account_id "$ACCOUNT_ID" '{ profile_receipt: ( first( .block_receipts[] | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | { - receipt_id, - transaction_hash, - block_height, - tx_block_height - } + | {receipt_id, transaction_hash, block_height, tx_block_height} ) ) -}' /tmp/mike-profile-block.json +}' + +PROFILE_TX_HASH="$(echo "$BLOCK_RECEIPTS" | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )')" ``` 3. Reuse that tx hash in `POST /v0/transactions` and decode the SocialDB write payload. @@ -101,31 +88,24 @@ jq --arg account_id "$ACCOUNT_ID" '{ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-profile-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), - description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), - tags: ( - .args - | @base64d - | fromjson - | .data["mike.near"].profile.tags - | keys - ) - } - ) -}' /tmp/mike-profile-transaction.json + | jq --arg account_id "$ACCOUNT_ID" '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | (.args | @base64d | fromjson | .data[$account_id].profile) as $profile + | { + method_name, + profile_name: $profile.name, + description: $profile.description, + tags: ($profile.tags | keys) + } + ) + }' ``` That is the whole lookup pattern: readable value, field-level block, receipt bridge, and transaction payload. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index 2f778f3..790e7b5 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /api/examples title: "Примеры API" -description: "Практические примеры FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга." +description: "Практические примеры FastNear API: поиск аккаунта по ключу, просмотр активов и классификация стейкинга." displayed_sidebar: fastnearApiSidebar page_actions: - markdown @@ -10,427 +10,64 @@ page_actions: ## Примеры -### Определить аккаунт по публичному ключу, а затем получить сводку по нему +### Определить аккаунт по публичному ключу и сразу получить сводку -
-
- Ход -

Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку.

-
-
-

01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа.

-

02jq поднимает тот аккаунт, который вы хотите смотреть дальше.

-

03GET /v1/account/.../full в одном ответе показывает балансы, NFT и стейкинг.

-
-
- -**Ход** - -- Ищете по публичному ключу один или несколько `account_id`. -- Извлекаете первый найденный `account_id` через `jq`. -- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. +Найдите, какому аккаунту принадлежит ключ, и прочитайте его активы за один следующий запрос. ```bash API_BASE_URL=https://api.fastnear.com -PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' -# Пример публичного ключа из модели страницы в документации: -# PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' +PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' -ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" +LOOKUP="$(curl -s "$API_BASE_URL/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')")" -ACCOUNT_ID="$( - curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ - | tee /tmp/fastnear-public-key.json \ - | jq -r '.account_ids[0]' -)" +echo "$LOOKUP" | jq '{matched: (.account_ids | length), account_ids}' -jq '{account_ids}' /tmp/fastnear-public-key.json +ACCOUNT_ID="$(echo "$LOOKUP" | jq -r '.account_ids[0]')" curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' + | jq '{account_id, state, tokens: (.tokens|length), nfts: (.nfts|length), pools: (.pools|length)}' ``` -**Когда переходить дальше** - -Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. - -### Показывает ли этот кошелёк прямой стейкинг, ликвидные стейкинг-токены или и то и другое? - -
-
- Ход -

Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк.

-
-
-

01GET /v1/account/.../staking находит прямую экспозицию через пулы.

-

02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них.

-

03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed.

-
-
- -**Сеть** +Если `matched` больше 1, переключайтесь на [V1 Public Key Lookup All](/api/v1/public-key-all) и пройдитесь по каждому найденному аккаунту. -- mainnet +### Показывает ли кошелёк прямой стейкинг, liquid staking-токены или оба варианта? -**Официальные ссылки** - -- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) -- [Liquid staking](https://docs.near.org/primitives/liquid-staking) - -Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. - -**Ход** - -- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. -- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. -- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. +Прямые позиции в пулах лежат на `/staking`; liquid staking-токены (stNEAR, LiNEAR и т. п.) лежат на `/ft` как обычные FT. Прочитайте оба эндпоинта и классифицируйте кошелёк — `root.near` оказывается `mixed`. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID +ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -``` -1. Получите представление по прямому стейкингу. +STAKING="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking")" +FT="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft")" -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` - -2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null -``` - -3. Классифицируйте аккаунт на основе этих двух индексированных представлений. - -```bash jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ + --argjson staking "$STAKING" \ + --argjson ft "$FT" \ --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + ($staking.pools // []) as $direct + | (($ft.tokens // []) | map(select(.contract_id as $id | $providers | index($id)))) as $liquid | { classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) - }' -``` - -**Когда переходить дальше** - -Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. - -### Заархивировать версию BOS-виджета как provenance NFT - -
-
- Ход -

Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы.

-
-
-

01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции.

-

02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB.

-

03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner.

-
-
- -**Сети** - -- mainnet для чтения виджета из `social.near` -- testnet для безопасного mint provenance NFT в `nft.examples.testnet` - -**Официальные ссылки** - -- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) -- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Ход** - -- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. -- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. -- Хешируете исходник виджета и превращаете его в provenance-метаданные. -- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. -- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. - -Зафиксированный исходный виджет: - -- аккаунт автора: `mob.near` -- путь виджета: `mob.near/widget/Profile` -- SocialDB-блок уровня виджета: `86494825` - -```bash -API_BASE_URL=https://test.api.fastnear.com -MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com -TESTNET_RPC_URL=https://rpc.testnet.fastnear.com -AUTHOR_ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -DESTINATION_COLLECTION_ID=nft.examples.testnet -RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" -``` - -1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. - -```bash -curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ - | tee /tmp/provenance-account-nfts.json >/dev/null - -jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_tokens: [ - .tokens[]? - | select(.contract_id == $destination_collection_id) - | { - contract_id, - token_id, - last_update_block_height - } - ] -}' /tmp/provenance-account-nfts.json -``` - -2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. - -```bash -WIDGET_ARGS_BASE64="$( - jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($author_account_id + "/widget/" + $widget_name)], - options: {with_block_height: true} - }' | base64 | tr -d '\n' -)" - -curl -s "$MAINNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget.json >/dev/null - -jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - widget_path: ($author_account_id + "/widget/" + $widget_name), - socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], - source_preview: ( - .[$author_account_id].widget[$widget_name][""] - | split("\n")[0:8] - ) -}' /tmp/bos-widget.json -``` - -3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. - -```bash -jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][""] -' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx - -WIDGET_BLOCK_HEIGHT="$( - jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][":block"] - ' /tmp/bos-widget.json -)" - -SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" -SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" -TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" - -PROVENANCE_METADATA_JSON="$( - jq -nc \ - --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" \ - --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ - --arg block_height "$WIDGET_BLOCK_HEIGHT" \ - --arg source_sha256 "$SOURCE_SHA256" '{ - title: ("BOS widget archive: " + $widget_path), - description: ("Archived from social.near on mainnet at block " + $block_height), - copies: 1, - extra: ({ - author_account_id: $author_account_id, - widget_name: $widget_name, - widget_path: $widget_path, - source_contract_id: "social.near", - source_network: "mainnet", - socialdb_block_height: ($block_height | tonumber), - source_sha256: $source_sha256 - } | @json) + if ($direct|length)>0 and ($liquid|length)>0 then "mixed" + elif ($direct|length)>0 then "direct_only" + elif ($liquid|length)>0 then "liquid_only" + else "no_visible_staking_position" end, + direct_pools: ($direct | map(.pool_id)), + liquid_tokens: ($liquid | map({contract_id, balance})) }' -)" - -printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' ``` -4. Выпустите provenance NFT в testnet. - -```bash -near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ - --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --argjson metadata "$PROVENANCE_METADATA_JSON" '{ - token_id: $token_id, - receiver_id: $receiver_id, - metadata: $metadata - }')" \ - --accountId "$SIGNER_ACCOUNT_ID" \ - --deposit 0.1 \ - --networkId testnet -``` - -5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. - -Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. - -```bash -NFT_TOKEN_ARGS_BASE64="$( - jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ - | base64 | tr -d '\n' -)" - -for attempt in 1 2 3 4 5; do - curl -s "$TESTNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_token", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget-provenance-token.json >/dev/null - - if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then - break - fi - - sleep 1 -done - -jq '{ - token_id, - owner_id, - title: .metadata.title, - provenance: (.metadata.extra | fromjson) -}' /tmp/bos-widget-provenance-token.json -``` - -**Когда переходить дальше** - -FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](/tx/examples). - -## Частые задачи - -### Что этот аккаунт вообще держит прямо сейчас? - -**Начните здесь** - -- [V1 Full Account View](/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» - -**Следующая страница при необходимости** - -- [V1 Account FT](/api/v1/account-ft), [V1 Account NFT](/api/v1/account-nft) или [V1 Account Staking](/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. -- [Transactions API account history](/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» - -**Остановитесь, когда** - -- Сводка уже отвечает на вопрос по активам в одной выдаче. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](/rpc). -- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](/tx). - -### Определить аккаунты по публичному ключу - -**Начните здесь** - -- [V1 Public Key Lookup](/api/v1/public-key), когда нужен основной аккаунт для ключа. -- [V1 Public Key Lookup All](/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. - -**Следующая страница при необходимости** - -- [V1 Full Account View](/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. - -**Остановитесь, когда** - -- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](/rpc/account/view-access-key) или [View Access Key List](/rpc/account/view-access-key-list). -- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](/tx). - -### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? - -**Начните здесь** - -- [V1 Account FT](/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. -- [V1 Account NFT](/api/v1/account-nft), когда вопрос конкретно про владение NFT. -- [V1 Account Staking](/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. - -**Следующая страница при необходимости** - -- [V1 Full Account View](/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. -- [Transactions API account history](/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» - -**Остановитесь, когда** - -- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. - -**Переходите дальше, когда** - -- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](/rpc). -- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](/tx). +Классификатор знает только то, чему вы его научили — расширяйте `LIQUID_PROVIDERS_JSON` по мере появления новых liquid staking-продуктов и рассматривайте результат как наблюдательный, а не исчерпывающий. ## Частые ошибки - Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. -- Использовать FastNear API, хотя пользователю прямо нужны точные поля RPC или права доступа. +- Использовать FastNear API, хотя пользователю нужны точные поля RPC или права доступа. - Оставаться на страницах сводок по аккаунту, когда вопрос уже стал вопросом об истории транзакций. -- Забывать, что `?network=testnet` поддерживается только на совместимых страницах. -## Полезные связанные страницы +## Связанные страницы - [FastNear API](/api) - [API Reference](/api/reference) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index b36b3d0..42de5db 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: "Примеры KV FastData" -description: "Практические примеры для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC." +description: "Практические примеры KV FastData: scoped-записи, история ключа и переход к точному состоянию." displayed_sidebar: kvFastDataSidebar page_actions: - markdown @@ -10,159 +10,47 @@ page_actions: ## Пример -### Посмотреть индексированные записи одного `predecessor_id`, а затем сузиться до ключа, который изменился +### Посмотреть индексированные записи одного `predecessor_id` и сузиться до изменившегося ключа -
-
- Ход -

Начните с области по `predecessor_id`, переходите к точному ключу только после того, как он заслужил внимание, а RPC оставляйте на самый конец.

-
-
-

01all-by-predecessor даёт последние индексированные строки для одного `predecessor_id` по затронутым контрактам.

-

02get-history-key или history-by-predecessor объясняют, как менялась интересующая строка во времени.

-

03RPC view_state — это уже необязательное точное чтение, когда нужен именно канонический current state, а не индексированная история.

-
-
- -### Shell-сценарий по области предшественника -**Ход** - -- Читаете последние индексированные строки для одного `predecessor_id` по затронутым контрактам. -- Поднимаете интересующие `current_account_id` и точный `key` через `jq`. -- Переиспользуете эти значения в документированном маршруте истории по точному ключу. -- Только после этого решаете, нужен ли вам `view_state` для канонического current state. +`all-by-predecessor` возвращает последние индексированные записи одного аккаунта по каждому контракту, которого он касался. Выберите интересный ключ и прогоните его через `history`, чтобы увидеть, как эта строка менялась со временем. ```bash KV_BASE_URL=https://kv.main.fastnear.com -PREDECESSOR_ID=james.near +PREDECESSOR_ID=jemartel.near -curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ +FIRST="$(curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ -H 'content-type: application/json' \ - --data '{"include_metadata":true,"limit":10}' \ - | tee /tmp/kv-predecessor.json >/dev/null + --data '{"include_metadata":true,"limit":10}')" -jq '{ +echo "$FIRST" | jq '{ page_token, - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] -}' /tmp/kv-predecessor.json - -CURRENT_ACCOUNT_ID="$(jq -r '.entries[0].current_account_id' /tmp/kv-predecessor.json)" -EXACT_KEY="$(jq -r '.entries[0].key' /tmp/kv-predecessor.json)" -ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" - -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | jq '{ - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] - }' + entries: [.entries[] | {current_account_id, predecessor_id, block_height, key, value, tx_hash}] +}' ``` -**Когда переходить дальше** - -Первый запрос отвечает на вопрос по области: «что этот `predecessor_id` сейчас пишет?». Сужение из этой ленты до одного точного ключа отвечает на более точный вопрос: «как именно эта строка дошла до такого состояния?». Если картина всё ещё шире одного ключа, ещё немного побудьте на [History by Predecessor](/fastdata/kv/history-by-predecessor), а уже потом переходите к точной истории ключа или RPC. - -## Частые задачи - -### Начать с записей одного `predecessor_id` - -**Начните здесь** - -- [Всё по `predecessor_id`](/fastdata/kv/all-by-predecessor), когда вы знаете, кто писал строки, но ещё не знаете, какой точный ключ важнее всего. - -**Следующая страница при необходимости** - -- [История по точному ключу](/fastdata/kv/get-history-key), если одна строка становится настоящим фокусом. -- [История по `predecessor_id`](/fastdata/kv/history-by-predecessor), если более широкая картина записей всё ещё важнее точного ключа. - -**Остановитесь, когда** - -- Уже можно объяснить, что писал этот `predecessor_id`, и заслуживает ли одна строка более глубокой истории. - -**Переходите дальше, когда** - -- Пользователю нужно каноническое текущее состояние в цепочке, а не индексированная история записей. Переходите к [View State](/rpc/contract/view-state). - -### Превратить один точный ключ в историю изменений - -**Начните здесь** - -- [История по точному ключу](/fastdata/kv/get-history-key) для поиска истории по пути. -- [History by Key](/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. - -**Следующая страница при необходимости** - -- Возвращайтесь к [Последнему по точному ключу](/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. +Для `jemartel.near` в выдаче смешиваются подтверждение идентичности `account_id` на `contextual.near` и серия добавлений `graph/follow/*` в тот же контракт. `tx_hash` в каждой строке — это прямой переход в [/tx/examples](/tx/examples#у-меня-один-хеш-транзакции-что-произошло), если нужна полная история транзакции за любой записью. -**Остановитесь, когда** +Поднимите самую свежую строку и прогоните её через `history`: -- Уже можно объяснить, как ключ менялся со временем. - -**Переходите дальше, когда** - -- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. - -### Проследить записи от одного `predecessor_id` - -**Начните здесь** - -- [Всё по `predecessor_id`](/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. -- [История по `predecessor_id`](/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. - -**Следующая страница при необходимости** - -- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. - -**Остановитесь, когда** - -- Уже можно ответить, что именно этот предшественник изменил и где. - -**Переходите дальше, когда** - -- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. - -### Пакетно проверить несколько известных ключей - -**Начните здесь** - -- [Пакетный поиск по ключам](/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. - -**Следующая страница при необходимости** - -- Переведите один интересный ключ в [Историю по точному ключу](/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. - -**Остановитесь, когда** - -- Пакетный ответ уже показывает, какие ключи действительно важны. +```bash +CURRENT_ACCOUNT_ID="$(echo "$FIRST" | jq -r '.entries[0].current_account_id')" +EXACT_KEY="$(echo "$FIRST" | jq -r '.entries[0].key')" +ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" -**Переходите дальше, когда** +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | jq '{entries: [.entries[] | {block_height, value}]}' +``` -- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. +Для строки `account_id` `history` возвращает одну запись на блоке `185965311` со значением `"jemartel.near:mainnet"` — подтверждение идентичности держится, стабильно с того блока. KV сохраняет каждую запись одинаково: у тихого ключа — одна строка, у активного — много, форма та же, без агрегации. ## Частые ошибки - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. -- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. +- Использовать KV FastData, когда пользователю на самом деле нужны балансы или активы. - Путать индексированную историю с точным текущим состоянием в цепочке. - Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. -## Полезные связанные страницы +## Связанные страницы - [KV FastData API](/fastdata/kv) - [RPC Reference](/rpc) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index e014946..2e059fb 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -2,36 +2,17 @@ sidebar_label: Examples slug: /neardata/examples title: "Примеры NEAR Data" -description: "Практические примеры для мониторинга недавней активности контракта, подтверждения optimistic-наблюдений и проверки изменений на уровне shard." +description: "Практические примеры NEAR Data: живой мониторинг, optimistic-проверки и доказательство на уровне shard." displayed_sidebar: nearDataApiSidebar page_actions: - markdown --- -Используйте NEAR Data, когда вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. - ## Примеры -### Был ли мой контракт затронут в последнем финализированном блоке? - -
-
- Ход -

Сначала дайте NEAR Data ответить на задачу мониторинга, а уже потом сохраняйте tx hash или receipt ID для следующей поверхности, если это вообще понадобится.

-
-
-

01last-block-final находит самую новую финализированную высоту.

-

02block даёт один недавний гидратированный документ блока с уже присоединёнными данными по shard.

-

03Суммируйте прямые транзакции, входящие receipts, результаты выполнения и state_changes для нужного контракта. Считайте state_changes самым сильным сигналом того, что контракт действительно изменился.

-
-
- -Такой сценарий вполне честно может вернуть `touched: false`, если блок тихий. Это тоже полезный ответ: в самом новом финализированном блоке сейчас нет ничего, что требовало бы более глубокого разбора. +Каждый гидратированный документ блока NEAR Data несёт транзакции, receipts, результаты исполнения и state changes с разбивкой по shard. Три сценария ниже используют один `bash`-помощник, который сворачивает эти четыре сигнала в одну сводку с полями для перехода дальше. Определите его один раз и прогоняйте блоки через него: ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near - contract_touch_summary() { jq -r --arg contract "$1" ' [ .shards[] | { @@ -66,166 +47,80 @@ contract_touch_summary() { sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) }' } +``` -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" +### Был ли мой контракт затронут в последнем финализированном блоке? -printf 'Latest finalized block: %s\n' "$FINAL_LOCATION" +`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок; пройдите по нему и направьте результат сразу в помощник. -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + +curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ | contract_touch_summary "$TARGET_CONTRACT" ``` -Читайте ответ так: - -- `touched: false` означает, что самый новый финализированный блок не упомянул и не изменил контракт ни одним из отслеживаемых способов. -- `sample_tx_hash` означает, что у вас уже есть хороший якорь для следующего шага на `/tx`. -- `sample_receipt_id` без tx hash обычно означает, что контракт появился в цепочке через receipts, и NEAR Data уже сэкономила вам более дешёвый этап мониторинга. +Читайте `touched: false` как полный и однозначный ответ для тихого блока. При `true` поля перехода (`sample_tx_hash`, `sample_receipt_id`) сразу ведут вас в [/tx/examples](/tx/examples) за человекочитаемой историей. Один запрос заменяет ручной просмотр chunks — и учтите: `touched: true` с `state_changes: 0` — это реальная форма: receipt может попасть в chunk, не вызвав в том же блоке мутации состояния. ### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? -
-
- Ход -

Используйте один и тот же словарь contract-touch на обеих поверхностях, чтобы сравнение было честным.

-
-
-

01last-block-optimistic находит самую новую optimistic-высоту.

-

02block-optimistic показывает ранний сигнал для того же контракта.

-

03block на той же высоте либо подтверждает то же наблюдение, либо показывает, что finality ещё не догнала.

-
-
- -Если finality уже догнала, optimistic- и finalized-сводки могут совпасть сразу. Это тоже полезно: ранний сигнал уже попал в стабильную историю. +Optimistic-блоки живут по адресу `/v0/block_opt/{height}`; как только finality догоняет (обычно в пределах одного блока, ~1 с на mainnet), та же высота становится доступна и по `/v0/block/{height}`. Прогоните помощник на обеих и сравните. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near - -contract_touch_summary() { - jq -r --arg contract "$1" ' - [ .shards[] | { - shard_id, - direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) - } - | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) - ] as $rows - | { - height: .block.header.height, - hash: .block.header.hash, - contract: $contract, - touched: (($rows | length) > 0), - shards: ($rows | map(.shard_id)), - evidence: { - direct_txs: (($rows | map(.direct_txs) | add) // 0), - incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), - execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), - state_changes: (($rows | map(.state_changes) | add) // 0) - } - }' -} - OPT_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' + | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" - OPT_HEIGHT="${OPT_LOCATION##*/}" -printf 'Latest optimistic block: %s\n' "$OPT_LOCATION" +echo "Optimistic view at $OPT_HEIGHT:" +curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | contract_touch_summary "$TARGET_CONTRACT" -curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" \ - | tee /tmp/neardata-optimistic-block.json \ - | contract_touch_summary "$TARGET_CONTRACT" - -curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT" \ - | tee /tmp/neardata-final-same-height.json >/dev/null - -if jq -e 'type == "null"' /tmp/neardata-final-same-height.json >/dev/null; then - printf 'Finalized block %s is not available yet; finality has not caught up.\n' "$OPT_HEIGHT" +echo "Finalized view at $OPT_HEIGHT:" +FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" +if [ "$(echo "$FINAL" | jq 'type')" = '"null"' ]; then + echo "finality has not caught up to $OPT_HEIGHT yet" else - printf 'Finalized block %s is already available; compare the stable answer below.\n' "$OPT_HEIGHT" - contract_touch_summary "$TARGET_CONTRACT" < /tmp/neardata-final-same-height.json + echo "$FINAL" | contract_touch_summary "$TARGET_CONTRACT" fi ``` -Практический вывод такой: - -- optimistic — это ранний сигнал, на который цикл мониторинга может быстро отреагировать; -- finalized — это стабильный ответ, который уже можно показывать пользователям или использовать в устойчивой автоматизации. +На здоровой сети обе сводки совпадают сразу; ценность — в самом шаблоне, а не в драматичной разнице. Цикл мониторинга, который реагирует на optimistic-сигнал, знает: тот же ответ — на один блок от надёжного. Ветку `finality has not caught up` используйте, когда действительно нужно отличить «увидено optimistically» от «подтверждено» — во время стресса сети этот разрыв расширяется. ### Какой shard действительно изменил мой контракт в этом блоке? -
-
- Ход -

Сначала используйте весь блок, чтобы найти нужный shard, а затем дайте block-shard доказать само изменение.

-
-
-

01Просканируйте список shard внутри финализированного блока и найдите state_changes по вашему контракту.

-

02Откройте только тот shard, который действительно изменил контракт.

-

03Сохраните совпадающие state_changes и нужные результаты исполнения как доказательство на уровне shard.

-
-
- -На момент написания недавний финализированный блок `194727131` дал чистый живой пример для `intents.near`: контракт сначала появился как входящий receipt на shard `8`, а затем действительно выполнился и изменил состояние на shard `7`. - -Если для вашей задачи нужен более свежий блок, переиспользуйте ту же сводку из первого примера на нескольких соседних финализированных высотах, а затем подставьте найденную высоту в тот же вызов `block-shard`. +Блоки тонкие — в большинстве финализированных блоков нет мутаций состояния ни для одного конкретного контракта. Идите назад от финализированной головы, пока помощник не покажет `state_changes > 0`, затем откройте «победивший» shard через `/v0/block/{height}/shard/{shard_id}` ради самого payload мутации. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near -EXAMPLE_HEIGHT=194727131 - -curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT" \ - | tee /tmp/neardata-block-194727131.json \ - | jq --arg contract "$TARGET_CONTRACT" '[ - .shards[] | { - shard_id, - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) - } - | select(.incoming_receipts + .execution_outcomes + .state_changes > 0) - ]' - -curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT/shard/7" \ +HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" +TARGET_HEIGHT="" +WINNING_SHARD="" + +for OFFSET in 0 1 2 3 4 5 6 7 8 9; do + H=$((HEAD - OFFSET)) + SUMMARY="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" | contract_touch_summary "$TARGET_CONTRACT")" + if [ "$(echo "$SUMMARY" | jq '.evidence.state_changes')" -gt 0 ]; then + TARGET_HEIGHT=$H + WINNING_SHARD="$(echo "$SUMMARY" | jq -r '.shards[0]')" + echo "$SUMMARY" + break + fi +done + +curl -s "$NEARDATA_BASE_URL/v0/block/$TARGET_HEIGHT/shard/$WINNING_SHARD" \ | jq --arg contract "$TARGET_CONTRACT" '{ shard_id, chunk_hash: .chunk.header.chunk_hash, - matching_state_changes: [ - .state_changes[] - | select(.change.account_id? == $contract) - | {type, cause, account_id: .change.account_id} - ][0:2], - matching_execution_outcomes: [ - .receipt_execution_outcomes[] - | select(.execution_outcome.outcome.executor_id == $contract) - | { - receipt_id: .execution_outcome.id, - executor_id: .execution_outcome.outcome.executor_id, - status: .execution_outcome.outcome.status, - predecessor_id: .receipt.predecessor_id - } - ][0:2] + matching_state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause_type: (.cause | keys[0]), account_id: .change.account_id}][0:3], + matching_execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0]), predecessor_id: .receipt.predecessor_id}][0:3] }' ``` -Практическое правило здесь простое: - -- используйте `block`, когда первый вопрос звучит как «какой shard вообще важен?»; -- используйте `block-shard`, когда настоящий вопрос уже стал таким: «покажи мне сам payload shard, который изменил состояние». +На mainnet `intents.near` стабильно выполняется на shard 7, поэтому обход назад обычно попадает в цель за несколько блоков. Payload shard затем называет конкретные типы state-change (`account_update`, `data_update` и т. п.) и результаты исполнения receipt, которые их породили, — shard-локальное доказательство без догадок. Для менее активных контрактов расширьте диапазон `OFFSET`. -## Когда пора расширять поверхность +## Когда расширить поверхность - Используйте [Transactions API](/tx), когда у вас уже есть `tx_hash` и нужен человекочитаемый рассказ о транзакции. -- Используйте [Справочник RPC](/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. +- Используйте [RPC Reference](/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. - Используйте [Block Headers](/neardata/block-headers), когда нужна только динамика head/finality, а не проверка contract-touch. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index 217c0b3..0792e6b 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /rpc/examples title: "Примеры RPC" -description: "Практические примеры FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций." +description: "Практические примеры RPC: проверки состояния, инспекция блоков, чтение контрактов и отправка транзакций." displayed_sidebar: rpcSidebar page_actions: - markdown @@ -10,1904 +10,365 @@ page_actions: # Примеры RPC -Начинайте с RPC-метода, который отвечает на вопрос. Отправляйте через `broadcast_tx_async`, отслеживайте через `tx` и расширяйте разбор только когда действительно нужны дерево receipts, raw state или трассировка по шардам. +Начинайте с RPC-метода, который отвечает на вопрос. Используйте `tx`, чтобы отследить включение и финальность по хешу транзакции, и расширяйте поверхность только когда нужны дерево receipts, сырой state или трассировка на уровне shard. -## Отправка и отслеживание транзакции +## Включение транзакции и финальность -### Отправить транзакцию и затем проследить её от хеша до финального исполнения +### Отследить транзакцию от хеша до финальности -Нужен стандартный путь отправки через RPC? Отправляйте через `broadcast_tx_async`, затем опрашивайте `tx`. К `EXPERIMENTAL_tx_status` переходите только если нужно дерево receipts. - -Зафиксированная mainnet-транзакция: - -- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- signer: `mike.near` -- receiver: `social.near` -- высота блока включения: `79574923` -- высота блока исполнения receipt для записи в SocialDB: `79574924` - -
-
- Ход -

Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно.

-
-
-

01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше.

-

02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения.

-

03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts.

-
-
- -**Точки выбора** - -- какой эндпоинт отправки брать первым -- что опрашивать после того, как у вас появился tx hash -- как `wait_until` связан с included-, optimistic- и final-гарантиями -- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` - -```mermaid -flowchart LR - S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] - A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] - T --> F["Транзакция полностью завершена"] - T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] - F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] -``` - -| Метод | Когда использовать | Что вернётся | Роль здесь | -| --- | --- | --- | --- | -| [`broadcast_tx_async`](/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | -| [`send_tx`](/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | -| [`broadcast_tx_commit`](/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | -| [`tx`](/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | -| [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | - -**Карта статусов и ожидания** - -Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. - -| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | -| --- | --- | --- | -| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | -| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | -| `INCLUDED_FINAL` | блок включения уже финален | `tx` | -| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | -| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | - -Используйте методы так: - -- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash -- используйте `tx` как обычный цикл опроса -- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу - -**Ход** - -- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. -- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. -- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. -- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. +Есть tx hash? Опрашивайте `tx` с минимальным порогом `wait_until`, который отвечает на ваш вопрос. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near -RECEIVER_ID=social.near -``` - -1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data '{ - "jsonrpc": "2.0", - "id": "fastnear", - "method": "broadcast_tx_async", - "params": ["BASE64_SIGNED_TX"] - }' \ - | jq . -``` - -В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. - -2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow +SIGNER_ACCOUNT_ID=mike.testnet -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "INCLUDED_FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' -``` - -Что здесь важно заметить: - -- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality -- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения -- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt - -3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - receipts_outcome_count: (.result.receipts_outcome | length) - }' -``` - -Что здесь важно заметить: - -- для исторической tx этот вызов тоже возвращается сразу -- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» -- для многих приложений именно здесь и стоит остановиться - -4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. - -```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" --arg signer_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", id: "fastnear", method: "tx", + params: {tx_hash: $tx_hash, sender_account_id: $signer_id, wait_until: "INCLUDED"} + }')" \ | jq '{ + asked: "INCLUDED", final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status, + status_class: (.result.status | keys[0]), receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». - -5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transaction_handoff: .transactions[0].transaction_outcome.outcome.status - }' -``` - -Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» - -**Рекомендуемый паттерн** - -- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. -- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. -- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. - -## Механика аккаунтов и ключей +Для зафиксированной исторической транзакции (1-yocto self-transfer от `mike.testnet`) ответ возвращается как `FINAL`, хотя мы спрашивали `INCLUDED`. Правило такое: **`wait_until` — это минимальный порог, а не целевой**. Узел возвращает тот этап, которого транзакция действительно достигла: для исторической всегда `FINAL`; для полётной выбирайте `INCLUDED`, когда достаточно включения и нужен самый ранний возврат, или `FINAL`, когда реальный вопрос звучит «завершилась ли?». -Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. +Два перехода отсюда: -### Проверить и удалить старые function-call-ключи Near Social +- **Отправляете в реальном времени?** [`broadcast_tx_async`](/rpc/transaction/broadcast-tx-async) возвращает хеш сразу после того, как узел принял payload — отслеживайте отдельно через `tx`. [`send_tx`](/rpc/transaction/send-tx) одновременно отправляет и блокируется на выбранном `wait_until` в одном запросе. +- **Нужно дерево receipts, а не только outcome?** `tx` уже включает `receipts_outcome`; расширяйте поверхность до [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) только тогда, когда дополнительно нужны сырые записи receipts. -Есть старые function-call-ключи для `social.near`? Сначала найдите нужный ключ, потом удалите один конкретный. +## Инспекция блока на tip -
-
- Ход -

Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление.

-
-
-

01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near.

-

02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта.

-

03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат.

-
-
+### Описать первый action первой транзакции на текущем tip -**Ход** - -- Через сам RPC получаете полный список access key аккаунта. -- Сужаете этот список до function-call-ключей, привязанных к `social.near`. -- Точно проверяете один выбранный ключ перед удалением. -- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. - -Сразу важны два ограничения: - -- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. -- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». +Пройдите `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export SOCIAL_RECEIVER_ID=social.near -export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' -export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' -export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' -``` +RPC_URL=https://rpc.mainnet.fastnear.com +EMPTY_TX_ROOT=11111111111111111111111111111111 -1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. +BLOCK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ + | jq -r '.result.sync_info.latest_block_hash')" -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } +CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_hash "$BLOCK_HASH" '{ + jsonrpc:"2.0",id:"fastnear",method:"block",params:{block_id:$block_hash} }')" \ - | tee /tmp/fastnear-access-keys.json >/dev/null - -jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' - .result.keys[] - | select((.access_key.permission | type) == "object") - | select(.access_key.permission.FunctionCall.receiver_id == $receiver) - | { - public_key, - nonce: .access_key.nonce, - receiver_id: .access_key.permission.FunctionCall.receiver_id, - method_names: .access_key.permission.FunctionCall.method_names, - allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") - } -' /tmp/fastnear-access-keys.json -``` + | jq -r --arg empty "$EMPTY_TX_ROOT" ' + first(.result.chunks[] | select(.tx_root != $empty) | .chunk_hash) // empty')" -Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. - -2. Ещё раз проверьте конкретный ключ перед удалением. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$DELETE_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } +if [ -z "$CHUNK_HASH" ]; then + echo "tip block had no transactions in any chunk — rerun on the next head" +else + curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg chunk_hash "$CHUNK_HASH" '{ + jsonrpc:"2.0",id:"fastnear",method:"chunk",params:{chunk_id:$chunk_hash} }')" \ - | jq '{nonce: .result.nonce, permission: .result.permission}' + | jq '{ + chunk_shard: .result.header.shard_id, + chunk_height: .result.header.height_included, + first_tx: { + hash: .result.transactions[0].hash, + signer_id: .result.transactions[0].signer_id, + receiver_id: .result.transactions[0].receiver_id + }, + first_action: ( + .result.transactions[0].actions[0] as $a + | if ($a | type) == "string" then {kind: $a} + elif $a.FunctionCall then {kind: "FunctionCall", method_name: $a.FunctionCall.method_name} + else {kind: ($a | keys[0])} end + ) + }' +fi ``` -3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. - -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - is_function_call: true, - limit: 10 - }')" \ - | jq '{ - account_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_success - } - ] - }' -``` +Живой запуск возвращает первый chunk текущего tip, первую транзакцию и первый action — часто это `FunctionCall` на контракте моста или tg-бота (mainnet активен). Tip-блок может быть валидным и при этом не содержать транзакций ни в одном chunk — поэтому ветка с пустым результатом остаётся; это честный ответ для тихого момента сети. -Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. +## Механика аккаунтов и ключей -4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. +### Аудит старых function-call-ключей Near Social -Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. +У создателей накапливаются Social function-call-ключи от каждого кошелька и каждого BOS-шлюза, которым они пользовались. `view_access_key_list` возвращает их все; один фильтр сужает до `social.near`, а **младшие шесть цифр nonce** заодно служат счётчиком использования — новые ключи стартуют с `block_height * 10^6` и инкрементируются на единицу за каждую транзакцию. ```bash -SIGNED_TX_BASE64="$( - node --input-type=module <<'EOF' -import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; - -const { - ACCOUNT_ID, - NETWORK_ID = 'mainnet', - RPC_URL = 'https://rpc.mainnet.fastnear.com', - DELETE_PUBLIC_KEY, - FULL_ACCESS_PUBLIC_KEY, - FULL_ACCESS_PRIVATE_KEY, -} = process.env; - -for (const name of [ - 'ACCOUNT_ID', - 'DELETE_PUBLIC_KEY', - 'FULL_ACCESS_PUBLIC_KEY', - 'FULL_ACCESS_PRIVATE_KEY', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); -const derivedPublicKey = keyPair.getPublicKey().toString(); - -if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { - throw new Error( - `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: ACCOUNT_ID, - public_key: FULL_ACCESS_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const transaction = transactions.createTransaction( - ACCOUNT_ID, - utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), - ACCOUNT_ID, - BigInt(accessKey.nonce) + 1n, - [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -)" -``` - -5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +RECEIVER_ID=social.near -```bash curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. - -```bash -if curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_access_key_list",account_id:$account_id,finality:"final"} }')" \ - | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' - .result.keys[] - | select(.public_key == $public_key) - ' >/dev/null; then - echo "Key is still present: $DELETE_PUBLIC_KEY" -else - echo "Key deleted: $DELETE_PUBLIC_KEY" -fi + | jq --arg receiver "$RECEIVER_ID" ' + { + total_keys: (.result.keys | length), + social_fcks: [ + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + created_near_block: (.access_key.nonce / 1000000 | floor), + tx_count: (.access_key.nonce % 1000000), + method_names: (.access_key.permission.FunctionCall.method_names | if . == [] then "ANY" else . end), + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } + ] | sort_by(.tx_count) + }' ``` -**Когда переходить дальше** +Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на очистку. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность конкретного dapp. -Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. +Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом — function-call-ключ не может авторизовать `DeleteKey` — и отправьте через [`send_tx`](/rpc/transaction/send-tx). Повторный запуск того же списка подтверждает удаление. Само подписание — стандартная near-api-js-история и не самая интересная часть аудита. -### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? +### Какая транзакция добавила этот `social.near` function-call-ключ и кто её авторизовал? -Ключ уже виден на аккаунте? Вернитесь к транзакции `AddKey`, которая его создала. - -
-
- Ход -

Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно.

-
-
-

01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории.

-

02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта.

-

03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey.

-
-
- -**Ход** - -- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. -- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. -- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. -- Подтягиваете кандидата по транзакциям и различаете три разных ключа: - - ключ, который был добавлен - - public key верхнеуровневого signer - - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` - -Сразу важны три детали про nonce: - -- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. -- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. -- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. +Тот же nonce, что считает использование, заодно якорит `AddKey` во времени блоков: новые ключи стартуют примерно с `block_height * 10^6`, так что деление текущего nonce на миллион даёт плотное окно поиска. Один раз гидратируйте кандидатов — и ответ уже несёт достаточно, чтобы отличить прямой `AddKey` от делегированной (meta-tx) авторизации, то есть показать, *какой ключ подписал решение*, а не только какой аккаунт оплатил gas. ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' - -# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: -# export ACCOUNT_ID=mike.near -# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' -``` - -1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$TARGET_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } - }')" \ - | tee /tmp/key-origin-view.json >/dev/null - -CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" -ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" -SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" -SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" - -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_public_key "$TARGET_PUBLIC_KEY" \ - --argjson current_nonce "$CURRENT_NONCE" \ - --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ - --argjson search_from "$SEARCH_FROM" \ - --argjson search_to "$SEARCH_TO" \ - --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ - account_id: $account_id, - target_public_key: $target_public_key, - current_nonce: $current_nonce, - estimated_receipt_block: $estimated_receipt_block, - search_from_tx_block_height: $search_from, - search_to_tx_block_height: $search_to, - permission: ($permission | fromjson) - }' -``` +RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=mike.near +TARGET_PUBLIC_KEY=ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs -Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. +CURRENT_NONCE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg public_key "$TARGET_PUBLIC_KEY" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_access_key",account_id:$account_id,public_key:$public_key,finality:"final"} + }')" \ + | jq -r '.result.nonce')" -2. Ищите историю аккаунта только внутри этого диапазона блоков. +ADD_KEY_BLOCK=$((CURRENT_NONCE / 1000000)) -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_tx_block_height "$SEARCH_FROM" \ - --argjson to_tx_block_height "$SEARCH_TO" '{ - account_id: $account_id, - is_real_signer: true, - from_tx_block_height: $from_tx_block_height, - to_tx_block_height: $to_tx_block_height, - desc: false, - limit: 50 +TX_HASHES="$(curl -s "$TX_BASE_URL/v0/account" -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" \ + --argjson from $((ADD_KEY_BLOCK - 20)) --argjson to $((ADD_KEY_BLOCK + 5)) '{ + account_id: $account_id, is_real_signer: true, + from_tx_block_height: $from, to_tx_block_height: $to, desc: false, limit: 50 }')" \ - | tee /tmp/key-origin-candidates.json >/dev/null - -jq '{ - txs_count, - candidate_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_signer, - is_real_signer, - is_predecessor, - is_receiver - } - ] -}' /tmp/key-origin-candidates.json -``` - -Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. - -3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. - -```bash -TX_HASHES_JSON="$( - jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json -)" - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ - | tee /tmp/key-origin-transactions.json >/dev/null - -jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) + | jq -c '[.account_txs[].transaction_hash]')" + +curl -s "$TX_BASE_URL/v0/transactions" -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES" '{tx_hashes: $tx_hashes}')" \ + | jq --arg target "$TARGET_PUBLIC_KEY" ' + [ .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? | .AddKey? | select(.public_key == $target) + | {mode: "direct", authorizing_public_key: $tx.transaction.public_key, permission: .access_key.permission}), + ($tx.transaction.actions[]? | .Delegate? | .delegate_action as $d + | $d.actions[]? | .AddKey? | select(.public_key == $target) + | {mode: "delegated", authorizing_public_key: $d.public_key, permission: .access_key.permission}) + ) | { - authorization_mode: "direct", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $tx.transaction.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }), - ($tx.transaction.actions[]? - | .Delegate? - | .delegate_action as $delegate - | $delegate.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "delegated", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $delegate.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - tx_block_hash: $tx.execution_outcome.block_hash, - receiver_id: $tx.transaction.receiver_id - } + . -' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json -``` - -Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. - -Для примерного ключа `mike.near` выше совпадение оказывается делегированным: - -- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` -- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` -- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` -- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` - -4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. - -```bash -ADD_KEY_RECEIPT_ID="$( - jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | .receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) - | .receipt.receipt_id - ' /tmp/key-origin-transactions.json | head -n 1 -)" - -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receipt_block_height: .receipt.block_height, - tx_block_height: .receipt.tx_block_height, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - transaction_hash: .receipt.transaction_hash - }' -``` - -Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. - -**Когда переходить дальше** - -Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. - -### Проверить регистрацию FT storage и затем перевести токены - -Нужно отправить FT безопасно? Сначала проверьте storage registration получателя. - -
-
- Ход -

Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу.

-
-
-

01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас.

-

02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит.

-

03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог.

-
-
- -**Сеть** - -- testnet - -**Официальные ссылки** - -- [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) -- [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) - -В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. - -**Ход** - -- Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. -- При необходимости получаете минимальный размер storage deposit. -- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. -- Подтверждаете баланс получателя тем же view-методом самого контракта. - -```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet -export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' -export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' -export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + signer_id: $tx.transaction.signer_id, + receiver_id: $tx.transaction.receiver_id, + add_key_receipt: ([$tx.receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]?; .AddKey.public_key? == $target)) + | {receipt_id: .receipt.receipt_id, receipt_block: .execution_outcome.block_height}][0]) + } + . + ]' ``` -1. Проверьте, зарегистрирован ли получатель на FT-контракте. +Для ключа `ed25519:7GZg…` аккаунта `mike.near` (первый `social.near` FCK из аудита выше) это разрешается в транзакцию `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` на внешнем блоке tx `112057390`. Внешний signer — `app.herewallet.near`, это relayer HERE Wallet, и `mode: "delegated"` рассказывает остальную историю: relayer оплатил gas, но *авторизующий* ключ внутри Delegate — `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu`, full-access-ключ `mike.near`, который подписал сам `AddKey`. Это та разница meta-tx, которую верхнеуровневый `signer_id` в одиночку скрыл бы. -```bash -STORAGE_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" +`add_key_receipt` замыкает картину: `AddKey` выполнился в блоке `112057392`, через два блока после внешней tx, потому что Delegate прыгает из shard relayer в shard целевого аккаунта. Расширьте окно `-20/+5`, если ключом с момента создания пользовались активно. -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$STORAGE_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "storage_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/ft-storage-balance.json >/dev/null - -jq '{ - registered: ((.result.result | implode | fromjson) != null), - storage_balance: (.result.result | implode | fromjson) -}' /tmp/ft-storage-balance.json -``` +### Зарегистрировать FT-хранилище при необходимости и затем перевести токены -2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. +Токены NEP-141 требуют, чтобы каждый получатель предварительно зарегистрировал storage на контракте, прежде чем сможет держать баланс. Два view-вызова авторитетно отвечают на вопрос регистрации *до* отправки — пропуск этой проверки и есть причина, по которой `ft_transfer` в итоге тихо возвращается отправителю. ```bash -MIN_STORAGE_YOCTO="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_CONTRACT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "storage_balance_bounds", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee /tmp/ft-storage-bounds.json \ - | jq -r '.result.result | implode | fromjson | .min' -)" - -printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" -``` - -3. Определите одну переиспользуемую функцию подписи для function-call к контракту. +RPC_URL=https://rpc.testnet.fastnear.com +TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +RECEIVER_ACCOUNT_ID=mike.testnet -Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. - -```bash -sign_function_call() { - METHOD_NAME="$1" \ - ARGS_JSON="$2" \ - DEPOSIT_YOCTO="$3" \ - GAS_TGAS="$4" \ - node --input-type=module <<'EOF' -import { InMemorySigner, KeyPair, transactions, utils } from 'near-api-js'; - -const { - NETWORK_ID = 'testnet', - RPC_URL = 'https://rpc.testnet.fastnear.com', - TOKEN_CONTRACT_ID, - SENDER_ACCOUNT_ID, - SENDER_PUBLIC_KEY, - SENDER_PRIVATE_KEY, - METHOD_NAME, - ARGS_JSON, - DEPOSIT_YOCTO = '0', - GAS_TGAS = '100', -} = process.env; - -for (const name of [ - 'TOKEN_CONTRACT_ID', - 'SENDER_ACCOUNT_ID', - 'SENDER_PUBLIC_KEY', - 'SENDER_PRIVATE_KEY', - 'METHOD_NAME', - 'ARGS_JSON', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); -const signer = await InMemorySigner.fromKeyPair( - NETWORK_ID, - SENDER_ACCOUNT_ID, - keyPair -); - -const derivedPublicKey = keyPair.getPublicKey().toString(); -if (derivedPublicKey !== SENDER_PUBLIC_KEY) { - throw new Error( - `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: SENDER_ACCOUNT_ID, - public_key: SENDER_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const action = transactions.functionCall( - METHOD_NAME, - Buffer.from(ARGS_JSON), - BigInt(GAS_TGAS) * 10n ** 12n, - BigInt(DEPOSIT_YOCTO) -); - -const transaction = transactions.createTransaction( - SENDER_ACCOUNT_ID, - utils.PublicKey.fromString(SENDER_PUBLIC_KEY), - TOKEN_CONTRACT_ID, - BigInt(accessKey.nonce) + 1n, - [action], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - SENDER_ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -} -``` +ACCOUNT_ARGS_B64="$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -4. При необходимости сначала зарегистрируйте storage для получателя. - -```bash -if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then - SIGNED_TX_BASE64="$( - sign_function_call \ - storage_deposit \ - "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id, - registration_only: true - }')" \ - "$MIN_STORAGE_YOCTO" \ - 100 - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash - }' -fi -``` - -5. После готовности storage переведите FT. - -```bash -SIGNED_TX_BASE64="$( - sign_function_call \ - ft_transfer \ - "$(jq -nc \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ - receiver_id: $receiver_id, - amount: $amount, - memo: "FastNear RPC example" - }')" \ - 1 \ - 100 -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } +REGISTERED="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_of",args_base64:$args,finality:"final"} }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Подтвердите FT-баланс получателя тем же view-методом контракта. - -```bash -RECEIVER_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - receiver_balance: (.result.result | implode | fromjson) - }' -``` - -**Когда переходить дальше** - -Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. - -## Чтения контракта и сырое состояние - -Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» - -### Как прочитать сырое состояние контракта напрямую? + | jq '(.result.result | implode | fromjson) != null')" -Нужен raw storage, а не только view-метод? Читайте состояние напрямую, затем сравнивайте с view. - -Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`. Значение может меняться; важна последовательность чтений: сначала raw storage, потом проверка через view-метод. - -- `view_state` читает сырой ключ `STATE` прямо из storage контракта -- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API - -
-
- Ход -

Сначала прочитайте storage напрямую, а уже потом дайте контракту подтвердить тот же ответ через view-метод.

-
-
-

01RPC view_state читает сырой ключ STATE, не запуская код контракта.

-

02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта.

-

03RPC call_function get_num — это удобная перепроверка того, что прямое чтение storage и view-метод по-прежнему дают один и тот же ответ.

-
-
- -Здесь важнее ментальная модель, чем сам счётчик: - -- `view_state` — это прямое чтение storage из trie -- `call_function` исполняет read-only-метод контракта -- оба способа могут ответить на один и тот же вопрос, но делают разную работу - -```mermaid -flowchart LR - S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] - R --> D["Декодировать base64 + Borsh"] - D --> N["Знаковое значение счётчика"] - C["RPC call_function get_num"] --> J["JSON-результат метода"] - N --> X["Сравнить"] - J --> X - X --> A["Одно и то же текущее значение"] -``` - -**Ход** - -- Читаете сырой ключ `STATE` из storage контракта. -- Декодируете возвращённые байты в текущее знаковое значение счётчика. -- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. - -```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export CONTRACT_ID=counter.near-examples.testnet -export STATE_PREFIX_BASE64=U1RBVEU= -``` - -1. Сначала прочитайте сырое состояние контракта. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$CONTRACT_ID" \ - --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_state", - account_id: $account_id, - prefix_base64: $prefix_base64, - finality: "final" - } - }')" \ - | tee /tmp/counter-view-state.json >/dev/null - -jq '{ - block_height: .result.block_height, - key_base64: .result.values[0].key, - value_base64: .result.values[0].value -}' /tmp/counter-view-state.json - -jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json -``` - -Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. - -2. Декодируйте байты значения в знаковое число счётчика. - -```bash -RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" - -python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . -import base64 -import json -import sys - -raw = base64.b64decode(sys.argv[1]) - -print(json.dumps({ - "value_base64": sys.argv[1], - "bytes": list(raw), - "hex": raw.hex(), - "signed_i8": int.from_bytes(raw, "little", signed=True), - "unsigned_u8": int.from_bytes(raw, "little", signed=False), -})) -PY -``` - -Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. - -Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. - -Переиспользуемый рецепт здесь короткий: - -- `view_state` возвращает сырые байты в base64 -- вы декодируете эти байты по известной схеме хранения контракта -- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема - -3. Теперь спросите контракт более привычным способом и сравните. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$CONTRACT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_num", - args_base64: "e30=", - finality: "final" - } +MIN_DEPOSIT="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_bounds",args_base64:"e30=",finality:"final"} }')" \ - | tee /tmp/counter-call-function.json >/dev/null - -jq '{ - block_height: .result.block_height, - view_method_value: (.result.result | implode | fromjson) -}' /tmp/counter-call-function.json -``` - -4. Сравните оба ответа напрямую. - -```bash -RAW_STATE_NUMBER="$( - python3 - "$RAW_VALUE_BASE64" <<'PY' -import base64 -import sys - -raw = base64.b64decode(sys.argv[1]) -print(int.from_bytes(raw, "little", signed=True)) -PY -)" - -VIEW_METHOD_NUMBER="$( - jq -r '.result.result | implode | fromjson' /tmp/counter-call-function.json -)" - -jq -n \ - --argjson raw_state "$RAW_STATE_NUMBER" \ - --argjson view_method "$VIEW_METHOD_NUMBER" '{ - raw_state: $raw_state, - view_method: $view_method, - agrees_now: ($raw_state == $view_method) - }' -``` - -Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: - -- `view_state` ответил на вопрос, прочитав storage напрямую -- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта - -**Когда переходить дальше** - -Используйте `view_state`, когда настоящий вопрос относится к точному storage, отсутствующему view-методу или проверке известного семейства ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](/fastdata/kv). - -## Трассировка чанков и шардов - -Начинайте отсюда, когда вопрос уже не просто «транзакция прошла или нет?», а «какой именно чанк на шарде исполнил каждый шаг работы?» - -### Проследить, как сгенерированная `Transfer`-receipt переходит из одного чанка на шарде в другой - -Нужен cross-shard handoff, а не только исходная транзакция? Смотрите чанки. - -Зафиксированный mainnet-пример: - -- транзакции `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` от `7419369993.tg` к `game.hot.tg` с вызовом `l2_claim` -- исходному чанку `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` на шарде `11` в блоке `194623170` -- чанку первой receipt `FJWpAYzVXbZwqJUbGXELTnnBBkdvc6W8vWkwuUA3Zwz9` на шарде `11` в блоке `194623171` -- сгенерированной `Transfer`-receipt `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` -- конечному чанку `EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU` на шарде `6` в блоке `194623172` - -
-
- Ход -

Сначала восстановите receipt-цепочку, потом напрямую посмотрите на сгенерированную receipt, а затем привяжите каждый шаг к тому чанку на шарде, который действительно нёс эту работу.

-
-
-

01RPC EXPERIMENTAL_tx_status быстро показывает граф receipts и в какие следующие блоки перешла работа.

-

02RPC EXPERIMENTAL_receipt позволяет посмотреть на тело сгенерированной receipt напрямую, а не выводить его только из логов.

-

03RPC chunk по блоку и шарду или по хешу чанка доказывает, какая именно единица исполнения на шарде нёсла каждый шаг.

-
-
+ | jq -r '.result.result | implode | fromjson | .min')" -Оба experimental-метода здесь очень уместны: `EXPERIMENTAL_tx_status` быстро находит граф receipts, а `EXPERIMENTAL_receipt` показывает тело сгенерированной receipt ещё до того, как вы привяжете её обратно к чанкам. - -```mermaid -flowchart LR - A["Tx 8xrc...
блок 194623170
чанк Bfyd...
шард 11"] --> B["Receipt AFC2...
блок 194623171
чанк FJWp...
шард 11
логи ft_mint"] - B --> C["Сгенерированная receipt TtRn...
Transfer 1800930478788300000000 yoctoNEAR"] - C --> D["Чанк EPau...
блок 194623172
шард 6
receipt исполняется"] +jq -n --argjson registered "$REGISTERED" --arg min "$MIN_DEPOSIT" '{ + registered: $registered, + min_storage_deposit_yocto: $min +}' ``` -**Ход** - -- Сначала восстанавливаете receipt-цепочку из транзакции. -- Напрямую смотрите на тело сгенерированной `Transfer`-receipt. -- Используете координаты блока и шарда там, где они уже известны. -- Используете хеш чанка там, где другой инструмент уже выдал точный конечный чанк. - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_HASH=8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP -export SIGNER_ACCOUNT_ID=7419369993.tg -export ORIGIN_BLOCK_HEIGHT=194623170 -export ORIGIN_SHARD_ID=11 -export RECEIPT_BLOCK_HEIGHT=194623171 -export RECEIPT_SHARD_ID=11 -export GENERATED_RECEIPT_ID=TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4 -export DESTINATION_CHUNK_HASH=EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU -``` - -1. Начните с `EXPERIMENTAL_tx_status`, чтобы сначала увидеть граф receipts, а уже потом думать о чанках. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: [$tx_hash, $signer_account_id] - }')" \ - | tee /tmp/chunk-trace-status.json >/dev/null - -jq '{ - final_execution_status: .result.final_execution_status, - transaction_handoff: .result.transaction_outcome.outcome.status, - receipts: ( - .result.receipts_outcome - | map({ - receipt_id: .id, - executor_id: .outcome.executor_id, - block_hash, - status: .outcome.status - }) - ) -}' /tmp/chunk-trace-status.json -``` +Для зафиксированного testnet-контракта `storage_balance_of({account_id: "mike.testnet"})` возвращает `null` (не зарегистрирован), а `storage_balance_bounds` возвращает `{min: "1250000000000000000000", max: "1250000000000000000000"}` — плоскую комиссию регистрации 0.00125 NEAR. Это собственный ответ контракта, и большего на read-стороне до записи не нужно. -На что смотреть: +Write-сторона — это две подписанных function call (near-api-js `transactions.functionCall` или любая NEAR-библиотека подписи работает одинаково): -- подписанная транзакция передаёт работу в receipt `AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y` -- позже в том же графе receipts исполняется `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` для `7419369993.tg` -- уже одного tx status достаточно, чтобы увидеть: настоящая работа продолжилась после исходной подписанной транзакции +- `storage_deposit({account_id: "", registration_only: true})` с депозитом `` yocto и 100 Tgas — пропустите, если `registered: true`. +- `ft_transfer({receiver_id: "", amount: "", memo: "..."})` с депозитом 1 yocto (требует NEP-141) и 100 Tgas. -2. Посмотрите на сгенерированную receipt напрямую, чтобы доказать, что это действительно `Transfer`-receipt. +Отправьте каждую подписанную транзакцию через [`send_tx`](/rpc/transaction/send-tx) с `wait_until: "FINAL"`. После этого подтвердите через собственный view-метод контракта — индексированная история не нужна, чтобы доказать, что перевод закрепился: ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$GENERATED_RECEIPT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_receipt", - params: { - receipt_id: $receipt_id - } +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"ft_balance_of",args_base64:$args,finality:"final"} }')" \ - | tee /tmp/chunk-trace-receipt.json >/dev/null - -jq '{ - predecessor_id: .result.predecessor_id, - receiver_id: .result.receiver_id, - signer_id: .result.receipt.Action.signer_id, - signer_public_key: .result.receipt.Action.signer_public_key, - actions: .result.receipt.Action.actions -}' /tmp/chunk-trace-receipt.json + | jq '{receiver_balance: (.result.result | implode | fromjson)}' ``` -Именно здесь история по шардам становится конкретной: эта цепочка исполнения контракта сгенерировала `Transfer` action receipt от `system` к `7419369993.tg` с депозитом `1800930478788300000000`. +## Чтение контрактов и сырой state -3. Используйте `chunk` по блоку и шарду, чтобы найти исходную подписанную транзакцию на шарде `11`. +### Как прочитать сырое storage контракта напрямую? -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --argjson block_id "$ORIGIN_BLOCK_HEIGHT" \ - --argjson shard_id "$ORIGIN_SHARD_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - block_id: $block_id, - shard_id: $shard_id - } - }')" \ - | jq --arg tx_hash "$TX_HASH" '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created - }, - matching_transaction: ( - .result.transactions[] - | select(.hash == $tx_hash) - | { - hash, - signer_id, - receiver_id - } - ) - }' -``` - -Это самый чистый use case для `chunk` по блоку и шарду: координаты уже известны, а вам нужна точная единица исполнения на шарде, которая несла исходную подписанную транзакцию. - -4. Оставайтесь на том же маршруте и посмотрите, как первая receipt исполняется в следующем блоке на том же шарде. +Два RPC-метода отвечают на один и тот же вопрос о counter с разных слоёв: `view_state` достаёт сырые байты trie без запуска кода, а `call_function` запускает собственный view-метод контракта. Когда они совпадают, вы доказали, что view-метод контракта соответствует его сохранённому состоянию. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --argjson block_id "$RECEIPT_BLOCK_HEIGHT" \ - --argjson shard_id "$RECEIPT_SHARD_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - block_id: $block_id, - shard_id: $shard_id - } - }')" \ - | jq '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created, - tx_root: .result.header.tx_root, - gas_used: .result.header.gas_used - }, - tx_count: (.result.transactions | length), - receipt_count: (.result.receipts | length), - matching_receipt: ( - .result.receipts[] - | select(.receipt_id == "AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y") - | { - receipt_id, - predecessor_id, - receiver_id - } - ) - }' -``` - -Вот здесь chunks наконец становятся естественными: +RPC_URL=https://rpc.testnet.fastnear.com +CONTRACT_ID=counter.near-examples.testnet -- у чанка `tx_root = 11111111111111111111111111111111` -- `tx_count` равен `0` -- но шард всё равно жжёт gas и исполняет receipt `AFC2...` - -То есть этот шард реально сделал работу в этом блоке, хотя новая подписанная транзакция прямо в самом чанке не появилась. +RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_state",account_id:$contract,prefix_base64:"U1RBVEU=",finality:"final"} + }')" \ + | jq -r '.result.values[0].value')" -5. Переключайтесь на `chunk` по хешу, когда другой инструмент уже выдал точный конечный чанк. +RAW_I8="$(python3 -c "import base64,sys;print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg chunk_id "$DESTINATION_CHUNK_HASH" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - chunk_id: $chunk_id - } +METHOD_VALUE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"get_num",args_base64:"e30=",finality:"final"} }')" \ - | jq --arg receipt_id "$GENERATED_RECEIPT_ID" '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created, - tx_root: .result.header.tx_root, - gas_used: .result.header.gas_used - }, - tx_count: (.result.transactions | length), - receipt_count: (.result.receipts | length), - matching_receipt: ( - .result.receipts[] - | select(.receipt_id == $receipt_id) - | { - receipt_id, - predecessor_id, - receiver_id - } - ) - }' -``` - -Это и подтверждает cross-shard hop: + | jq -r '.result.result | implode | fromjson')" -- сгенерированная `Transfer`-receipt исполняется в чанке `EPau...` -- этот чанк живёт на шарде `6`, а не на шарде `11` -- подписанная транзакция стартовала на одном шарде, а следующая receipt завершилась уже на другом +jq -n --arg raw_b64 "$RAW_B64" --argjson raw_i8 "$RAW_I8" --argjson method "$METHOD_VALUE" '{ + raw_state_b64: $raw_b64, + raw_state_decoded: $raw_i8, + view_method_value: $method, + agree: ($raw_i8 == $method) +}' +``` -**Когда переходить дальше** +Для живого counter `view_state` по ключу `STATE` (base64 `U1RBVEU=`) возвращает `"CQ=="` — один байт `0x09`, декодируется как signed i8 в `9`; `get_num` тоже возвращает `9`. Они совпадают, потому что контракт хранит `val: i8` по этому ключу. `signed=True` важен: отрицательный counter выглядел бы как `"/w=="` (байт `0xff` → i8 `-1`, а не u8 `255`). -Используйте [Chunk by Block and Shard](/rpc/protocol/chunk-by-block-shard), когда вы знаете координаты блока и шарда и хотите буквально спросить: «что этот шард исполнил в этом блоке?» Используйте [Chunk by Hash](/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. Используйте [EXPERIMENTAL_tx_status](/rpc/transaction/experimental-tx-status) и [EXPERIMENTAL_receipt](/rpc/transaction/experimental-receipt), когда настоящий вопрос относится к трассировке на уровне receipts. Если ещё нужны state changes и produced receipts, расширяйтесь в [Block Shard](/neardata/block-shard). +`view_state` — правильный инструмент, когда у контракта нет view-метода для нужных данных, когда нужно сверить view-метод с реальным storage или когда нужна семья ключей, которую контракт не раскрывает публично. Для всего остального `call_function` требует меньше церемоний. Если следующий вопрос становится историческим, а не текущим, расширяйте поверхность до [KV FastData API](/fastdata/kv). -## Точные чтения NEAR Social и BOS +## NEAR Social и точные чтения BOS -Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. +Оставайтесь на точных чтениях SocialDB и on-chain-проверках готовности — пока вопрос не станет историческим. ### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? -Нужно понять «готово / не готово» до окна подписи? Проверьте storage и права до отправки. - -
-
- Ход -

Спросите у social.near ровно о двух вещах, которые важны до подписи.

-
-
-

01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию.

-

02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near.

-

03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer.

-
-
- -Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: - -- есть ли у целевого аккаунта storage на `social.near`? -- если есть, осталось ли там ещё место? -- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Ход** - -- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. -- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. -- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. -- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». +`social.near` знает две вещи, о которых UI кошелька может только догадываться: сколько storage осталось у каждого аккаунта и разрешена ли делегированному signer запись под этим аккаунтом. Два view-вызова сворачивают вопрос готовности к одному boolean. ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mike.near -export SIGNER_ACCOUNT_ID=mike.near -``` +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near # account you're writing under +SIGNER_ACCOUNT_ID=mike.near # account signing the transaction -1. Сначала проверьте сам аккаунт signer. +STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } +STORAGE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$STORAGE_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"get_account_storage",args_base64:$args,finality:"final"} }')" \ - | tee /tmp/social-publish-signer.json >/dev/null - -jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - signer_account_id: $signer_account_id, - amount: .result.amount, - locked: .result.locked, - storage_usage: .result.storage_usage -}' /tmp/social-publish-signer.json -``` - -Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. - -2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. - -```bash -SOCIAL_STORAGE_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_account_storage", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-account-storage.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - storage: (.result.result | implode | fromjson), - storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) -}' /tmp/social-account-storage.json -``` + | jq '.result.result | implode | fromjson')" -Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. - -3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. - -```bash if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - jq -n --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - signer_matches_target: true, - permission_granted: true, - reason: "owner write" - }' + PERMISSION=true else - WRITE_PERMISSION_ARGS_BASE64="$( - jq -nc \ - --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ - --arg key "$ACCOUNT_ID" '{ - predecessor_id: $predecessor_id, - key: $key - }' | base64 | tr -d '\n' - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - signer_matches_target: false, - permission_granted: (.result.result | implode | fromjson) - }' -fi -``` - -4. Сведите проверку storage и разрешения в один читаемый итог. - -```bash -AVAILABLE_BYTES="$( - jq -r ' - .result.result - | if length == 0 then "0" - else (implode | fromjson | .available_bytes // 0 | tostring) - end - ' /tmp/social-account-storage.json -)" - -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - PERMISSION_GRANTED=true -else - PERMISSION_GRANTED="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq -r '.result.result | implode | fromjson' - )" + PERM_ARGS_B64="$(jq -nc --arg pred "$SIGNER_ACCOUNT_ID" --arg key "$ACCOUNT_ID" '{predecessor_id:$pred,key:$key}' | base64 | tr -d '\n')" + PERMISSION="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$PERM_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"is_write_permission_granted",args_base64:$args,finality:"final"} + }')" \ + | jq '.result.result | implode | fromjson')" fi -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ - --argjson available_bytes "$AVAILABLE_BYTES" \ - --argjson permission_granted "$PERMISSION_GRANTED" '{ +jq -n --argjson storage "$STORAGE" --argjson permission "$PERMISSION" \ + --arg account_id "$ACCOUNT_ID" --arg signer "$SIGNER_ACCOUNT_ID" '{ account_id: $account_id, - signer_account_id: $signer_account_id, - storage_ready: ($available_bytes > 0), - permission_ready: $permission_granted, - ready_to_publish_now: (($available_bytes > 0) and $permission_granted) + signer_account_id: $signer, + storage: $storage, + permission_granted: $permission, + ready_to_publish: (($storage.available_bytes // 0) > 0 and $permission) }' ``` -Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. +Для `mike.near`, подписывающего под собой, это возвращает `storage: {used_bytes: 139803, available_bytes: 83891}`, `permission_granted: true` (владельческая запись) и `ready_to_publish: true`. Если `storage` приходит как `null` или `available_bytes: 0`, аккаунту нужен `storage_deposit` на `social.near`, прежде чем новая запись сможет закрепиться. Если signer отличается от цели, ветка permission спрашивает `is_write_permission_granted({predecessor_id, key})` — тот же on-chain-ответ, который dapp видит, прежде чем писать от имени пользователя. Полную поверхность контракта см. в [SocialDB API](https://github.com/NearSocial/social-db#api). -**Когда переходить дальше** - -Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. - -### Что прямо сейчас содержит `mob.near/widget/Profile`? - -Нужен живой исходник виджета и последняя запись этого ключа? Оставайтесь на точных RPC-чтениях. - -
-
- Ход -

Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой.

-
-
-

01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*.

-

02RPC call_function get читает точный исходник widget/Profile.

-

03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples.

-
-
- -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Ход** - -- Спрашиваете у `social.near` каталог виджетов под `mob.near`. -- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. -- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. -- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](/tx/examples). - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mob.near -export WIDGET_NAME=Profile -``` - -1. Получите каталог виджетов и сохраните высоты блоков последней записи. - -```bash -WIDGET_KEYS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/widget/*")], - options: {return_type: "BlockHeight"} - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "keys", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-widget-keys.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | .[$account_id].widget - | to_entries - | sort_by(.value * -1) - | map({ - widget_name: .key, - last_write_block: .value - }) - | .[0:20] -' /tmp/social-widget-keys.json -``` - -2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. - -```bash -WIDGET_GET_ARGS_BASE64="$( - jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-widget-source.json >/dev/null - -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:25] - | join("\n") - ' /tmp/social-widget-source.json -``` +### Что `mob.near/widget/Profile` содержит прямо сейчас? -3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. +SocialDB хранит BOS-виджеты как ключи `/widget/` на `social.near`. Один `keys` с типом возврата `BlockHeight` возвращает каталог плюс якоря последней записи по каждому виджету; один `get` возвращает точный исходник. ```bash -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - ' /tmp/social-widget-keys.json \ - | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mob.near +WIDGET_NAME=Profile + +KEYS_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} +}' | base64 | tr -d '\n')" + +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$KEYS_ARGS" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"keys",args_base64:$args,finality:"final"} + }')" \ + | jq --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" ' + .result.result | implode | fromjson | .[$account_id].widget as $map + | { + total_widgets: ($map | length), + most_recently_written: ($map | to_entries | sort_by(-.value) | .[0:5] | map({widget: .key, last_write_block: .value})), + target_last_write_block: $map[$widget] + }' + +GET_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget)] +}' | base64 | tr -d '\n')" + +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$GET_ARGS" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"get",args_base64:$args,finality:"final"} + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" ' + .result.result | implode | fromjson | .[$account_id].widget[$widget] | split("\n")[0:20] | join("\n")' ``` -На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. - -**Когда переходить дальше** - -Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. - -## Частые задачи - -### Проверить точное состояние аккаунта или ключа доступа - -**Начните здесь** - -- [View Account](/rpc/account/view-account) для точных полей аккаунта. -- [View Access Key](/rpc/account/view-access-key) или [View Access Key List](/rpc/account/view-access-key-list) для проверки ключей. - -**Следующая страница при необходимости** - -- [FastNear API full account view](/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. -- [Transactions API account history](/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» - -**Остановитесь, когда** - -- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. - -**Переходите дальше, когда** - -- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. -- Пользователя интересует не текущее состояние, а недавняя история активности. - -### Трассировать исполнение на уровне шарда через чанки - -**Начните здесь** - -- Начните с примера выше, если настоящий вопрос звучит как «какой чанк или шард вообще исполнил эту receipt?» -- [Chunk by Block and Shard](/rpc/protocol/chunk-by-block-shard), когда координаты блока и шарда уже известны. -- [Chunk by Hash](/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. - -**Следующая страница при необходимости** - -- [Experimental Receipt](/rpc/transaction/experimental-receipt), если нужно само тело сгенерированной receipt. -- [Block Shard](/neardata/block-shard), если chunk payload уже недостаточен и ещё нужны state changes или produced receipts. -- [Transactions Examples](/tx/examples), если вопрос превращается в более широкое async- или callback-расследование. - -**Остановитесь, когда** - -- Уже можно назвать, какой именно чанк и какой шард несли ту работу, которая была важна. - -**Переходите дальше, когда** - -- Пользователю нужны уже не детали исполнения на уровне шарда, а читаемая история транзакции. Тогда переходите к [Transactions API](/tx). - -### Проверить один точный блок или снимок состояния протокола - -**Начните здесь** - -- [Block by ID](/rpc/block/block-by-id) или [Block by Height](/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. -- [Latest Block](/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» -- [Status](/rpc/protocol/status), [Health](/rpc/protocol/health) или [Network Info](/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. - -**Следующая страница при необходимости** - -- [Block Effects](/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. -- [Transactions API block history](/tx/block) или [Transactions API block range](/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» - -**Остановитесь, когда** - -- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. - -**Переходите дальше, когда** - -- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](/neardata). -- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](/tx). - -### Что этот контракт возвращает прямо сейчас? - -**Начните здесь** - -- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» -- [Call Function](/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. -- [View State](/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. -- [View Code](/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» - -**Следующая страница при необходимости** - -- [FastNear API](/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. -- [KV FastData API](/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» - -**Остановитесь, когда** - -- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. - -**Переходите дальше, когда** - -- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. -- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» - -### Отправить транзакцию и подтвердить результат - -**Начните здесь** - -- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. -- [Send Transaction](/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. -- [Broadcast Transaction Async](/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. -- [Transaction Status](/rpc/transaction/tx-status), чтобы подтвердить финальный результат. - -**Следующая страница при необходимости** - -- [Transactions by Hash](/tx/transactions), если после отправки нужна более читаемая история по транзакции. -- [Receipt Lookup](/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. -- [Transactions Examples](/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» - -**Остановитесь, когда** - -- У вас уже есть результат отправки и нужный финальный статус. - -**Переходите дальше, когда** - -- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. -- Нужен уже не единичный статус, а более широкий сценарий расследования. +Для `mob.near` каталог показывает 264 виджета; `Profile` последний раз записывался в блоке `86494825` — годами ранее, стабильно с тех пор — и исходник начинается с `const accountId = props.accountId ?? context.accountId;`. Тип возврата `BlockHeight` ничего не стоит дополнительно и превращает листинг ключей в дешёвую проверку актуальности. Сохраните блок последней записи, если позже захотите доказать, *какая транзакция* записала именно эту версию — передайте его в [Расширенный поиск записи SocialDB](/tx/socialdb-proofs). ## Частые ошибки -- Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. -- Забывать переключаться с обычного RPC на архивный RPC для старого состояния. -- Воспринимать браузерную аутентификацию в интерфейсе документации как продовый паттерн для бэкенда. -- Продолжать пользоваться низкоуровневыми статусами транзакций, когда вопрос уже превратился в расследование или исторический разбор. +- Начинать в RPC, когда пользователю нужна сводка по активам или индексированная история. +- Забывать переключаться с обычного RPC на archival RPC для более старого state. +- Считать browser auth в UI документации продовым backend-паттерном. +- Оставаться в низкоуровневых вызовах статуса транзакции, когда вопрос уже стал forensic или историческим. -## Полезные связанные страницы +## Связанные страницы - [RPC Reference](/rpc) - [Auth & Access](/auth) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx index 92c8c57..c740818 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/examples.mdx @@ -1,43 +1,20 @@ --- sidebar_label: Examples slug: /snapshots/examples -title: "Примеры Snapshot" -description: "Практические примеры восстановления для выбора правильного пути через FastNear snapshots." +title: "Примеры snapshot" +description: "Практические примеры восстановления узла: optimized, standard и archival." displayed_sidebar: snapshotsSidebars page_actions: - markdown --- -## Быстрый старт +## Пути восстановления mainnet -Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. +Выберите один класс — optimized `fast-rpc`, standard RPC или archival — и выполняйте только команды этого пути. Смешивание классов приводит к несогласованным данным узла. -Эти helper-скрипты поддерживаются FastNear и оптимизированы под скорость восстановления. Если в вашей среде нужен review изменений, сначала скачайте скрипт, проверьте его и только потом запускайте, вместо прямого piping в `bash`. +FastNear поддерживает эти скрипты ради скорости восстановления. Если в вашей среде требуется review изменений, скачайте скрипт и проверьте его перед запуском (вместо прямой передачи через pipe в `bash`). -```bash -DATA_PATH=~/.near/data - -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ - | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash -``` - -## Пример - -### Выбрать и выполнить правильный сценарий восстановления mainnet - -
-
- Ход -

Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него.

-
-
-

01Сначала решите, нужен ли вам optimized fast-rpc, обычный RPC или архивный режим.

-

02Если нужен архив, сначала зафиксируйте одну точную высоту snapshot-блока и дальше переиспользуйте только её.

-

03Выполняйте только команды выбранного пути и не смешивайте optimized, standard и archival шаги в одном сценарии.

-
-
- -### Минимальная команда для optimized mainnet `fast-rpc` +### Optimized mainnet `fast-rpc` ```bash DATA_PATH=~/.near/data @@ -46,7 +23,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash ``` -### Минимальная команда для стандартного mainnet RPC +### Standard mainnet RPC ```bash DATA_PATH=~/.near/data @@ -55,14 +32,9 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet bash ``` -### Shell-сценарий архивного восстановления mainnet -Для архивного восстановления сначала получите одну высоту snapshot и переиспользуйте её для hot- и cold-data. - -**Ход** +### Archival mainnet -- Один раз получаете последнюю высоту архивного снапшота mainnet. -- Сохраняете её в `LATEST`. -- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. +Для archival нужны две загрузки из *одного и того же* среза снапшота. Зафиксируйте одно значение `LATEST` и переиспользуйте его и для hot-, и для cold-data — смешивание высот даёт внутренне несогласованный набор данных и удивляет nearcore на этапе настройки. ```bash HOT_DATA_PATH=~/.near/data @@ -78,21 +50,16 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_TYPE=cold-data DATA_PATH="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash ``` -**Когда переходить дальше** - -Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. - - ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. -- Выбирать архивное восстановление, когда достаточно обычного или optimized RPC. -- Забывать про разделение hot/cold-данных для архивного режима. +- Выбирать archival-восстановление, когда достаточно standard или optimized RPC. +- Забывать про разделение hot/cold-хранилища для archival-данных. - Переходить к командам до выбора сети и цели узла. -## Полезные связанные страницы +## Связанные страницы -- [Обзор снапшотов](/snapshots) +- [Обзор snapshot](/snapshots) - [Снапшоты mainnet](/snapshots/mainnet) - [Снапшоты testnet](/snapshots/testnet) - [RPC Reference](/rpc) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md index c003125..9d368a3 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -2,7 +2,7 @@ sidebar_label: Examples slug: /transfers/examples title: "Примеры Transfers API" -description: "Практические примеры для поиска переводов, пагинации через resume_token и перехода к истории транзакций." +description: "Практические примеры: фильтрация ленты переводов, пагинация и переход к истории транзакций." displayed_sidebar: transfersApiSidebar page_actions: - markdown @@ -12,147 +12,44 @@ page_actions: ### Отфильтровать и листать ленту переводов одного аккаунта -
-
- Ход -

Сначала соберите саму ленту аккаунта, а `receipt` поднимайте только тогда, когда одна строка действительно требует истории исполнения.

-
-
-

01POST /v0/transfers даёт первую страницу отфильтрованной ленты одного аккаунта.

-

02jq поднимает сами строки плюс resume_token, чтобы вы могли продолжать листать ту же ленту.

-

03POST /v0/receipt — это уже необязательный следующий шаг, если одной строке нужна её история исполнения.

-
-
- -**Сеть** - -- только mainnet - -**Ход** - -- Забираете первую страницу одной отфильтрованной ленты переводов для выбранного аккаунта. -- Используете сами параметры ленты как главный учебный материал: `account_id`, `direction`, `asset_id`, `min_amount`, `desc` и `limit`. -- Сначала смотрите на строки ответа и `resume_token`, а не прыгаете сразу в историю исполнения. -- Только если какая-то строка действительно требует дополнительной истории, переиспользуете её `receipt_id` в Transactions API. +`/v0/transfers` возвращает отфильтрованную ленту плюс `resume_token`, который вы переиспользуете *без изменения фильтров*, чтобы продолжать листать. В каждой строке уже есть `human_amount`, `usd_amount`, `transaction_id` и `receipt_id` — большинство audit-вопросов закрываются без второго запроса. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -ASSET_ID=native:near -MIN_AMOUNT=1000000000000000000000000 +ACCOUNT_ID=root.near -curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ +FEED="$(curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - min_amount: $min_amount, - desc: true, - limit: 10 - }')" \ - | tee /tmp/transfers-feed.json >/dev/null - -jq '{ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + direction: "receiver", + asset_id: "native:near", + min_amount: "1000000000000000000000000", + desc: true, + limit: 10 + }')")" + +echo "$FEED" | jq '{ resume_token, - transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - asset_id, - amount, - human_amount, - usd_amount, - other_account_id, - block_height - } - ] -}' /tmp/transfers-feed.json + transfers: [.transfers[] | {block_height, amount, human_amount, usd_amount, other_account_id, transaction_id, receipt_id}] +}' ``` -Необязательный следующий шаг: если одной строке всё-таки нужна её точка исполнения, поднимите её `receipt_id` и один раз перейдите в Transactions API. +Для зафиксированного аккаунта это возвращает недавние входящие native-NEAR переводы не меньше 1 NEAR — в примерных строках видны native-переводы с `escrow.ai.near` и уже посчитанным USD. Чтобы получить следующую страницу, отправьте то же тело с верхнеуровневым `resume_token: ""`; изменение любого другого фильтра делает токен недействительным. + +Когда одной строке нужна точка исполнения, возьмите её `receipt_id` и сразу обратитесь к `/v0/receipt`: ```bash -RECEIPT_ID="$(jq -r '.transfers[0].receipt_id' /tmp/transfers-feed.json)" +RECEIPT_ID="$(echo "$FEED" | jq -r '.transfers[0].receipt_id')" curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' + | jq '.receipt | {receipt_id, transaction_hash, receiver_id, predecessor_id, tx_block_height, is_success}' ``` -**Когда переходить дальше** - -Запрос переводов напрямую отвечает на первый вопрос: как сейчас выглядит отфильтрованная лента этого аккаунта и как её продолжать без потери места? Только после того как сама лента подскажет, какая строка действительно важна, имеет смысл переходить по `receipt_id` и забирать историю исполнения из `/tx`. - -## Частые задачи - -### Отфильтровать ленту переводов одного аккаунта - -**Начните здесь** - -- [Запрос переводов](/transfers/query) с аккаунтом и самым узким стабильным набором фильтров для ленты: направление, актив, сумма и порядок. - -**Следующая страница при необходимости** - -- Уточните те же фильтры по активу или сумме, если в первой странице всё ещё слишком много лишних строк. - -**Остановитесь, когда** - -- Уже можно объяснить, как выглядит эта отфильтрованная лента и как листать её дальше. - -**Переходите дальше, когда** - -- Одна конкретная строка уже требует истории исполнения или следа по receipt. Переходите к [Transactions API](/tx). - -### Листать ленту переводов дальше и не потерять своё место - -**Начните здесь** - -- [Запрос переводов](/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. - -**Следующая страница при необходимости** - -- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. -- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. - -**Остановитесь, когда** - -- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. - -**Переходите дальше, когда** - -- Пользователь просит метаданные транзакции сверх самих переводов. -- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](/api). - -### Перейти от истории переводов к полному расследованию транзакции - -**Начните здесь** - -- [Запрос переводов](/transfers/query), чтобы выделить конкретные интересующие переводы. - -**Следующая страница при необходимости** - -- [История аккаунта в Transactions API](/tx/account), если нужна окружающая история исполнения для того же аккаунта. -- [Transactions by Hash](/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. - -**Остановитесь, когда** - -- Уже определено правильное событие перевода и понятно, какой API открывать следующим. - -**Переходите дальше, когда** - -- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](/tx), затем к [RPC Reference](/rpc), если потребуется. +Это тот же переход, что описан в [Превратить один неказистый receipt ID из логов в человекочитаемую историю](/tx/examples#превратить-один-неказистый-receipt-id-из-логов-в-человекочитаемую-историю) — один запрос возвращает и квитанцию, и её родительскую транзакцию целиком. ## Частые ошибки @@ -160,7 +57,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - Считать историю переводов полной историей исполнения. - Переиспользовать `resume_token` с другими фильтрами. -## Полезные связанные страницы +## Связанные страницы - [Transfers API](/transfers) - [Transactions API](/tx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index b2a203f..360a496 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -1,16 +1,18 @@ --- sidebar_label: Examples slug: /tx/examples -title: "Примеры Transactions API" -description: "Практические расследования транзакций для типовых задач разработчика." +title: "Примеры Transactions" +description: "Практические расследования транзакций: хеши, receipts, async-сбои и callback." displayed_sidebar: transactionsApiSidebar page_actions: - markdown --- -## Быстрый старт +## Начните здесь -Начните с одного tx hash и сначала получите самый короткий читаемый ответ. +### У меня один хеш транзакции. Что произошло? + +Вставьте хеш в `POST /v0/transactions` — один ответ обычно содержит всю историю. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -20,960 +22,211 @@ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height, + actions: (.transactions[0].transaction.actions | map(if type == "string" then . else keys[0] end)), + first_receipt_id: .transactions[0].execution_outcome.outcome.status.SuccessReceiptId, receipt_count: (.transactions[0].receipts | length) }' ``` -## С чего начать - -### У меня есть один хеш транзакции. Что вообще произошло? - -
-
- Ход -

Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно.

-
-
-

01POST /v0/transactions даёт signer, receiver, типы действий, высоту блока и первую receipt-точку передачи.

-

02RPC EXPERIMENTAL_tx_status нужен только для точной протокольной семантики успеха.

-

03POST /v0/receipt имеет смысл только тогда, когда именно первая receipt становится новой опорной точкой.

-
-
- -Зафиксированный пример: - -- хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- высота включающего блока: `194263342` -- ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` - -Короткий ответ: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. - -```mermaid -flowchart LR - H["Один tx hash
AdgNifPY..."] --> T["Получаем транзакцию"] - T --> A["Читаем signer, receiver, actions, block"] - A --> S["Короткая человеческая история"] - T -. "если потом понадобится" .-> R["Первая receipt
5GhZcpfK..."] -``` +Для зафиксированного хеша `mike.near` отправил один `Transfer` на `global-counter.mike.near` в блоке `194263342`, с передачей в receipt `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq`. Если `receipt_count > 1` или следующий вопрос касается поведения на уровне receipt, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие) или [`POST /v0/receipt`](/tx/receipt). -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Читаемая история транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Стартуем с хеша транзакции и печатаем signer, receiver, включающий блок, список действий и handoff в первую receipt | Даёт самый быстрый читаемый ответ на вопрос «что вообще сделала эта транзакция?» | -| Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | -| Переход к receipt | Transactions API [`POST /v0/receipt`](/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | +### Какой receipt испустил этот лог или событие? -#### Shell-сценарий: от хеша транзакции к человеческой истории - -**Ход** - -- Получаете транзакцию по хешу и печатаете её основные поля. -- Подтверждаете финальный статус только если нужны точные RPC-семантики. -- Сохраняете первую receipt только как необязательный следующий шаг. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp -SIGNER_ACCOUNT_ID=mike.near -``` - -1. Получите транзакцию и распечатайте базовую историю. - -```bash -FIRST_RECEIPT_ID="$( - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/basic-tx-story.json \ - | jq -r '.transactions[0].transaction_outcome.outcome.status.SuccessReceiptId' -)" - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/basic-tx-story.json - -# Ожидаемый список действий: ["Transfer"] -# Ожидаемая первая receipt: 5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq -``` - -2. Если нужны точные RPC-семантики статуса, подтвердите их через `EXPERIMENTAL_tx_status`. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' -``` - -3. Если следующий вопрос уже звучит как «что это была за первая receipt?», один раз перейдите по ней и остановитесь. - -```bash -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$FIRST_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash - }' -``` - -Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. - -**Когда переходить дальше** - -`POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. - -### Какая receipt выдала этот лог или event? - -
-
- Ход -

Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога.

-
-
-

01POST /v0/transactions даёт полный индексированный список receipt для одного tx hash, включая receipt-логи.

-

02jq сужает этот список до receipt, в логах которых встречается нужный вам фрагмент.

-

03Как только совпадение осталось одно, сохраняйте его receipt_id, executor и имя метода как точный ответ.

-
-
- -Для этого зафиксированного mainnet-примера используйте: - -- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` -- фрагмент лога: `Refund` -- ожидаемый matching `receipt_id`: `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` -- ожидаемый executor: `wrap.near` -- ожидаемый метод: `ft_resolve_transfer` - -В этой транзакции есть две logged receipt внутри одной истории: - -- ранний лог `Transfer ...` на receipt с `ft_transfer_call` -- более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` - -```mermaid -flowchart LR - T["Один tx hash
2KhhB1uD..."] --> L["Читаем все receipt-логи"] - L --> X["Ищем фрагмент:
Refund"] - X --> R["Точная receipt
9sLHQpaG..."] - R --> A["Ответ:
wrap.near / ft_resolve_transfer"] -``` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Атрибуция лога | Transactions API [`POST /v0/transactions`](/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | -| Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | - -#### Shell-сценарий атрибуции лога - -**Ход** - -- Один раз получаете транзакцию и сохраняете список её receipt. -- Фильтруете receipt по одному фрагменту лога. -- Останавливаетесь, как только у вас есть один точный `receipt_id`, один executor и одно имя метода. +Выведите список всех receipt транзакции с логами и флагом, содержат ли их логи ваш фрагмент. Совпадение доказывается, а не угадывается: у зафиксированной транзакции один receipt логирует `Transfer`, другой — `Refund`, и только сторона `Refund` переключается в `true`. ```bash TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL LOG_FRAGMENT=Refund -``` - -1. Получите транзакцию и сохраните список receipt. -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/log-attribution-transaction.json >/dev/null + | jq --arg fragment "$LOG_FRAGMENT" ' + [ + .transactions[0].receipts[] + | select((.execution_outcome.outcome.logs | length) > 0) + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0] + | if type == "string" then . else (.FunctionCall.method_name // keys[0]) end), + matches_fragment: any(.execution_outcome.outcome.logs[]?; contains($fragment)), + logs: .execution_outcome.outcome.logs + } + ]' ``` -2. Отфильтруйте список receipt до логов, которые содержат нужный вам фрагмент. +Фрагмент `Refund` атрибутируется receipt `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` на `wrap.near`, метод `ft_resolve_transfer`. Логи receipt живут на receipts, а не на транзакции, поэтому одного прохода достаточно — более глубокая async-трассировка не нужна. -```bash -jq --arg fragment "$LOG_FRAGMENT" '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id - }, - matching_receipts: [ - .transactions[0].receipts[] - | select(any(.execution_outcome.outcome.logs[]?; contains($fragment))) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - block_height: .execution_outcome.block_height, - logs: .execution_outcome.outcome.logs - } - ] -}' /tmp/log-attribution-transaction.json - -# На что смотреть: -# - фрагмент `Refund` совпадает ровно с одной receipt -# - это receipt 9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w -# - receipt исполнилась на wrap.near -# - имя метода — ft_resolve_transfer -``` +### Превратить один неказистый receipt ID из логов в человекочитаемую историю -3. Если хотите увидеть все logged receipt рядом, распечатайте только те receipt, где вообще были логи. - -```bash -jq '{ - logged_receipts: [ - .transactions[0].receipts[] - | select((.execution_outcome.outcome.logs | length) > 0) - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - logs: .execution_outcome.outcome.logs - } - ] -}' /tmp/log-attribution-transaction.json -``` - -Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. - -**Когда переходить дальше** - -Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. - -### Превратить один страшный receipt ID из логов в понятную человеческую историю - -Есть только `receipt_id` из логов или трассы? Сначала разрешите сам receipt, затем восстановите родительскую транзакцию. - -Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. - -
-
- Ход -

Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой.

-
-
-

01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt.

-

02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий.

-

03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола».

-
-
- -Зафиксированный receipt из логов: - -- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` -- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- высота блока транзакции: `194263342` -- высота блока исполнения receipt: `194263343` - -Короткий ответ: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. - -```mermaid -flowchart LR - L["Один страшный receipt ID
5GhZcpfK..."] --> R["Ищем receipt"] - R --> T["Восстанавливаем tx hash
AdgNifPY..."] - T --> S["Читаем действия транзакции"] - S --> H["Человеческая история:
mike.near отправил 5 NEAR в global-counter.mike.near"] -``` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Якорь по квитанции | Transactions API [`POST /v0/receipt`](/tx/receipt) | Сначала ищем ID квитанции и печатаем аккаунты, блок исполнения, флаг успеха и связанный хеш транзакции | Даёт самый короткий путь от сырого receipt ID к пониманию, что вообще за объект перед вами | -| История транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | -| Каноническое продолжение | RPC [`tx`](/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | - -#### Shell-сценарий: от страшного receipt ID к человеческой истории +`POST /v0/receipt` возвращает запись receipt **и** его полную родительскую транзакцию в одном ответе, поэтому единственного запроса хватает на всю историю — дополнительный `/v0/transactions` не нужен. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' -``` - -1. Разрешите receipt и поймите, что за объект вы смотрите. - -```bash -TX_HASH="$( - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | tee /tmp/receipt-lookup.json \ - | jq -r '.receipt.transaction_hash' -)" - -jq '{ - receipt: { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - receipt_type: .receipt.receipt_type, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash, - tx_block_height: .receipt.tx_block_height - } -}' /tmp/receipt-lookup.json -``` - -2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. +RECEIPT_ID=5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/receipt-parent-transaction.json >/dev/null - -jq '{ - transaction: { - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transfer_deposit_yocto: ( - .transactions[0].transaction.actions[0].Transfer.deposit // null - ) - }, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/receipt-parent-transaction.json -``` - -3. Сведите это к одному человеческому предложению. - -```bash -jq -r ' - def zeros($n): - reduce range(0; $n) as $i (""; . + "0"); - def yocto_to_near($yocto): - ($yocto | tostring) as $digits - | if ($digits | length) <= 24 then - ("0." + zeros(24 - ($digits | length)) + $digits) - else - ($digits[0:(($digits | length) - 24)] + "." + $digits[-24:]) - end - | sub("0+$"; "") - | sub("\\.$"; ""); - .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил \(yocto_to_near($tx.transaction.actions[0].Transfer.deposit)) NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." -' /tmp/receipt-parent-transaction.json -``` - -Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. - -В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. - -**Когда переходить дальше** - -`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. - -## Ошибки и async - -Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. - -### Доказать, что одно неудачное действие сорвало весь пакет - -Нужно проверить, закрепились ли ранние действия в неудачном батче? Используйте этот testnet-пример с `CreateAccount -> Transfer -> AddKey -> FunctionCall`. - -В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. - -
-
- Ход -

Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов.

-
-
-

01POST /v0/transactions показывает упорядоченный пакет ровно в том виде, в каком его подписал signer.

-

02RPC EXPERIMENTAL_tx_status показывает падающий FunctionCall и точную причину отказа на уровне протокола.

-

03RPC view_account по предполагаемому новому аккаунту доказывает, закрепились ли вообще ранние create, fund и add-key действия.

-
-
- -**Официальные ссылки** - -- [Основы транзакций](/transaction-flow/foundations) -- [Исполнение в рантайме](/transaction-flow/runtime-execution) - -Этот зафиксированный сбой был получен в **testnet 18 апреля 2026 года**: - -- хеш транзакции: `CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M` -- аккаунт signer: `temp.mike.testnet` -- целевой новый аккаунт: `rollback-mo4vmkig.temp.mike.testnet` -- высота включающего блока: `246365118` -- хеш включающего блока: `6f5zTKDqQRwrxMywzvxeRvYcCERJmAnatJaqUEtQYUNM` -- порядок действий: `CreateAccount -> Transfer -> AddKey -> FunctionCall` -- упавший метод: `definitely_missing_method` -- RPC-ошибка: `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet` - -```mermaid -flowchart LR - T["Одна подписанная транзакция"] --> A["CreateAccount"] - A --> B["Transfer 0.01 NEAR"] - B --> C["AddKey"] - C --> D["FunctionCall definitely_missing_method()"] - D --> E["Сбой: CodeDoesNotExist"] - E --> R["Весь пакет не закрепился"] - R --> N["Новый аккаунт не появился"] - R --> K["Новый ключ не закрепился"] - R --> F["У получателя нет профинансированного состояния"] + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block: .receipt.block_height, + tx_block: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }, + parent_transaction: { + signer_id: .transaction.transaction.signer_id, + receiver_id: .transaction.transaction.receiver_id, + action_types: (.transaction.transaction.actions | map(if type == "string" then . else keys[0] end)) + } + }' ``` -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Задуманный пакет | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем зафиксированный хеш транзакции и печатаем упорядоченный список действий, получателя и метаданные включающего блока | Показывает, что именно signer пытался сделать, ещё до разговора о том, что закрепилось | -| Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | -| Доказательство по состоянию после исполнения | RPC [`query(view_account)`](/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | - -Индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. +Для зафиксированного receipt это возвращает `Action`-receipt от `mike.near` к `global-counter.mike.near`, который успешно выполнился в блоке `194263343`, через один блок после попадания родительской транзакции `AdgNifPY…`, — один `Transfer` (5 NEAR, в сыром `.transaction.transaction.actions` видимо как `5000000000000000000000000` yocto). Если интересным якорем становится родительская транзакция, хеш у вас уже есть — переиспользуйте его в [У меня один хеш транзакции. Что произошло?](#у-меня-один-хеш-транзакции-что-произошло). -#### Shell-сценарий неудачной транзакции с пакетом действий +## Сбои и async -**Ход** +### Доказать, что один провалившийся action откатил весь batch -- Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. -- Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. -- Через один RPC-запрос к состоянию после исполнения доказываете, что новый аккаунт так и не появился после finality. +Один batch отправил `CreateAccount → Transfer → AddKey → FunctionCall`, и финальный вызов попал в отсутствующий метод. Индексированная запись транзакции уже несёт упорядоченный batch *и* точный сбой на уровне receipt, поэтому одного запроса хватает, чтобы ответить «что пытались и что сломалось»; проверка через `view_account` затем доказывает, что предыдущие actions откатились. ```bash TX_BASE_URL=https://tx.test.fastnear.com RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M -SIGNER_ACCOUNT_ID=temp.mike.testnet NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet -``` - -1. Получите транзакцию и распечатайте задуманный пакет действий. -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/failed-batch-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height, - included_block_hash: .transactions[0].execution_outcome.block_hash - }, - batch: { - action_count: (.transactions[0].transaction.actions | length), - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - final_function_call_method_name: ( - .transactions[0].transaction.actions[3].FunctionCall.method_name - ) - }, - first_receipt_handoff: .transactions[0].transaction_outcome.outcome.status -}' /tmp/failed-batch-transaction.json - -# Ожидаемый порядок действий: -# 1. CreateAccount -# 2. Transfer -# 3. AddKey -# 4. FunctionCall + | jq '{ + action_types: (.transactions[0].transaction.actions | map(if type == "string" then . else keys[0] end)), + final_method: .transactions[0].transaction.actions[3].FunctionCall.method_name, + tx_handoff: .transactions[0].execution_outcome.outcome.status, + receipt_failure: ( + first( + .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) + | .execution_outcome.outcome.status.Failure.ActionError + ) + ) + }' ``` -2. Запросите RPC transaction status и посмотрите точную верхнеуровневую ошибку. +Статус на уровне транзакции — `SuccessReceiptId`: транзакция успешно передала свои batched actions в receipt. Сбой лежит слоем ниже на этом receipt: `index: 3` (именно `FunctionCall`), вид `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet`. `SuccessReceiptId` в tx-outcome означает «handoff прошёл», а не «всё завершилось» — реальная ловушка, если смотреть только на статус уровня транзакции. -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/failed-batch-rpc-status.json >/dev/null - -jq '{ - final_execution_status: .result.final_execution_status, - failed_action_index: .result.status.Failure.ActionError.index, - failure: .result.status.Failure.ActionError.kind.FunctionCallError.CompilationError.CodeDoesNotExist -}' /tmp/failed-batch-rpc-status.json - -# Ожидаемый failed_action_index: 3 -# Ожидаемый failure account_id: rollback-mo4vmkig.temp.mike.testnet -``` - -3. Запросите предполагаемый новый аккаунт после finality и докажите, что его всё ещё нет. +Теперь докажите откат предыдущих actions: спросите аккаунт, который batch *пытался* создать: ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } + jsonrpc: "2.0", id: "fastnear", method: "query", + params: {request_type: "view_account", account_id: $account_id, finality: "final"} }')" \ - | tee /tmp/failed-batch-view-account.json >/dev/null - -jq '{ - error: .error.cause.name, - message: .error.data, - requested_account_id: .error.cause.info.requested_account_id, - proof_block_height: .error.cause.info.block_height -}' /tmp/failed-batch-view-account.json - -# Ожидаемая ошибка: "UNKNOWN_ACCOUNT" -``` - -Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. - -**Когда переходить дальше** - -Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. - -### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? - -Нужно доказать, что позже упал отдельный cross-contract receipt, хотя первый вызов выглядел успешным? Используйте этот зафиксированный testnet-пример. - -Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. - -
-
- Ход -

Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась.

-
-
-

01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже.

-

02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой.

-

03Как только эти два представления сходятся на одном и том же разрезе истории, остановитесь. Этот пример держится за сохранённые исторические свидетельства, а не за живой read состояния роутера.

-
-
- -**Официальные ссылки** - -- [Основы транзакций](/transaction-flow/foundations) -- [Исполнение в рантайме](/transaction-flow/runtime-execution) - -Этот зафиксированный асинхронный сбой был получен в **testnet 18 апреля 2026 года**: - -- хеш транзакции: `AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz` -- аккаунт signer: `temp.mike.testnet` -- первый контракт-получатель: `seq-dr.mike.testnet` -- аккаунт detached-цели: `asyncfail-in2hwikn.temp.mike.testnet` -- блок включения транзакции: `246368568` -- успешная первая receipt: `6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS` в блоке `246368569` -- более поздняя упавшая receipt: `2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es` в блоке `246368570` -- первый метод: `kickoff_append` -- более поздний упавший метод: `append` -- верхнеуровневый RPC `status`: `SuccessValue` - -```mermaid -flowchart LR - T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] - R --> D["Detached cross-contract receipt
append(...)"] - D --> F["Более поздний сбой
CodeDoesNotExist"] - T -. "внешняя транзакция всё равно завершается" .-> X["RPC top-level status
SuccessValue"] + | jq '{error: .error.cause.name, requested_account_id: .error.cause.info.requested_account_id}' ``` -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Каркас транзакции | Transactions API [`POST /v0/transactions`](/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | -| Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | +`UNKNOWN_ACCOUNT` — это и есть доказательство. Если бы `CreateAccount` закрепился, `view_account` вернул бы результат; раз нет — предыдущие `Transfer` и `AddKey` из того же batched-receipt тоже не закрепились. -Успех receipt не транзитивен. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. +### Почему этот вызов контракта выглядел успешным, но потом receipt упал? -#### Shell-сценарий более позднего сбоя receipt - -**Ход** - -- Читаете транзакцию и её таймлайн receipt из индексированного представления. -- Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. -- Останавливаетесь, как только эти два сохранённых представления сходятся на одном и том же разрезе истории. +Одна транзакция может закончиться тем, что внешний handoff рапортует `SuccessReceiptId`, а дочерний receipt при этом тихо падает — это и есть async-модель NEAR, и `/v0/transactions` выдаёт весь timeline за один запрос. ```bash -TX_BASE_URL=https://tx.test.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com -TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz -SIGNER_ACCOUNT_ID=temp.mike.testnet -FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS -FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es -``` - -1. Получите транзакцию и распечатайте таймлайн receipt по порядку блоков. +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/later-receipt-failure-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - tx_handoff: .transactions[0].transaction_outcome.outcome.status - }, - receipts: [ - .transactions[0].receipts[] - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - block_height: .execution_outcome.block_height, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system_transfer"), - status: .execution_outcome.outcome.status - } - ] -}' /tmp/later-receipt-failure-transaction.json - -# На что смотреть: -# - первая receipt контракта на seq-dr.mike.testnet успешно прошла в блоке 246368569 -# - более поздняя receipt append(...) упала в блоке 246368570 -``` - -2. Запросите RPC transaction status и сравните верхнеуровневую историю с более поздней упавшей receipt. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/later-receipt-failure-rpc.json >/dev/null - -jq \ - --arg first_receipt_id "$FIRST_RECEIPT_ID" \ - --arg failed_receipt_id "$FAILED_RECEIPT_ID" '{ - top_level_status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status, - first_contract_receipt: ( - .result.receipts_outcome[] - | select(.id == $first_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - logs: .outcome.logs, - status: .outcome.status - } - ), - later_failed_receipt: ( - .result.receipts_outcome[] - | select(.id == $failed_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - status: .outcome.status - } - ) - }' /tmp/later-receipt-failure-rpc.json - -# На что смотреть: -# - top_level_status всё ещё равен SuccessValue -# - первая receipt контракта залогировала dishonest_router:kickoff:late-failure -# - более поздняя receipt append(...) упала с CodeDoesNotExist -``` - -Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. - -**Когда переходить дальше** - -Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. - -### Дошёл ли callback вообще? - -Нужно проверить, вернулся ли callback в исходный контракт? Начните с этого mainnet-примера. - -
-
- Ход -

Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а.

-
-
-

01POST /v0/transactions показывает downstream-вызов и более поздний receipt, который возвращается в исходный контракт.

-

02jq сужает этот список receipt до одного downstream-вызова и одного callback-receipt.

-

03RPC EXPERIMENTAL_tx_status нужен только как дополнительное подтверждение, если вам важны канонический результат callback-а и его логи.

-
-
- -Зафиксированный mainnet-пример с callback замечен **19 апреля 2026 года**: - -- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` -- аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` -- исходный контракт: `wrap.near` -- downstream-receiver: `v2.ref-finance.near` -- верхнеуровневый метод: `ft_transfer_call` -- downstream-метод: `ft_on_transfer` -- callback-метод: `ft_resolve_transfer` -- блок транзакции: `194692298` -- блок downstream-receipt: `194692300` -- блок callback-receipt: `194692301` - -```mermaid -flowchart LR - T["Одна mainnet-транзакция
ft_transfer_call на wrap.near"] --> D["Downstream-receipt
v2.ref-finance.near.ft_on_transfer"] - D --> F["Receiver упал
E51: contract paused"] - F --> C["Callback-receipt обратно в wrap.near
ft_resolve_transfer"] - C --> R["Лог refund на wrap.near"] + | jq '{ + tx_handoff: .transactions[0].execution_outcome.outcome.status, + outer_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + descendant_failures: [ + .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) + | { + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + block_height: .execution_outcome.block_height, + failure: .execution_outcome.outcome.status.Failure + } + ], + receipt_timeline: [ + .transactions[0].receipts[] + | { + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + status_class: (.execution_outcome.outcome.status | keys[0]) + } + ] + }' ``` -Здесь хорошо видна одна полезная деталь NEAR: downstream-сбой не означает, что callback исчез. В этом случае `v2.ref-finance.near` уронил свой `ft_on_transfer`, но `wrap.near` всё равно позже получил `ft_resolve_transfer` и залогировал refund. +Для зафиксированной транзакции mainnet `tx_handoff` — `SuccessReceiptId`: транзакция чисто запустила свой первый receipt. Если смотреть только сюда, можно назвать это победой. `descendant_failures` рассказывают вторую историю: `ft_on_transfer` на `v2.ref-finance.near` упал с `E51: contract paused` — DEX был на паузе во время этого свопа и не мог принять wrapped NEAR. А `receipt_timeline` показывает, как история разрешилась: callback `ft_resolve_transfer` на `wrap.near` всё равно отработал и вывел лог `Refund`, вернув wrapped NEAR отправителю. -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | -| Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | +Успех receipt не транзитивен. Протокол может чисто отдать handoff и при этом увидеть, как отцеплённая работа провалится позже. Если ваше приложение «выглядело успешным», но деньги всё равно вернулись, пройдите этот же timeline — разделение видно на индексированном ответе без отдельного RPC status-запроса. Чтобы отдельно проверить, что ваш callback отработал, см. [Отработал ли мой callback?](#отработал-ли-мой-callback). -#### Shell-сценарий проверки callback-а +### Отработал ли мой callback? -**Ход** - -- Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. -- Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. -- Останавливаетесь сразу, как только можете сказать, вернулся ли callback и что он сделал. +Кросс-контрактные вызовы NEAR возвращаются через callback-receipt на исходном контракте. Отработал ли этот callback — это одна строка с `any(...)` против индексированного списка receipts; а полная история refund выпадает из того же ответа. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL -SENDER_ACCOUNT_ID=7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72 ORIGIN_CONTRACT_ID=wrap.near -DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near -``` - -1. Получите транзакцию и сохраните receipt-цепочку. +CALLBACK_METHOD=ft_resolve_transfer -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/callback-check-transaction.json >/dev/null -``` - -2. Сначала ответьте на самый короткий полезный вопрос: вернулся ли callback вообще? - -```bash -jq --arg origin "$ORIGIN_CONTRACT_ID" ' - [ - .transactions[0].receipts[] - | select( + | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ + top_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + callback_ran: any( + .transactions[0].receipts[]; .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - ] | length > 0 -' /tmp/callback-check-transaction.json -``` - -3. Если ответ `true`, распечатайте downstream-receipt вместе с callback-receipt. - -```bash - -CALLBACK_RECEIPT_ID="$( - jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | .receipt.receipt_id - ) - ' /tmp/callback-check-transaction.json -)" - -jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - tx_block_height: .transactions[0].execution_outcome.block_height - }, - downstream_receipt: ( - first( - .transactions[0].receipts[] - | select(.receipt.receiver_id == $downstream) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - status: .execution_outcome.outcome.status, - block_height: .execution_outcome.block_height - } - ) - ), - callback_receipt: ( - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: .receipt.receipt.Action.actions[0].FunctionCall.method_name, - logs: .execution_outcome.outcome.logs, - status: .execution_outcome.outcome.status, - block_height: .execution_outcome.block_height - } - ) - ), - callback_ran: ( - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | true - ) // false - ) -}' /tmp/callback-check-transaction.json - -# На что смотреть: -# - downstream-receipt выполнил ft_on_transfer на v2.ref-finance.near -# - более поздний callback-receipt выполнил ft_resolve_transfer на wrap.near -# - callback_ran равно true, даже несмотря на downstream-сбой -``` - -4. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg sender_account_id "$SENDER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $sender_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/callback-check-rpc.json >/dev/null - -jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ - top_level_status: .result.status, - callback_receipt: ( - first( - .result.receipts_outcome[] - | select(.id == $callback_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - logs: .outcome.logs, - status: .outcome.status - } - ) - ) -}' /tmp/callback-check-rpc.json - -# На что смотреть: -# - downstream ft_on_transfer receipt упал на v2.ref-finance.near -# - wrap.near всё равно позже получил ft_resolve_transfer -# - лог callback-а показывает refund обратно отправителю + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback + ), + receipt_chain: [ + .transactions[0].receipts[] + | { + receiver_id: .receipt.receiver_id, + method: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + block: .execution_outcome.block_height, + status: (.execution_outcome.outcome.status | keys[0]), + logs: .execution_outcome.outcome.logs + } + ] + }' ``` -**Когда переходить дальше** - -Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. +Для зафиксированной транзакции `ft_transfer_call` на `wrap.near` передаёт управление в `ft_on_transfer` на `v2.ref-finance.near`, который **падает**. Callback `ft_resolve_transfer` всё равно выполняется на `wrap.near` и логирует `Refund 7278020378457059679767103 from v2.ref-finance.near to …` обратно отправителю — поэтому `callback_ran: true`, несмотря на сбой дочернего receipt. Сбой ниже по цепочке не мешает исходному контракту увидеть свой callback; так async-обработка ошибок NEAR остаётся восстанавливаемой. Строки с `method: "system"` — это рантайм-возвраты газа, а не логика контракта. Чтобы привязать один из этих логов к породившему его receipt, см. [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). ## Частые ошибки -- Пытаться отправлять транзакцию через history API вместо сырого RPC. +- Пытаться отправить транзакцию через history-API вместо raw RPC. - Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. -- Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». +- Спускаться в raw RPC до того, как индексированная история ответила на читаемый вопрос «что произошло?». -## Полезные связанные страницы +## Связанные страницы - [Transactions API](/tx) - [RPC Reference](/rpc) - [FastNear API](/api) - [NEAR Data API](/neardata) - [Berry Club: живая доска и один путь исторической реконструкции](/tx/examples/berry-club) -- [OutLayer: связать одну транзакцию запроса с одним ответом воркера](/tx/examples/outlayer) - [Расширенный поиск записи SocialDB](/tx/socialdb-proofs) - [Choosing the Right Surface](/agents/choosing-surfaces) - [Agent Playbooks](/agents/playbooks) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx deleted file mode 100644 index c4409e4..0000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/outlayer.mdx +++ /dev/null @@ -1,117 +0,0 @@ ---- -sidebar_label: OutLayer -slug: /tx/examples/outlayer -title: "OutLayer: что сделала эта пара request/resolution?" -description: "Используйте Transactions API, чтобы прочитать один caller-side запрос OutLayer, одно более позднее worker-side resolution и обращаться к finish-receipts только когда это действительно нужно." -displayed_sidebar: transactionsApiSidebar -page_actions: - - markdown -keywords: - - OutLayer - - FastNear - - NEAR - - Transactions API - - receipt - - callback ---- - -import Link from '@site/src/components/LocalizedLink'; - -{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как прочитать один caller-side запрос OutLayer вместе с более поздним worker-side resolution, а затем разобрать finish-receipts только если это действительно нужно. */} - -# OutLayer: что сделала эта пара request/resolution? - -Используйте этот walkthrough, когда вопрос звучит так: «что сделал этот запрос OutLayer, какое более позднее resolution к нему относится и нужно ли мне вообще смотреть на finish-receipts?» - -Оставайтесь в пределах публичных chain-данных: прочитайте request tx, прочитайте более поздний resolution tx и только потом решайте, нужны ли вам finish-receipts. - -## Компактный shell-сценарий - -Эта пара работала 18 апреля 2026 года: - -- caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` -- worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` - -### 1. Основной ответ: сразу раскройте request tx и resolution tx вместе - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -REQUEST_TX_HASH=AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4 -WORKER_TX_HASH=AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs - -curl -sS "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg request_tx_hash "$REQUEST_TX_HASH" --arg worker_tx_hash "$WORKER_TX_HASH" '{ - tx_hashes: [$request_tx_hash, $worker_tx_hash] - }')" \ - | tee /tmp/outlayer-pair.json >/dev/null - -jq '{ - transactions: [ - .transactions[] - | { - hash: .transaction.hash, - signer_id: .transaction.signer_id, - receiver_id: .transaction.receiver_id, - methods: [ - .transaction.actions[] - | .FunctionCall.method_name? - | select(. != null) - ], - first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) - } - ] -}' /tmp/outlayer-pair.json -``` - -Это и есть основной ответ: один request tx, один более поздний resolution tx и читаемые signer-, receiver-, method- и log-доказательства для обеих транзакций. - -### Необязательное продолжение: Что сделал finish-путь? - -```bash -jq --arg worker_tx_hash "$WORKER_TX_HASH" ' - .transactions[] - | select(.transaction.hash == $worker_tx_hash) - | { - worker_tx_hash: .transaction.hash, - receipts: [ - .receipts[] - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - actions: [.receipt.receipt.Action.actions[] | keys[0]], - logs: .execution_outcome.outcome.logs - } - ] - } -' /tmp/outlayer-pair.json -``` - -Смотрите на самое маленькое читаемое доказательство finish-пути: - -- `FunctionCall`-receipts, которые продолжают finish-путь -- логи списания вроде `[[yNEAR charged: "..."]]` -- последующие `Transfer`-receipts, которые похожи на refund или settlement-движение - -Именно здесь receipts становятся нужной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции здесь относятся друг к другу?» - -### Необязательный шаг: сначала найдите два хеша - -```bash -curl -sS "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data '{"account_id":"outlayer.near","desc":true,"limit":10}' \ - | jq '{ - txs_count, - recent_hashes: [.account_txs[:10][] | .transaction_hash] - }' -``` - -Используйте это только если вы ещё не знаете пару хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` превращает их в читаемый ответ. - -## Полезные связанные страницы - -- Transactions API: история аккаунта -- Transactions API: транзакции по хешу -- Transactions API: receipt по ID diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx index 0591119..db53c66 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx @@ -9,15 +9,15 @@ page_actions: # Расширенный поиск записи SocialDB -Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому поиску записи. +Используйте эту страницу только тогда, когда отправная точка — уже читаемое значение SocialDB из `api.near.social`, а следующий вопрос относится к историческому поиску записи. -Для FastNear-first-задач сначала откройте [Transactions Examples](/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" +Для FastNear-first-задач сначала откройте [Transactions Examples](/tx/examples). Сюда переходите только тогда, когда вопрос звучит как «какая запись сделала это читаемое значение SocialDB истинным?». ## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` -Используйте этот сценарий, когда читаемый факт уже звучит как "текущее `profile.name` равно `Mike Purvis`", а остаётся только вопрос, какая запись сделала это поле истинным. +Используйте этот сценарий, когда читаемый факт уже звучит как «текущее `profile.name` равно `Mike Purvis`», а остаётся вопрос, какая запись сделала это поле истинным. -Здесь достаточно сохранить один важный нюанс SocialDB: для исторического provenance правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. +Это единственный нюанс SocialDB, который стоит запомнить: для исторического доказательства правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. Для этого живого якоря: @@ -29,70 +29,57 @@ page_actions: ### Shell-сценарий -1. Прочитайте поле из NEAR Social и сохраните block записи на уровне поля. +1. Прочитайте поле из NEAR Social и сохраните блок записи на уровне поля. ```bash SOCIAL_API_BASE_URL=https://api.near.social TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com ACCOUNT_ID=mike.near PROFILE_FIELD=profile/name -PROFILE_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-profile-name.json \ - | jq -r --arg account_id "$ACCOUNT_ID" \ - '.[ $account_id ].profile.name[":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ +PROFILE="$(curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')")" + +echo "$PROFILE" | jq --arg account_id "$ACCOUNT_ID" '{ current_name: .[$account_id].profile.name[""], field_block_height: .[$account_id].profile.name[":block"], parent_profile_block_height: .[$account_id].profile[":block"] -}' /tmp/mike-profile-name.json +}' + +PROFILE_BLOCK_HEIGHT="$(echo "$PROFILE" | jq -r --arg account_id "$ACCOUNT_ID" '.[$account_id].profile.name[":block"]')" ``` -2. Переиспользуйте этот block уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. +2. Переиспользуйте этот блок уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. ```bash -PROFILE_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-profile-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ +BLOCK_RECEIPTS="$(curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')")" + +echo "$BLOCK_RECEIPTS" | jq --arg account_id "$ACCOUNT_ID" '{ profile_receipt: ( first( .block_receipts[] | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | { - receipt_id, - transaction_hash, - block_height, - tx_block_height - } + | {receipt_id, transaction_hash, block_height, tx_block_height} ) ) -}' /tmp/mike-profile-block.json +}' + +PROFILE_TX_HASH="$(echo "$BLOCK_RECEIPTS" | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )')" ``` 3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. @@ -101,34 +88,27 @@ jq --arg account_id "$ACCOUNT_ID" '{ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-profile-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), - description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), - tags: ( - .args - | @base64d - | fromjson - | .data["mike.near"].profile.tags - | keys - ) - } - ) -}' /tmp/mike-profile-transaction.json + | jq --arg account_id "$ACCOUNT_ID" '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | (.args | @base64d | fromjson | .data[$account_id].profile) as $profile + | { + method_name, + profile_name: $profile.name, + description: $profile.description, + tags: ($profile.tags | keys) + } + ) + }' ``` -Это и есть весь паттерн lookup-а: читаемое значение, block уровня поля, мост через receipt и payload транзакции. +Это и есть весь паттерн lookup: читаемое значение, блок уровня поля, мост через receipt и payload транзакции. Тот же мост работает и для других читаемых значений SocialDB: diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index 588f7dd..c49209b 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -2,412 +2,64 @@ ## Примеры -### Определить аккаунт по публичному ключу, а затем получить сводку по нему +### Определить аккаунт по публичному ключу и сразу получить сводку - Ход - Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. - - 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. - 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. - 03GET /v1/account/.../full в одном ответе показывает балансы, NFT и стейкинг. - -**Ход** - -- Ищете по публичному ключу один или несколько `account_id`. -- Извлекаете первый найденный `account_id` через `jq`. -- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. +Найдите, какому аккаунту принадлежит ключ, и прочитайте его активы за один следующий запрос. ```bash API_BASE_URL=https://api.fastnear.com -PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' -# Пример публичного ключа из модели страницы в документации: -# PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' +PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' -ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" +LOOKUP="$(curl -s "$API_BASE_URL/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')")" -ACCOUNT_ID="$( - curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ - | tee /tmp/fastnear-public-key.json \ - | jq -r '.account_ids[0]' -)" +echo "$LOOKUP" | jq '{matched: (.account_ids | length), account_ids}' -jq '{account_ids}' /tmp/fastnear-public-key.json +ACCOUNT_ID="$(echo "$LOOKUP" | jq -r '.account_ids[0]')" curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' + | jq '{account_id, state, tokens: (.tokens|length), nfts: (.nfts|length), pools: (.pools|length)}' ``` -**Когда переходить дальше** - -Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. - -### Показывает ли этот кошелёк прямой стейкинг, ликвидные стейкинг-токены или и то и другое? - - Ход - Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. - - 01GET /v1/account/.../staking находит прямую экспозицию через пулы. - 02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них. - 03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed. - -**Сеть** - -- mainnet - -**Официальные ссылки** - -- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) -- [Liquid staking](https://docs.near.org/primitives/liquid-staking) - -Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. +Если `matched` больше 1, переключайтесь на [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) и пройдитесь по каждому найденному аккаунту. -**Ход** +### Показывает ли кошелёк прямой стейкинг, liquid staking-токены или оба варианта? -- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. -- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. -- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. +Прямые позиции в пулах лежат на `/staking`; liquid staking-токены (stNEAR, LiNEAR и т. п.) лежат на `/ft` как обычные FT. Прочитайте оба эндпоинта и классифицируйте кошелёк — `root.near` оказывается `mixed`. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID +ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -``` - -1. Получите представление по прямому стейкингу. -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` +STAKING="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking")" +FT="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft")" -2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null -``` - -3. Классифицируйте аккаунт на основе этих двух индексированных представлений. - -```bash jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ + --argjson staking "$STAKING" \ + --argjson ft "$FT" \ --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + ($staking.pools // []) as $direct + | (($ft.tokens // []) | map(select(.contract_id as $id | $providers | index($id)))) as $liquid | { classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) + if ($direct|length)>0 and ($liquid|length)>0 then "mixed" + elif ($direct|length)>0 then "direct_only" + elif ($liquid|length)>0 then "liquid_only" + else "no_visible_staking_position" end, + direct_pools: ($direct | map(.pool_id)), + liquid_tokens: ($liquid | map({contract_id, balance})) }' ``` -**Когда переходить дальше** - -Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. - -### Заархивировать версию BOS-виджета как provenance NFT - - Ход - Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. - - 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. - 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. - 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner. - -**Сети** - -- mainnet для чтения виджета из `social.near` -- testnet для безопасного mint provenance NFT в `nft.examples.testnet` - -**Официальные ссылки** - -- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) -- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Ход** - -- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. -- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. -- Хешируете исходник виджета и превращаете его в provenance-метаданные. -- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. -- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. - -Зафиксированный исходный виджет: - -- аккаунт автора: `mob.near` -- путь виджета: `mob.near/widget/Profile` -- SocialDB-блок уровня виджета: `86494825` - -```bash -API_BASE_URL=https://test.api.fastnear.com -MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com -TESTNET_RPC_URL=https://rpc.testnet.fastnear.com -AUTHOR_ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -DESTINATION_COLLECTION_ID=nft.examples.testnet -RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" -``` - -1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. - -```bash -curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ - | tee /tmp/provenance-account-nfts.json >/dev/null - -jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_tokens: [ - .tokens[]? - | select(.contract_id == $destination_collection_id) - | { - contract_id, - token_id, - last_update_block_height - } - ] -}' /tmp/provenance-account-nfts.json -``` - -2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. - -```bash -WIDGET_ARGS_BASE64="$( - jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($author_account_id + "/widget/" + $widget_name)], - options: {with_block_height: true} - }' | base64 | tr -d '\n' -)" - -curl -s "$MAINNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget.json >/dev/null - -jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - widget_path: ($author_account_id + "/widget/" + $widget_name), - socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], - source_preview: ( - .[$author_account_id].widget[$widget_name][""] - | split("\n")[0:8] - ) -}' /tmp/bos-widget.json -``` - -3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. - -```bash -jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][""] -' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx - -WIDGET_BLOCK_HEIGHT="$( - jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][":block"] - ' /tmp/bos-widget.json -)" - -SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" -SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" -TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" - -PROVENANCE_METADATA_JSON="$( - jq -nc \ - --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" \ - --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ - --arg block_height "$WIDGET_BLOCK_HEIGHT" \ - --arg source_sha256 "$SOURCE_SHA256" '{ - title: ("BOS widget archive: " + $widget_path), - description: ("Archived from social.near on mainnet at block " + $block_height), - copies: 1, - extra: ({ - author_account_id: $author_account_id, - widget_name: $widget_name, - widget_path: $widget_path, - source_contract_id: "social.near", - source_network: "mainnet", - socialdb_block_height: ($block_height | tonumber), - source_sha256: $source_sha256 - } | @json) - }' -)" - -printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' -``` - -4. Выпустите provenance NFT в testnet. - -```bash -near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ - --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --argjson metadata "$PROVENANCE_METADATA_JSON" '{ - token_id: $token_id, - receiver_id: $receiver_id, - metadata: $metadata - }')" \ - --accountId "$SIGNER_ACCOUNT_ID" \ - --deposit 0.1 \ - --networkId testnet -``` - -5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. - -Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. - -```bash -NFT_TOKEN_ARGS_BASE64="$( - jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ - | base64 | tr -d '\n' -)" - -for attempt in 1 2 3 4 5; do - curl -s "$TESTNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_token", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget-provenance-token.json >/dev/null - - if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then - break - fi - - sleep 1 -done - -jq '{ - token_id, - owner_id, - title: .metadata.title, - provenance: (.metadata.extra | fromjson) -}' /tmp/bos-widget-provenance-token.json -``` - -**Когда переходить дальше** - -FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). - -## Частые задачи - -### Что этот аккаунт вообще держит прямо сейчас? - -**Начните здесь** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» - -**Следующая страница при необходимости** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» - -**Остановитесь, когда** - -- Сводка уже отвечает на вопрос по активам в одной выдаче. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Определить аккаунты по публичному ключу - -**Начните здесь** - -- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. -- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. - -**Остановитесь, когда** - -- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). -- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? - -**Начните здесь** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. -- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft), когда вопрос конкретно про владение NFT. -- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» - -**Остановитесь, когда** - -- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. - -**Переходите дальше, когда** - -- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). +Классификатор знает только то, чему вы его научили — расширяйте `LIQUID_PROVIDERS_JSON` по мере появления новых liquid staking-продуктов и рассматривайте результат как наблюдательный, а не исчерпывающий. ## Частые ошибки - Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. -- Использовать FastNear API, хотя пользователю прямо нужны точные поля RPC или права доступа. +- Использовать FastNear API, хотя пользователю нужны точные поля RPC или права доступа. - Оставаться на страницах сводок по аккаунту, когда вопрос уже стал вопросом об истории транзакций. -- Забывать, что `?network=testnet` поддерживается только на совместимых страницах. -## Полезные связанные страницы +## Связанные страницы - [FastNear API](https://docs.fastnear.com/ru/api) - [API Reference](https://docs.fastnear.com/ru/api/reference) diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index 588f7dd..c49209b 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -2,412 +2,64 @@ ## Примеры -### Определить аккаунт по публичному ключу, а затем получить сводку по нему +### Определить аккаунт по публичному ключу и сразу получить сводку - Ход - Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. - - 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. - 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. - 03GET /v1/account/.../full в одном ответе показывает балансы, NFT и стейкинг. - -**Ход** - -- Ищете по публичному ключу один или несколько `account_id`. -- Извлекаете первый найденный `account_id` через `jq`. -- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. +Найдите, какому аккаунту принадлежит ключ, и прочитайте его активы за один следующий запрос. ```bash API_BASE_URL=https://api.fastnear.com -PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' -# Пример публичного ключа из модели страницы в документации: -# PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' +PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' -ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" +LOOKUP="$(curl -s "$API_BASE_URL/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')")" -ACCOUNT_ID="$( - curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ - | tee /tmp/fastnear-public-key.json \ - | jq -r '.account_ids[0]' -)" +echo "$LOOKUP" | jq '{matched: (.account_ids | length), account_ids}' -jq '{account_ids}' /tmp/fastnear-public-key.json +ACCOUNT_ID="$(echo "$LOOKUP" | jq -r '.account_ids[0]')" curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' + | jq '{account_id, state, tokens: (.tokens|length), nfts: (.nfts|length), pools: (.pools|length)}' ``` -**Когда переходить дальше** - -Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. - -### Показывает ли этот кошелёк прямой стейкинг, ликвидные стейкинг-токены или и то и другое? - - Ход - Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. - - 01GET /v1/account/.../staking находит прямую экспозицию через пулы. - 02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них. - 03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed. - -**Сеть** - -- mainnet - -**Официальные ссылки** - -- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) -- [Liquid staking](https://docs.near.org/primitives/liquid-staking) - -Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. +Если `matched` больше 1, переключайтесь на [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) и пройдитесь по каждому найденному аккаунту. -**Ход** +### Показывает ли кошелёк прямой стейкинг, liquid staking-токены или оба варианта? -- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. -- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. -- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. +Прямые позиции в пулах лежат на `/staking`; liquid staking-токены (stNEAR, LiNEAR и т. п.) лежат на `/ft` как обычные FT. Прочитайте оба эндпоинта и классифицируйте кошелёк — `root.near` оказывается `mixed`. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID +ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -``` - -1. Получите представление по прямому стейкингу. -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` +STAKING="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking")" +FT="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft")" -2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null -``` - -3. Классифицируйте аккаунт на основе этих двух индексированных представлений. - -```bash jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ + --argjson staking "$STAKING" \ + --argjson ft "$FT" \ --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + ($staking.pools // []) as $direct + | (($ft.tokens // []) | map(select(.contract_id as $id | $providers | index($id)))) as $liquid | { classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) + if ($direct|length)>0 and ($liquid|length)>0 then "mixed" + elif ($direct|length)>0 then "direct_only" + elif ($liquid|length)>0 then "liquid_only" + else "no_visible_staking_position" end, + direct_pools: ($direct | map(.pool_id)), + liquid_tokens: ($liquid | map({contract_id, balance})) }' ``` -**Когда переходить дальше** - -Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. - -### Заархивировать версию BOS-виджета как provenance NFT - - Ход - Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. - - 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. - 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. - 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner. - -**Сети** - -- mainnet для чтения виджета из `social.near` -- testnet для безопасного mint provenance NFT в `nft.examples.testnet` - -**Официальные ссылки** - -- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) -- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Ход** - -- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. -- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. -- Хешируете исходник виджета и превращаете его в provenance-метаданные. -- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. -- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. - -Зафиксированный исходный виджет: - -- аккаунт автора: `mob.near` -- путь виджета: `mob.near/widget/Profile` -- SocialDB-блок уровня виджета: `86494825` - -```bash -API_BASE_URL=https://test.api.fastnear.com -MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com -TESTNET_RPC_URL=https://rpc.testnet.fastnear.com -AUTHOR_ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -DESTINATION_COLLECTION_ID=nft.examples.testnet -RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" -``` - -1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. - -```bash -curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ - | tee /tmp/provenance-account-nfts.json >/dev/null - -jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_tokens: [ - .tokens[]? - | select(.contract_id == $destination_collection_id) - | { - contract_id, - token_id, - last_update_block_height - } - ] -}' /tmp/provenance-account-nfts.json -``` - -2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. - -```bash -WIDGET_ARGS_BASE64="$( - jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($author_account_id + "/widget/" + $widget_name)], - options: {with_block_height: true} - }' | base64 | tr -d '\n' -)" - -curl -s "$MAINNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget.json >/dev/null - -jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - widget_path: ($author_account_id + "/widget/" + $widget_name), - socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], - source_preview: ( - .[$author_account_id].widget[$widget_name][""] - | split("\n")[0:8] - ) -}' /tmp/bos-widget.json -``` - -3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. - -```bash -jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][""] -' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx - -WIDGET_BLOCK_HEIGHT="$( - jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][":block"] - ' /tmp/bos-widget.json -)" - -SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" -SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" -TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" - -PROVENANCE_METADATA_JSON="$( - jq -nc \ - --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" \ - --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ - --arg block_height "$WIDGET_BLOCK_HEIGHT" \ - --arg source_sha256 "$SOURCE_SHA256" '{ - title: ("BOS widget archive: " + $widget_path), - description: ("Archived from social.near on mainnet at block " + $block_height), - copies: 1, - extra: ({ - author_account_id: $author_account_id, - widget_name: $widget_name, - widget_path: $widget_path, - source_contract_id: "social.near", - source_network: "mainnet", - socialdb_block_height: ($block_height | tonumber), - source_sha256: $source_sha256 - } | @json) - }' -)" - -printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' -``` - -4. Выпустите provenance NFT в testnet. - -```bash -near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ - --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --argjson metadata "$PROVENANCE_METADATA_JSON" '{ - token_id: $token_id, - receiver_id: $receiver_id, - metadata: $metadata - }')" \ - --accountId "$SIGNER_ACCOUNT_ID" \ - --deposit 0.1 \ - --networkId testnet -``` - -5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. - -Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. - -```bash -NFT_TOKEN_ARGS_BASE64="$( - jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ - | base64 | tr -d '\n' -)" - -for attempt in 1 2 3 4 5; do - curl -s "$TESTNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_token", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget-provenance-token.json >/dev/null - - if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then - break - fi - - sleep 1 -done - -jq '{ - token_id, - owner_id, - title: .metadata.title, - provenance: (.metadata.extra | fromjson) -}' /tmp/bos-widget-provenance-token.json -``` - -**Когда переходить дальше** - -FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). - -## Частые задачи - -### Что этот аккаунт вообще держит прямо сейчас? - -**Начните здесь** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» - -**Следующая страница при необходимости** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» - -**Остановитесь, когда** - -- Сводка уже отвечает на вопрос по активам в одной выдаче. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Определить аккаунты по публичному ключу - -**Начните здесь** - -- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. -- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. - -**Остановитесь, когда** - -- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). -- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? - -**Начните здесь** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. -- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft), когда вопрос конкретно про владение NFT. -- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» - -**Остановитесь, когда** - -- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. - -**Переходите дальше, когда** - -- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). +Классификатор знает только то, чему вы его научили — расширяйте `LIQUID_PROVIDERS_JSON` по мере появления новых liquid staking-продуктов и рассматривайте результат как наблюдательный, а не исчерпывающий. ## Частые ошибки - Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. -- Использовать FastNear API, хотя пользователю прямо нужны точные поля RPC или права доступа. +- Использовать FastNear API, хотя пользователю нужны точные поля RPC или права доступа. - Оставаться на страницах сводок по аккаунту, когда вопрос уже стал вопросом об истории транзакций. -- Забывать, что `?network=testnet` поддерживается только на совместимых страницах. -## Полезные связанные страницы +## Связанные страницы - [FastNear API](https://docs.fastnear.com/ru/api) - [API Reference](https://docs.fastnear.com/ru/api/reference) diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index 6a47dc1..e5990e4 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -2,154 +2,47 @@ ## Пример -### Посмотреть индексированные записи одного `predecessor_id`, а затем сузиться до ключа, который изменился +### Посмотреть индексированные записи одного `predecessor_id` и сузиться до изменившегося ключа - Ход - Начните с области по `predecessor_id`, переходите к точному ключу только после того, как он заслужил внимание, а RPC оставляйте на самый конец. - - 01all-by-predecessor даёт последние индексированные строки для одного `predecessor_id` по затронутым контрактам. - 02get-history-key или history-by-predecessor объясняют, как менялась интересующая строка во времени. - 03RPC view_state — это уже необязательное точное чтение, когда нужен именно канонический current state, а не индексированная история. - -### Shell-сценарий по области предшественника -**Ход** - -- Читаете последние индексированные строки для одного `predecessor_id` по затронутым контрактам. -- Поднимаете интересующие `current_account_id` и точный `key` через `jq`. -- Переиспользуете эти значения в документированном маршруте истории по точному ключу. -- Только после этого решаете, нужен ли вам `view_state` для канонического current state. +`all-by-predecessor` возвращает последние индексированные записи одного аккаунта по каждому контракту, которого он касался. Выберите интересный ключ и прогоните его через `history`, чтобы увидеть, как эта строка менялась со временем. ```bash KV_BASE_URL=https://kv.main.fastnear.com -PREDECESSOR_ID=james.near +PREDECESSOR_ID=jemartel.near -curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ +FIRST="$(curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ -H 'content-type: application/json' \ - --data '{"include_metadata":true,"limit":10}' \ - | tee /tmp/kv-predecessor.json >/dev/null + --data '{"include_metadata":true,"limit":10}')" -jq '{ +echo "$FIRST" | jq '{ page_token, - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] -}' /tmp/kv-predecessor.json - -CURRENT_ACCOUNT_ID="$(jq -r '.entries[0].current_account_id' /tmp/kv-predecessor.json)" -EXACT_KEY="$(jq -r '.entries[0].key' /tmp/kv-predecessor.json)" -ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" - -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | jq '{ - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] - }' + entries: [.entries[] | {current_account_id, predecessor_id, block_height, key, value, tx_hash}] +}' ``` -**Когда переходить дальше** - -Первый запрос отвечает на вопрос по области: «что этот `predecessor_id` сейчас пишет?». Сужение из этой ленты до одного точного ключа отвечает на более точный вопрос: «как именно эта строка дошла до такого состояния?». Если картина всё ещё шире одного ключа, ещё немного побудьте на [History by Predecessor](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), а уже потом переходите к точной истории ключа или RPC. - -## Частые задачи - -### Начать с записей одного `predecessor_id` - -**Начните здесь** - -- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor), когда вы знаете, кто писал строки, но ещё не знаете, какой точный ключ важнее всего. - -**Следующая страница при необходимости** - -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если одна строка становится настоящим фокусом. -- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), если более широкая картина записей всё ещё важнее точного ключа. - -**Остановитесь, когда** - -- Уже можно объяснить, что писал этот `predecessor_id`, и заслуживает ли одна строка более глубокой истории. - -**Переходите дальше, когда** - -- Пользователю нужно каноническое текущее состояние в цепочке, а не индексированная история записей. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). - -### Превратить один точный ключ в историю изменений - -**Начните здесь** - -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для поиска истории по пути. -- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. - -**Следующая страница при необходимости** - -- Возвращайтесь к [Последнему по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. +Для `jemartel.near` в выдаче смешиваются подтверждение идентичности `account_id` на `contextual.near` и серия добавлений `graph/follow/*` в тот же контракт. `tx_hash` в каждой строке — это прямой переход в [/tx/examples](https://docs.fastnear.com/ru/tx/examples#%D1%83-%D0%BC%D0%B5%D0%BD%D1%8F-%D0%BE%D0%B4%D0%B8%D0%BD-%D1%85%D0%B5%D1%88-%D1%82%D1%80%D0%B0%D0%BD%D0%B7%D0%B0%D0%BA%D1%86%D0%B8%D0%B8-%D1%87%D1%82%D0%BE-%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%BE%D1%88%D0%BB%D0%BE), если нужна полная история транзакции за любой записью. -**Остановитесь, когда** +Поднимите самую свежую строку и прогоните её через `history`: -- Уже можно объяснить, как ключ менялся со временем. - -**Переходите дальше, когда** - -- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. - -### Проследить записи от одного `predecessor_id` - -**Начните здесь** - -- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. -- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. - -**Следующая страница при необходимости** - -- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. - -**Остановитесь, когда** - -- Уже можно ответить, что именно этот предшественник изменил и где. - -**Переходите дальше, когда** - -- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. - -### Пакетно проверить несколько известных ключей - -**Начните здесь** - -- [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. - -**Следующая страница при необходимости** - -- Переведите один интересный ключ в [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. - -**Остановитесь, когда** - -- Пакетный ответ уже показывает, какие ключи действительно важны. +```bash +CURRENT_ACCOUNT_ID="$(echo "$FIRST" | jq -r '.entries[0].current_account_id')" +EXACT_KEY="$(echo "$FIRST" | jq -r '.entries[0].key')" +ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" -**Переходите дальше, когда** +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | jq '{entries: [.entries[] | {block_height, value}]}' +``` -- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. +Для строки `account_id` `history` возвращает одну запись на блоке `185965311` со значением `"jemartel.near:mainnet"` — подтверждение идентичности держится, стабильно с того блока. KV сохраняет каждую запись одинаково: у тихого ключа — одна строка, у активного — много, форма та же, без агрегации. ## Частые ошибки - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. -- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. +- Использовать KV FastData, когда пользователю на самом деле нужны балансы или активы. - Путать индексированную историю с точным текущим состоянием в цепочке. - Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. -## Полезные связанные страницы +## Связанные страницы - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv) - [RPC Reference](https://docs.fastnear.com/ru/rpc) diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index 6a47dc1..e5990e4 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -2,154 +2,47 @@ ## Пример -### Посмотреть индексированные записи одного `predecessor_id`, а затем сузиться до ключа, который изменился +### Посмотреть индексированные записи одного `predecessor_id` и сузиться до изменившегося ключа - Ход - Начните с области по `predecessor_id`, переходите к точному ключу только после того, как он заслужил внимание, а RPC оставляйте на самый конец. - - 01all-by-predecessor даёт последние индексированные строки для одного `predecessor_id` по затронутым контрактам. - 02get-history-key или history-by-predecessor объясняют, как менялась интересующая строка во времени. - 03RPC view_state — это уже необязательное точное чтение, когда нужен именно канонический current state, а не индексированная история. - -### Shell-сценарий по области предшественника -**Ход** - -- Читаете последние индексированные строки для одного `predecessor_id` по затронутым контрактам. -- Поднимаете интересующие `current_account_id` и точный `key` через `jq`. -- Переиспользуете эти значения в документированном маршруте истории по точному ключу. -- Только после этого решаете, нужен ли вам `view_state` для канонического current state. +`all-by-predecessor` возвращает последние индексированные записи одного аккаунта по каждому контракту, которого он касался. Выберите интересный ключ и прогоните его через `history`, чтобы увидеть, как эта строка менялась со временем. ```bash KV_BASE_URL=https://kv.main.fastnear.com -PREDECESSOR_ID=james.near +PREDECESSOR_ID=jemartel.near -curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ +FIRST="$(curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ -H 'content-type: application/json' \ - --data '{"include_metadata":true,"limit":10}' \ - | tee /tmp/kv-predecessor.json >/dev/null + --data '{"include_metadata":true,"limit":10}')" -jq '{ +echo "$FIRST" | jq '{ page_token, - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] -}' /tmp/kv-predecessor.json - -CURRENT_ACCOUNT_ID="$(jq -r '.entries[0].current_account_id' /tmp/kv-predecessor.json)" -EXACT_KEY="$(jq -r '.entries[0].key' /tmp/kv-predecessor.json)" -ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" - -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | jq '{ - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] - }' + entries: [.entries[] | {current_account_id, predecessor_id, block_height, key, value, tx_hash}] +}' ``` -**Когда переходить дальше** - -Первый запрос отвечает на вопрос по области: «что этот `predecessor_id` сейчас пишет?». Сужение из этой ленты до одного точного ключа отвечает на более точный вопрос: «как именно эта строка дошла до такого состояния?». Если картина всё ещё шире одного ключа, ещё немного побудьте на [History by Predecessor](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), а уже потом переходите к точной истории ключа или RPC. - -## Частые задачи - -### Начать с записей одного `predecessor_id` - -**Начните здесь** - -- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor), когда вы знаете, кто писал строки, но ещё не знаете, какой точный ключ важнее всего. - -**Следующая страница при необходимости** - -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если одна строка становится настоящим фокусом. -- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), если более широкая картина записей всё ещё важнее точного ключа. - -**Остановитесь, когда** - -- Уже можно объяснить, что писал этот `predecessor_id`, и заслуживает ли одна строка более глубокой истории. - -**Переходите дальше, когда** - -- Пользователю нужно каноническое текущее состояние в цепочке, а не индексированная история записей. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). - -### Превратить один точный ключ в историю изменений - -**Начните здесь** - -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для поиска истории по пути. -- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. - -**Следующая страница при необходимости** - -- Возвращайтесь к [Последнему по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. +Для `jemartel.near` в выдаче смешиваются подтверждение идентичности `account_id` на `contextual.near` и серия добавлений `graph/follow/*` в тот же контракт. `tx_hash` в каждой строке — это прямой переход в [/tx/examples](https://docs.fastnear.com/ru/tx/examples#%D1%83-%D0%BC%D0%B5%D0%BD%D1%8F-%D0%BE%D0%B4%D0%B8%D0%BD-%D1%85%D0%B5%D1%88-%D1%82%D1%80%D0%B0%D0%BD%D0%B7%D0%B0%D0%BA%D1%86%D0%B8%D0%B8-%D1%87%D1%82%D0%BE-%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%BE%D1%88%D0%BB%D0%BE), если нужна полная история транзакции за любой записью. -**Остановитесь, когда** +Поднимите самую свежую строку и прогоните её через `history`: -- Уже можно объяснить, как ключ менялся со временем. - -**Переходите дальше, когда** - -- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. - -### Проследить записи от одного `predecessor_id` - -**Начните здесь** - -- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. -- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. - -**Следующая страница при необходимости** - -- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. - -**Остановитесь, когда** - -- Уже можно ответить, что именно этот предшественник изменил и где. - -**Переходите дальше, когда** - -- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. - -### Пакетно проверить несколько известных ключей - -**Начните здесь** - -- [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. - -**Следующая страница при необходимости** - -- Переведите один интересный ключ в [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. - -**Остановитесь, когда** - -- Пакетный ответ уже показывает, какие ключи действительно важны. +```bash +CURRENT_ACCOUNT_ID="$(echo "$FIRST" | jq -r '.entries[0].current_account_id')" +EXACT_KEY="$(echo "$FIRST" | jq -r '.entries[0].key')" +ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" -**Переходите дальше, когда** +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | jq '{entries: [.entries[] | {block_height, value}]}' +``` -- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. +Для строки `account_id` `history` возвращает одну запись на блоке `185965311` со значением `"jemartel.near:mainnet"` — подтверждение идентичности держится, стабильно с того блока. KV сохраняет каждую запись одинаково: у тихого ключа — одна строка, у активного — много, форма та же, без агрегации. ## Частые ошибки - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. -- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. +- Использовать KV FastData, когда пользователю на самом деле нужны балансы или активы. - Путать индексированную историю с точным текущим состоянием в цепочке. - Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. -## Полезные связанные страницы +## Связанные страницы - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv) - [RPC Reference](https://docs.fastnear.com/ru/rpc) diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index 183a1c2..e93e00a 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -13,10 +13,10 @@ - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Практические примеры для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Практические примеры KV FastData: scoped-записи, история ключа и переход к точному состоянию. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Недавние чтения по блокам и шардам для мониторинга активности контракта, подтверждения оптимистичных наблюдений и проверки изменений на уровне шарда. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. -- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Практические примеры FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. +- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Практические примеры RPC: проверки состояния, инспекция блоков, чтение контрактов и отправка транзакций. - [API переводов](https://docs.fastnear.com/ru/transfers.md): История переводов NEAR и FT-токенов по аккаунтам для продуктовых лент и инструментов расследования. - [Транзакции API](https://docs.fastnear.com/ru/tx.md): Индексированные запросы по транзакциям, квитанциям, истории аккаунтов и истории блоков для FastNear. @@ -29,19 +29,18 @@ ## Другие гайды -- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Практические примеры FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга. +- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Практические примеры FastNear API: поиск аккаунта по ключу, просмотр активов и классификация стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Практические примеры для мониторинга недавней активности контракта, подтверждения optimistic-наблюдений и проверки изменений на уровне shard. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Практические примеры NEAR Data: живой мониторинг, optimistic-проверки и доказательство на уровне shard. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. -- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Практические примеры для поиска переводов, пагинации через resume_token и перехода к истории транзакций. -- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Практические расследования транзакций для типовых задач разработчика. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Практические примеры: фильтрация ленты переводов, пагинация и переход к истории транзакций. +- [Примеры Transactions](https://docs.fastnear.com/ru/tx/examples.md): Практические расследования транзакций: хеши, receipts, async-сбои и callback. - [Berry Club: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Прочитайте живую доску Berry Club через RPC get_lines, а затем используйте Transactions API, чтобы восстановить одну более раннюю эпоху. -- [OutLayer: что сделала эта пара request/resolution?](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы прочитать один caller-side запрос OutLayer, одно более позднее worker-side resolution и обращаться к finish-receipts только когда это действительно нужно. - [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs.md): Один короткий расширенный сценарий, который начинается с читаемого значения SocialDB и восстанавливает транзакцию записи за ним. ## Снапшоты - [Снапшоты для валидаторов](https://docs.fastnear.com/ru/snapshots.md): Пути загрузки снапшотов FastNear для подъёма и восстановления узлов NEAR. -- [Примеры Snapshot](https://docs.fastnear.com/ru/snapshots/examples.md): Практические примеры восстановления для выбора правильного пути через FastNear snapshots. +- [Примеры snapshot](https://docs.fastnear.com/ru/snapshots/examples.md): Практические примеры восстановления узла: optimized, standard и archival. - [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. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index ea193c7..6d96c86 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -996,412 +996,64 @@ https://test.api.fastnear.com ## Примеры -### Определить аккаунт по публичному ключу, а затем получить сводку по нему +### Определить аккаунт по публичному ключу и сразу получить сводку - Ход - Сначала определите личность, а затем переиспользуйте тот же аккаунт для одной читаемой сводки по кошельку. - - 01GET /v1/public_key возвращает кандидатные значения account_id для этого ключа. - 02jq поднимает тот аккаунт, который вы хотите смотреть дальше. - 03GET /v1/account/.../full в одном ответе показывает балансы, NFT и стейкинг. - -**Ход** - -- Ищете по публичному ключу один или несколько `account_id`. -- Извлекаете первый найденный `account_id` через `jq`. -- Переиспользуете это значение в широком эндпоинте полного снимка аккаунта. +Найдите, какому аккаунту принадлежит ключ, и прочитайте его активы за один следующий запрос. ```bash API_BASE_URL=https://api.fastnear.com -PUBLIC_KEY='ed25519:YOUR_PUBLIC_KEY' -# Пример публичного ключа из модели страницы в документации: -# PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' +PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' -ENCODED_PUBLIC_KEY="$(jq -rn --arg public_key "$PUBLIC_KEY" '$public_key | @uri')" +LOOKUP="$(curl -s "$API_BASE_URL/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')")" -ACCOUNT_ID="$( - curl -s "$API_BASE_URL/v1/public_key/$ENCODED_PUBLIC_KEY" \ - | tee /tmp/fastnear-public-key.json \ - | jq -r '.account_ids[0]' -)" +echo "$LOOKUP" | jq '{matched: (.account_ids | length), account_ids}' -jq '{account_ids}' /tmp/fastnear-public-key.json +ACCOUNT_ID="$(echo "$LOOKUP" | jq -r '.account_ids[0]')" curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ - | jq '{ - account_id, - state, - token_count: (.tokens | length), - nft_count: (.nfts | length), - pool_count: (.pools | length) - }' + | jq '{account_id, state, tokens: (.tokens|length), nfts: (.nfts|length), pools: (.pools|length)}' ``` -**Когда переходить дальше** - -Поиск по публичному ключу говорит, с каким аккаунтом вы имеете дело. Полный снимок аккаунта — естественный следующий запрос, если нужны балансы, NFT, стейкинг и пулы в одном ответе. Если ключ сопоставляется не с одним, а с несколькими аккаунтами, переходите к [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) или пройдитесь по каждому найденному `account_id`. - -### Показывает ли этот кошелёк прямой стейкинг, ликвидные стейкинг-токены или и то и другое? +Если `matched` больше 1, переключайтесь на [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) и пройдитесь по каждому найденному аккаунту. - Ход - Сначала сравните staking-позиции и FT-балансы, а уже потом интерпретируйте кошелёк. +### Показывает ли кошелёк прямой стейкинг, liquid staking-токены или оба варианта? - 01GET /v1/account/.../staking находит прямую экспозицию через пулы. - 02GET /v1/account/.../ft находит liquid staking token, которые лежат рядом с пулами или вместо них. - 03jq превращает эти два индексированных чтения в direct_only, liquid_only или mixed. - -**Сеть** - -- mainnet - -**Официальные ссылки** - -- [Валидаторский стейкинг](https://docs.near.org/concepts/basics/staking) -- [Liquid staking](https://docs.near.org/primitives/liquid-staking) - -Этот пример намеренно остаётся наблюдательным. Он классифицирует то, что FastNear видит сейчас по staking-позициям и FT-балансам. Он не доказывает каждую возможную синтетическую или внешнюю форму стейкинг-экспозиции. - -**Ход** - -- Читаете индексированные прямые staking-позиции через staking-эндпоинт аккаунта. -- Читаете индексированные FT-балансы через FT-эндпоинт аккаунта. -- Классифицируете аккаунт как `direct_only`, `liquid_only`, `mixed` или `no_visible_staking_position`. -- Выводите список прямых пулов и список liquid staking-токенов, на которых основана эта классификация. +Прямые позиции в пулах лежат на `/staking`; liquid staking-токены (stNEAR, LiNEAR и т. п.) лежат на `/ft` как обычные FT. Прочитайте оба эндпоинта и классифицируйте кошелёк — `root.near` оказывается `mixed`. ```bash API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID +ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -``` -1. Получите представление по прямому стейкингу. +STAKING="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking")" +FT="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft")" -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking" \ - | tee /tmp/account-staking.json \ - | jq '{account_id, pools}' -``` - -2. Получите FT-балансы, чтобы увидеть liquid staking-позиции. - -```bash -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft" \ - | tee /tmp/account-ft.json >/dev/null -``` - -3. Классифицируйте аккаунт на основе этих двух индексированных представлений. - -```bash jq -n \ - --slurpfile staking /tmp/account-staking.json \ - --slurpfile ft /tmp/account-ft.json \ + --argjson staking "$STAKING" \ + --argjson ft "$FT" \ --argjson providers "$LIQUID_PROVIDERS_JSON" ' - ($staking[0].pools // []) as $direct_pools - | ($ft[0].tokens // []) as $tokens - | ($tokens | map(select(.contract_id as $id | $providers | index($id)))) as $liquid_tokens + ($staking.pools // []) as $direct + | (($ft.tokens // []) | map(select(.contract_id as $id | $providers | index($id)))) as $liquid | { classification: - if (($direct_pools | length) > 0 and ($liquid_tokens | length) > 0) then "mixed" - elif (($direct_pools | length) > 0) then "direct_only" - elif (($liquid_tokens | length) > 0) then "liquid_only" - else "no_visible_staking_position" - end, - direct_pools: ($direct_pools | map(.pool_id)), - liquid_tokens: ( - $liquid_tokens - | map({ - contract_id, - balance, - last_update_block_height - }) - ) - }' -``` - -**Когда переходить дальше** - -Если классификация показывает `direct_only`, следующий практический вопрос обычно касается сроков `unstake` и `withdraw`. Если она показывает `liquid_only`, следующий вопрос обычно про `redeem`, `swap` или провайдерский путь выхода. Если результат `mixed`, эти пути лучше рассматривать раздельно, а не пытаться свести их к одному сценарию. - -### Заархивировать версию BOS-виджета как provenance NFT - - Ход - Сначала прочитайте точный виджет, а mint делайте только тогда, когда provenance-поля уже детерминированы. - - 01GET /v1/account/.../nft проверяет, есть ли у получателя уже архивные NFT из этой коллекции. - 02RPC call_function get на social.near читает точный исходник виджета и блок его записи в SocialDB. - 03Захешируйте исходник, выполните nft_mint в testnet, а потом проверьте provenance-поля через nft_tokens_for_owner. - -**Сети** - -- mainnet для чтения виджета из `social.near` -- testnet для безопасного mint provenance NFT в `nft.examples.testnet` - -**Официальные ссылки** - -- [Предразвёрнутый NFT-контракт](https://docs.near.org/tutorials/nfts/js/predeployed-contract) -- [Стандарт NFT NEP-171](https://docs.near.org/primitives/nft/standard) -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Ход** - -- Через FastNear API проверяете, есть ли у получателя NFT из архивной коллекции. -- Читаете один точный BOS-виджет из `social.near`, включая SocialDB-блок именно этого виджета. -- Хешируете исходник виджета и превращаете его в provenance-метаданные. -- Выпускаете NFT в testnet, чьи метаданные фиксируют автора, widget-path, SocialDB-блок и хеш исходника. -- Подтверждаете, что выпущенный токен действительно несёт эти provenance-поля. - -Зафиксированный исходный виджет: - -- аккаунт автора: `mob.near` -- путь виджета: `mob.near/widget/Profile` -- SocialDB-блок уровня виджета: `86494825` - -```bash -API_BASE_URL=https://test.api.fastnear.com -MAINNET_RPC_URL=https://rpc.mainnet.fastnear.com -TESTNET_RPC_URL=https://rpc.testnet.fastnear.com -AUTHOR_ACCOUNT_ID=mob.near -WIDGET_NAME=Profile -DESTINATION_COLLECTION_ID=nft.examples.testnet -RECEIVER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -SIGNER_ACCOUNT_ID="$RECEIVER_ACCOUNT_ID" -``` - -1. Через FastNear API посмотрите, держит ли получатель уже какие-то NFT из архивной коллекции. - -```bash -curl -s "$API_BASE_URL/v1/account/$RECEIVER_ACCOUNT_ID/nft" \ - | tee /tmp/provenance-account-nfts.json >/dev/null - -jq --arg destination_collection_id "$DESTINATION_COLLECTION_ID" '{ - existing_archive_tokens: [ - .tokens[]? - | select(.contract_id == $destination_collection_id) - | { - contract_id, - token_id, - last_update_block_height - } - ] -}' /tmp/provenance-account-nfts.json -``` - -2. Прочитайте точное тело виджета и widget-level SocialDB-блок из mainnet. - -```bash -WIDGET_ARGS_BASE64="$( - jq -nc --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - keys: [($author_account_id + "/widget/" + $widget_name)], - options: {with_block_height: true} - }' | base64 | tr -d '\n' -)" - -curl -s "$MAINNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg args_base64 "$WIDGET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: "social.near", - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget.json >/dev/null - -jq --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" '{ - widget_path: ($author_account_id + "/widget/" + $widget_name), - socialdb_block_height: .[$author_account_id].widget[$widget_name][":block"], - source_preview: ( - .[$author_account_id].widget[$widget_name][""] - | split("\n")[0:8] - ) -}' /tmp/bos-widget.json -``` - -3. Захешируйте исходник виджета и постройте детерминированные provenance-метаданные. - -```bash -jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][""] -' /tmp/bos-widget.json > /tmp/bos-widget-source.jsx - -WIDGET_BLOCK_HEIGHT="$( - jq -r --arg author_account_id "$AUTHOR_ACCOUNT_ID" --arg widget_name "$WIDGET_NAME" ' - .[$author_account_id].widget[$widget_name][":block"] - ' /tmp/bos-widget.json -)" - -SOURCE_SHA256="$(shasum -a 256 /tmp/bos-widget-source.jsx | awk '{print $1}')" -SOURCE_HASH_SHORT="$(printf '%s' "$SOURCE_SHA256" | cut -c1-12)" -TOKEN_ID="bos-widget-$SOURCE_HASH_SHORT" - -PROVENANCE_METADATA_JSON="$( - jq -nc \ - --arg author_account_id "$AUTHOR_ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" \ - --arg widget_path "$AUTHOR_ACCOUNT_ID/widget/$WIDGET_NAME" \ - --arg block_height "$WIDGET_BLOCK_HEIGHT" \ - --arg source_sha256 "$SOURCE_SHA256" '{ - title: ("BOS widget archive: " + $widget_path), - description: ("Archived from social.near on mainnet at block " + $block_height), - copies: 1, - extra: ({ - author_account_id: $author_account_id, - widget_name: $widget_name, - widget_path: $widget_path, - source_contract_id: "social.near", - source_network: "mainnet", - socialdb_block_height: ($block_height | tonumber), - source_sha256: $source_sha256 - } | @json) + if ($direct|length)>0 and ($liquid|length)>0 then "mixed" + elif ($direct|length)>0 then "direct_only" + elif ($liquid|length)>0 then "liquid_only" + else "no_visible_staking_position" end, + direct_pools: ($direct | map(.pool_id)), + liquid_tokens: ($liquid | map({contract_id, balance})) }' -)" - -printf '%s\n' "$PROVENANCE_METADATA_JSON" | jq '.' -``` - -4. Выпустите provenance NFT в testnet. - -```bash -near call "$DESTINATION_COLLECTION_ID" nft_mint "$(jq -nc \ - --arg token_id "$TOKEN_ID" \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --argjson metadata "$PROVENANCE_METADATA_JSON" '{ - token_id: $token_id, - receiver_id: $receiver_id, - metadata: $metadata - }')" \ - --accountId "$SIGNER_ACCOUNT_ID" \ - --deposit 0.1 \ - --networkId testnet -``` - -5. Подтвердите, что выпущенный NFT действительно несёт ожидаемые provenance-поля. - -Не считайте отсутствие токена ошибкой мгновенно: после mint-транзакции опросите view-метод несколько раз. - -```bash -NFT_TOKEN_ARGS_BASE64="$( - jq -nc --arg token_id "$TOKEN_ID" '{token_id: $token_id}' \ - | base64 | tr -d '\n' -)" - -for attempt in 1 2 3 4 5; do - curl -s "$TESTNET_RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$DESTINATION_COLLECTION_ID" \ - --arg args_base64 "$NFT_TOKEN_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "nft_token", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '.result.result | implode | fromjson' \ - | tee /tmp/bos-widget-provenance-token.json >/dev/null - - if jq -e '. != null' /tmp/bos-widget-provenance-token.json >/dev/null; then - break - fi - - sleep 1 -done - -jq '{ - token_id, - owner_id, - title: .metadata.title, - provenance: (.metadata.extra | fromjson) -}' /tmp/bos-widget-provenance-token.json ``` -**Когда переходить дальше** - -FastNear API даёт быстрый чек со стороны получателя. Mainnet RPC даёт точное тело виджета и его SocialDB-блок. После этого mint в testnet превращает чтение в долговечную NFT-запись. Если позже понадобится доказать, какая именно историческая транзакция записала этот виджет, переходите к NEAR Social proof-расследованиям в [Transactions API examples](https://docs.fastnear.com/ru/tx/examples). - -## Частые задачи - -### Что этот аккаунт вообще держит прямо сейчас? - -**Начните здесь** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), когда нужен самый быстрый понятный ответ на вопрос «что сейчас лежит в этом аккаунте?» - -**Следующая страница при необходимости** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft) или [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), если широкая сводка уже помогла, но дальше хочется остаться только в одной категории активов. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «как аккаунт пришёл к такому состоянию?», а не «что он держит сейчас?» - -**Остановитесь, когда** - -- Сводка уже отвечает на вопрос по активам в одной выдаче. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точном состоянии аккаунта, о семантике ключей доступа или о протокольных полях. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Пользователя интересует история активности или исполнения, а не текущий набор активов. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Определить аккаунты по публичному ключу - -**Начните здесь** - -- [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key), когда нужен основной аккаунт для ключа. -- [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all), когда нужен более полный список связанных аккаунтов. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) после поиска, если сразу нужна сводка по балансам или активам найденных аккаунтов. - -**Остановитесь, когда** - -- Уже определён аккаунт или набор аккаунтов, которым принадлежит ключ. - -**Переходите дальше, когда** - -- Пользователь спрашивает о точных правах ключа, nonce или текущем состоянии access key. Переходите к [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list). -- Пользователя интересует недавняя активность найденных аккаунтов, а не только их идентификация. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Есть ли у этого аккаунта FT, NFT или стейкинг-позиции? - -**Начните здесь** - -- [V1 Account FT](https://docs.fastnear.com/ru/api/v1/account-ft), когда вопрос относится только к балансам FT-токенов. -- [V1 Account NFT](https://docs.fastnear.com/ru/api/v1/account-nft), когда вопрос конкретно про владение NFT. -- [V1 Account Staking](https://docs.fastnear.com/ru/api/v1/account-staking), когда пользователя интересуют именно стейкинг-позиции, а не вся картина по аккаунту. - -**Следующая страница при необходимости** - -- [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full), если после одной категории активов позже понадобится вся картина по аккаунту. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если вопрос уже меняется с «чем аккаунт владеет?» на «как он к этому пришёл?» - -**Остановитесь, когда** - -- Эндпоинт по конкретной категории активов уже отвечает на вопрос о владении без пересборки всей картины аккаунта. - -**Переходите дальше, когда** - -- Индексированного представления недостаточно и нужна точная семантика состояния в цепочке. Переходите к [RPC Reference](https://docs.fastnear.com/ru/rpc). -- Вопрос становится историческим или связанным с исполнением вместо «чем этот аккаунт владеет сейчас?». Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). +Классификатор знает только то, чему вы его научили — расширяйте `LIQUID_PROVIDERS_JSON` по мере появления новых liquid staking-продуктов и рассматривайте результат как наблюдательный, а не исчерпывающий. ## Частые ошибки - Сразу идти в широкий снимок аккаунта, когда пользователя интересует только одна категория активов. -- Использовать FastNear API, хотя пользователю прямо нужны точные поля RPC или права доступа. +- Использовать FastNear API, хотя пользователю нужны точные поля RPC или права доступа. - Оставаться на страницах сводок по аккаунту, когда вопрос уже стал вопросом об истории транзакций. -- Забывать, что `?network=testnet` поддерживается только на совместимых страницах. -## Полезные связанные страницы +## Связанные страницы - [FastNear API](https://docs.fastnear.com/ru/api) - [API Reference](https://docs.fastnear.com/ru/api/reference) @@ -1632,154 +1284,47 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY ## Пример -### Посмотреть индексированные записи одного `predecessor_id`, а затем сузиться до ключа, который изменился - - Ход - Начните с области по `predecessor_id`, переходите к точному ключу только после того, как он заслужил внимание, а RPC оставляйте на самый конец. - - 01all-by-predecessor даёт последние индексированные строки для одного `predecessor_id` по затронутым контрактам. - 02get-history-key или history-by-predecessor объясняют, как менялась интересующая строка во времени. - 03RPC view_state — это уже необязательное точное чтение, когда нужен именно канонический current state, а не индексированная история. +### Посмотреть индексированные записи одного `predecessor_id` и сузиться до изменившегося ключа -### Shell-сценарий по области предшественника -**Ход** - -- Читаете последние индексированные строки для одного `predecessor_id` по затронутым контрактам. -- Поднимаете интересующие `current_account_id` и точный `key` через `jq`. -- Переиспользуете эти значения в документированном маршруте истории по точному ключу. -- Только после этого решаете, нужен ли вам `view_state` для канонического current state. +`all-by-predecessor` возвращает последние индексированные записи одного аккаунта по каждому контракту, которого он касался. Выберите интересный ключ и прогоните его через `history`, чтобы увидеть, как эта строка менялась со временем. ```bash KV_BASE_URL=https://kv.main.fastnear.com -PREDECESSOR_ID=james.near +PREDECESSOR_ID=jemartel.near -curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ +FIRST="$(curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ -H 'content-type: application/json' \ - --data '{"include_metadata":true,"limit":10}' \ - | tee /tmp/kv-predecessor.json >/dev/null + --data '{"include_metadata":true,"limit":10}')" -jq '{ +echo "$FIRST" | jq '{ page_token, - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] -}' /tmp/kv-predecessor.json - -CURRENT_ACCOUNT_ID="$(jq -r '.entries[0].current_account_id' /tmp/kv-predecessor.json)" -EXACT_KEY="$(jq -r '.entries[0].key' /tmp/kv-predecessor.json)" -ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" - -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - | jq '{ - entries: [ - .entries[] - | { - current_account_id, - predecessor_id, - block_height, - key, - value - } - ] - }' + entries: [.entries[] | {current_account_id, predecessor_id, block_height, key, value, tx_hash}] +}' ``` -**Когда переходить дальше** - -Первый запрос отвечает на вопрос по области: «что этот `predecessor_id` сейчас пишет?». Сужение из этой ленты до одного точного ключа отвечает на более точный вопрос: «как именно эта строка дошла до такого состояния?». Если картина всё ещё шире одного ключа, ещё немного побудьте на [History by Predecessor](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), а уже потом переходите к точной истории ключа или RPC. - -## Частые задачи - -### Начать с записей одного `predecessor_id` - -**Начните здесь** - -- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor), когда вы знаете, кто писал строки, но ещё не знаете, какой точный ключ важнее всего. - -**Следующая страница при необходимости** - -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если одна строка становится настоящим фокусом. -- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), если более широкая картина записей всё ещё важнее точного ключа. - -**Остановитесь, когда** - -- Уже можно объяснить, что писал этот `predecessor_id`, и заслуживает ли одна строка более глубокой истории. - -**Переходите дальше, когда** - -- Пользователю нужно каноническое текущее состояние в цепочке, а не индексированная история записей. Переходите к [View State](https://docs.fastnear.com/ru/rpc/contract/view-state). - -### Превратить один точный ключ в историю изменений - -**Начните здесь** - -- [История по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key) для поиска истории по пути. -- [History by Key](https://docs.fastnear.com/ru/fastdata/kv/history-by-key), когда лучше подходит маршрут по полному ключу. - -**Следующая страница при необходимости** +Для `jemartel.near` в выдаче смешиваются подтверждение идентичности `account_id` на `contextual.near` и серия добавлений `graph/follow/*` в тот же контракт. `tx_hash` в каждой строке — это прямой переход в [/tx/examples](https://docs.fastnear.com/ru/tx/examples#%D1%83-%D0%BC%D0%B5%D0%BD%D1%8F-%D0%BE%D0%B4%D0%B8%D0%BD-%D1%85%D0%B5%D1%88-%D1%82%D1%80%D0%B0%D0%BD%D0%B7%D0%B0%D0%BA%D1%86%D0%B8%D0%B8-%D1%87%D1%82%D0%BE-%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%BE%D1%88%D0%BB%D0%BE), если нужна полная история транзакции за любой записью. -- Возвращайтесь к [Последнему по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-latest-key), если нужно увидеть текущее индексированное значение рядом с историей. +Поднимите самую свежую строку и прогоните её через `history`: -**Остановитесь, когда** - -- Уже можно объяснить, как ключ менялся со временем. - -**Переходите дальше, когда** - -- Пользователь спрашивает, совпадает ли последнее индексированное значение с тем, что цепочка возвращает прямо сейчас. - -### Проследить записи от одного `predecessor_id` - -**Начните здесь** - -- [Всё по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/all-by-predecessor) для последних записей по контрактам, затронутым одним предшественником. -- [История по `predecessor_id`](https://docs.fastnear.com/ru/fastdata/kv/history-by-predecessor), когда нужна история записей во времени. - -**Следующая страница при необходимости** - -- Сузьте область до точного ключа, если одна строка становится настоящим фокусом расследования. - -**Остановитесь, когда** - -- Уже можно ответить, что именно этот предшественник изменил и где. - -**Переходите дальше, когда** - -- Пользователя перестают интересовать индексированные записи и начинает интересовать текущее состояние в цепочке. - -### Пакетно проверить несколько известных ключей - -**Начните здесь** - -- [Пакетный поиск по ключам](https://docs.fastnear.com/ru/fastdata/kv/multi), когда уже известен фиксированный набор точных ключей. - -**Следующая страница при необходимости** - -- Переведите один интересный ключ в [Историю по точному ключу](https://docs.fastnear.com/ru/fastdata/kv/get-history-key), если batch-ответ вызывает исторический вопрос. - -**Остановитесь, когда** - -- Пакетный ответ уже показывает, какие ключи действительно важны. +```bash +CURRENT_ACCOUNT_ID="$(echo "$FIRST" | jq -r '.entries[0].current_account_id')" +EXACT_KEY="$(echo "$FIRST" | jq -r '.entries[0].key')" +ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" -**Переходите дальше, когда** +curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + | jq '{entries: [.entries[] | {block_height, value}]}' +``` -- У вас больше нет фиксированного списка ключей и нужно смотреть на контракт или предшественника шире. +Для строки `account_id` `history` возвращает одну запись на блоке `185965311` со значением `"jemartel.near:mainnet"` — подтверждение идентичности держится, стабильно с того блока. KV сохраняет каждую запись одинаково: у тихого ключа — одна строка, у активного — много, форма та же, без агрегации. ## Частые ошибки - Начинать с широких выборок по аккаунту или предшественнику, когда точный ключ уже известен. -- Использовать KV FastData, хотя пользователю на самом деле нужны балансы или активы. +- Использовать KV FastData, когда пользователю на самом деле нужны балансы или активы. - Путать индексированную историю с точным текущим состоянием в цепочке. - Переиспользовать токен пагинации или менять фильтры прямо во время просмотра. -## Полезные связанные страницы +## Связанные страницы - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv) - [RPC Reference](https://docs.fastnear.com/ru/rpc) @@ -2124,25 +1669,11 @@ https://testnet.neardata.xyz **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) -Используйте NEAR Data, когда вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. - ## Примеры -### Был ли мой контракт затронут в последнем финализированном блоке? - - Ход - Сначала дайте NEAR Data ответить на задачу мониторинга, а уже потом сохраняйте tx hash или receipt ID для следующей поверхности, если это вообще понадобится. - - 01last-block-final находит самую новую финализированную высоту. - 02block даёт один недавний гидратированный документ блока с уже присоединёнными данными по shard. - 03Суммируйте прямые транзакции, входящие receipts, результаты выполнения и state_changes для нужного контракта. Считайте state_changes самым сильным сигналом того, что контракт действительно изменился. - -Такой сценарий вполне честно может вернуть `touched: false`, если блок тихий. Это тоже полезный ответ: в самом новом финализированном блоке сейчас нет ничего, что требовало бы более глубокого разбора. +Каждый гидратированный документ блока NEAR Data несёт транзакции, receipts, результаты исполнения и state changes с разбивкой по shard. Три сценария ниже используют один `bash`-помощник, который сворачивает эти четыре сигнала в одну сводку с полями для перехода дальше. Определите его один раз и прогоняйте блоки через него: ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near - contract_touch_summary() { jq -r --arg contract "$1" ' [ .shards[] | { @@ -2177,158 +1708,82 @@ contract_touch_summary() { sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) }' } +``` -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" +### Был ли мой контракт затронут в последнем финализированном блоке? -printf 'Latest finalized block: %s\n' "$FINAL_LOCATION" +`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок; пройдите по нему и направьте результат сразу в помощник. + +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ +curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ | contract_touch_summary "$TARGET_CONTRACT" ``` -Читайте ответ так: - -- `touched: false` означает, что самый новый финализированный блок не упомянул и не изменил контракт ни одним из отслеживаемых способов. -- `sample_tx_hash` означает, что у вас уже есть хороший якорь для следующего шага на `/tx`. -- `sample_receipt_id` без tx hash обычно означает, что контракт появился в цепочке через receipts, и NEAR Data уже сэкономила вам более дешёвый этап мониторинга. +Читайте `touched: false` как полный и однозначный ответ для тихого блока. При `true` поля перехода (`sample_tx_hash`, `sample_receipt_id`) сразу ведут вас в [/tx/examples](https://docs.fastnear.com/ru/tx/examples) за человекочитаемой историей. Один запрос заменяет ручной просмотр chunks — и учтите: `touched: true` с `state_changes: 0` — это реальная форма: receipt может попасть в chunk, не вызвав в том же блоке мутации состояния. ### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? - Ход - Используйте один и тот же словарь contract-touch на обеих поверхностях, чтобы сравнение было честным. - - 01last-block-optimistic находит самую новую optimistic-высоту. - 02block-optimistic показывает ранний сигнал для того же контракта. - 03block на той же высоте либо подтверждает то же наблюдение, либо показывает, что finality ещё не догнала. - -Если finality уже догнала, optimistic- и finalized-сводки могут совпасть сразу. Это тоже полезно: ранний сигнал уже попал в стабильную историю. +Optimistic-блоки живут по адресу `/v0/block_opt/{height}`; как только finality догоняет (обычно в пределах одного блока, ~1 с на mainnet), та же высота становится доступна и по `/v0/block/{height}`. Прогоните помощник на обеих и сравните. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near - -contract_touch_summary() { - jq -r --arg contract "$1" ' - [ .shards[] | { - shard_id, - direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) - } - | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) - ] as $rows - | { - height: .block.header.height, - hash: .block.header.hash, - contract: $contract, - touched: (($rows | length) > 0), - shards: ($rows | map(.shard_id)), - evidence: { - direct_txs: (($rows | map(.direct_txs) | add) // 0), - incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), - execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), - state_changes: (($rows | map(.state_changes) | add) // 0) - } - }' -} - OPT_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' + | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" - OPT_HEIGHT="${OPT_LOCATION##*/}" -printf 'Latest optimistic block: %s\n' "$OPT_LOCATION" +echo "Optimistic view at $OPT_HEIGHT:" +curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | contract_touch_summary "$TARGET_CONTRACT" -curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" \ - | tee /tmp/neardata-optimistic-block.json \ - | contract_touch_summary "$TARGET_CONTRACT" - -curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT" \ - | tee /tmp/neardata-final-same-height.json >/dev/null - -if jq -e 'type == "null"' /tmp/neardata-final-same-height.json >/dev/null; then - printf 'Finalized block %s is not available yet; finality has not caught up.\n' "$OPT_HEIGHT" +echo "Finalized view at $OPT_HEIGHT:" +FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" +if [ "$(echo "$FINAL" | jq 'type')" = '"null"' ]; then + echo "finality has not caught up to $OPT_HEIGHT yet" else - printf 'Finalized block %s is already available; compare the stable answer below.\n' "$OPT_HEIGHT" - contract_touch_summary "$TARGET_CONTRACT" < /tmp/neardata-final-same-height.json + echo "$FINAL" | contract_touch_summary "$TARGET_CONTRACT" fi ``` -Практический вывод такой: - -- optimistic — это ранний сигнал, на который цикл мониторинга может быстро отреагировать; -- finalized — это стабильный ответ, который уже можно показывать пользователям или использовать в устойчивой автоматизации. +На здоровой сети обе сводки совпадают сразу; ценность — в самом шаблоне, а не в драматичной разнице. Цикл мониторинга, который реагирует на optimistic-сигнал, знает: тот же ответ — на один блок от надёжного. Ветку `finality has not caught up` используйте, когда действительно нужно отличить «увидено optimistically» от «подтверждено» — во время стресса сети этот разрыв расширяется. ### Какой shard действительно изменил мой контракт в этом блоке? - Ход - Сначала используйте весь блок, чтобы найти нужный shard, а затем дайте block-shard доказать само изменение. - - 01Просканируйте список shard внутри финализированного блока и найдите state_changes по вашему контракту. - 02Откройте только тот shard, который действительно изменил контракт. - 03Сохраните совпадающие state_changes и нужные результаты исполнения как доказательство на уровне shard. - -На момент написания недавний финализированный блок `194727131` дал чистый живой пример для `intents.near`: контракт сначала появился как входящий receipt на shard `8`, а затем действительно выполнился и изменил состояние на shard `7`. - -Если для вашей задачи нужен более свежий блок, переиспользуйте ту же сводку из первого примера на нескольких соседних финализированных высотах, а затем подставьте найденную высоту в тот же вызов `block-shard`. +Блоки тонкие — в большинстве финализированных блоков нет мутаций состояния ни для одного конкретного контракта. Идите назад от финализированной головы, пока помощник не покажет `state_changes > 0`, затем откройте «победивший» shard через `/v0/block/{height}/shard/{shard_id}` ради самого payload мутации. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near -EXAMPLE_HEIGHT=194727131 - -curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT" \ - | tee /tmp/neardata-block-194727131.json \ - | jq --arg contract "$TARGET_CONTRACT" '[ - .shards[] | { - shard_id, - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) - } - | select(.incoming_receipts + .execution_outcomes + .state_changes > 0) - ]' +HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" +TARGET_HEIGHT="" +WINNING_SHARD="" + +for OFFSET in 0 1 2 3 4 5 6 7 8 9; do + H=$((HEAD - OFFSET)) + SUMMARY="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" | contract_touch_summary "$TARGET_CONTRACT")" + if [ "$(echo "$SUMMARY" | jq '.evidence.state_changes')" -gt 0 ]; then + TARGET_HEIGHT=$H + WINNING_SHARD="$(echo "$SUMMARY" | jq -r '.shards[0]')" + echo "$SUMMARY" + break + fi +done -curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT/shard/7" \ +curl -s "$NEARDATA_BASE_URL/v0/block/$TARGET_HEIGHT/shard/$WINNING_SHARD" \ | jq --arg contract "$TARGET_CONTRACT" '{ shard_id, chunk_hash: .chunk.header.chunk_hash, - matching_state_changes: [ - .state_changes[] - | select(.change.account_id? == $contract) - | {type, cause, account_id: .change.account_id} - ][0:2], - matching_execution_outcomes: [ - .receipt_execution_outcomes[] - | select(.execution_outcome.outcome.executor_id == $contract) - | { - receipt_id: .execution_outcome.id, - executor_id: .execution_outcome.outcome.executor_id, - status: .execution_outcome.outcome.status, - predecessor_id: .receipt.predecessor_id - } - ][0:2] + matching_state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause_type: (.cause | keys[0]), account_id: .change.account_id}][0:3], + matching_execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0]), predecessor_id: .receipt.predecessor_id}][0:3] }' ``` -Практическое правило здесь простое: - -- используйте `block`, когда первый вопрос звучит как «какой shard вообще важен?»; -- используйте `block-shard`, когда настоящий вопрос уже стал таким: «покажи мне сам payload shard, который изменил состояние». +На mainnet `intents.near` стабильно выполняется на shard 7, поэтому обход назад обычно попадает в цель за несколько блоков. Payload shard затем называет конкретные типы state-change (`account_update`, `data_update` и т. п.) и результаты исполнения receipt, которые их породили, — shard-локальное доказательство без догадок. Для менее активных контрактов расширьте диапазон `OFFSET`. -## Когда пора расширять поверхность +## Когда расширить поверхность - Используйте [Transactions API](https://docs.fastnear.com/ru/tx), когда у вас уже есть `tx_hash` и нужен человекочитаемый рассказ о транзакции. -- Используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. +- Используйте [RPC Reference](https://docs.fastnear.com/ru/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. - Используйте [Block Headers](https://docs.fastnear.com/ru/neardata/block-headers), когда нужна только динамика head/finality, а не проверка contract-touch. --- @@ -2490,3505 +1945,1171 @@ https://archival-rpc.testnet.fastnear.com # Примеры RPC -Начинайте с RPC-метода, который отвечает на вопрос. Отправляйте через `broadcast_tx_async`, отслеживайте через `tx` и расширяйте разбор только когда действительно нужны дерево receipts, raw state или трассировка по шардам. +Начинайте с RPC-метода, который отвечает на вопрос. Используйте `tx`, чтобы отследить включение и финальность по хешу транзакции, и расширяйте поверхность только когда нужны дерево receipts, сырой state или трассировка на уровне shard. -## Отправка и отслеживание транзакции +## Включение транзакции и финальность -### Отправить транзакцию и затем проследить её от хеша до финального исполнения +### Отследить транзакцию от хеша до финальности -Нужен стандартный путь отправки через RPC? Отправляйте через `broadcast_tx_async`, затем опрашивайте `tx`. К `EXPERIMENTAL_tx_status` переходите только если нужно дерево receipts. +Есть tx hash? Опрашивайте `tx` с минимальным порогом `wait_until`, который отвечает на ваш вопрос. -Зафиксированная mainnet-транзакция: - -- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- signer: `mike.near` -- receiver: `social.near` -- высота блока включения: `79574923` -- высота блока исполнения receipt для записи в SocialDB: `79574924` - - Ход - Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. +```bash +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow +SIGNER_ACCOUNT_ID=mike.testnet - 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. - 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. - 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. - -**Точки выбора** - -- какой эндпоинт отправки брать первым -- что опрашивать после того, как у вас появился tx hash -- как `wait_until` связан с included-, optimistic- и final-гарантиями -- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` - -```mermaid -flowchart LR - S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] - A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] - T --> F["Транзакция полностью завершена"] - T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] - F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" --arg signer_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", id: "fastnear", method: "tx", + params: {tx_hash: $tx_hash, sender_account_id: $signer_id, wait_until: "INCLUDED"} + }')" \ + | jq '{ + asked: "INCLUDED", + final_execution_status: .result.final_execution_status, + status_class: (.result.status | keys[0]), + receipts_outcome_count: (.result.receipts_outcome | length) + }' ``` -| Метод | Когда использовать | Что вернётся | Роль здесь | -| --- | --- | --- | --- | -| [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | -| [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | -| [`broadcast_tx_commit`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | -| [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | -| [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | - -**Карта статусов и ожидания** +Для зафиксированной исторической транзакции (1-yocto self-transfer от `mike.testnet`) ответ возвращается как `FINAL`, хотя мы спрашивали `INCLUDED`. Правило такое: **`wait_until` — это минимальный порог, а не целевой**. Узел возвращает тот этап, которого транзакция действительно достигла: для исторической всегда `FINAL`; для полётной выбирайте `INCLUDED`, когда достаточно включения и нужен самый ранний возврат, или `FINAL`, когда реальный вопрос звучит «завершилась ли?». -Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. +Два перехода отсюда: -| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | -| --- | --- | --- | -| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | -| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | -| `INCLUDED_FINAL` | блок включения уже финален | `tx` | -| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | -| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | - -Используйте методы так: +- **Отправляете в реальном времени?** [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) возвращает хеш сразу после того, как узел принял payload — отслеживайте отдельно через `tx`. [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) одновременно отправляет и блокируется на выбранном `wait_until` в одном запросе. +- **Нужно дерево receipts, а не только outcome?** `tx` уже включает `receipts_outcome`; расширяйте поверхность до [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) только тогда, когда дополнительно нужны сырые записи receipts. -- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash -- используйте `tx` как обычный цикл опроса -- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу +## Инспекция блока на tip -**Ход** +### Описать первый action первой транзакции на текущем tip -- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. -- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. -- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. -- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. +Пройдите `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. ```bash RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near -RECEIVER_ID=social.near -``` - -1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data '{ - "jsonrpc": "2.0", - "id": "fastnear", - "method": "broadcast_tx_async", - "params": ["BASE64_SIGNED_TX"] - }' \ - | jq . -``` +EMPTY_TX_ROOT=11111111111111111111111111111111 -В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. +BLOCK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ + | jq -r '.result.sync_info.latest_block_hash')" -2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. +CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_hash "$BLOCK_HASH" '{ + jsonrpc:"2.0",id:"fastnear",method:"block",params:{block_id:$block_hash} + }')" \ + | jq -r --arg empty "$EMPTY_TX_ROOT" ' + first(.result.chunks[] | select(.tx_root != $empty) | .chunk_hash) // empty')" -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "INCLUDED_FINAL" - } +if [ -z "$CHUNK_HASH" ]; then + echo "tip block had no transactions in any chunk — rerun on the next head" +else + curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg chunk_hash "$CHUNK_HASH" '{ + jsonrpc:"2.0",id:"fastnear",method:"chunk",params:{chunk_id:$chunk_hash} }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' + | jq '{ + chunk_shard: .result.header.shard_id, + chunk_height: .result.header.height_included, + first_tx: { + hash: .result.transactions[0].hash, + signer_id: .result.transactions[0].signer_id, + receiver_id: .result.transactions[0].receiver_id + }, + first_action: ( + .result.transactions[0].actions[0] as $a + | if ($a | type) == "string" then {kind: $a} + elif $a.FunctionCall then {kind: "FunctionCall", method_name: $a.FunctionCall.method_name} + else {kind: ($a | keys[0])} end + ) + }' +fi ``` -Что здесь важно заметить: +Живой запуск возвращает первый chunk текущего tip, первую транзакцию и первый action — часто это `FunctionCall` на контракте моста или tg-бота (mainnet активен). Tip-блок может быть валидным и при этом не содержать транзакций ни в одном chunk — поэтому ветка с пустым результатом остаётся; это честный ответ для тихого момента сети. + +## Механика аккаунтов и ключей -- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality -- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения -- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt +### Аудит старых function-call-ключей Near Social -3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. +У создателей накапливаются Social function-call-ключи от каждого кошелька и каждого BOS-шлюза, которым они пользовались. `view_access_key_list` возвращает их все; один фильтр сужает до `social.near`, а **младшие шесть цифр nonce** заодно служат счётчиком использования — новые ключи стартуют с `block_height * 10^6` и инкрементируются на единицу за каждую транзакцию. ```bash +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +RECEIVER_ID=social.near + curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - receipts_outcome_count: (.result.receipts_outcome | length) - }' + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_access_key_list",account_id:$account_id,finality:"final"} + }')" \ + | jq --arg receiver "$RECEIVER_ID" ' + { + total_keys: (.result.keys | length), + social_fcks: [ + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + created_near_block: (.access_key.nonce / 1000000 | floor), + tx_count: (.access_key.nonce % 1000000), + method_names: (.access_key.permission.FunctionCall.method_names | if . == [] then "ANY" else . end), + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } + ] | sort_by(.tx_count) + }' ``` -Что здесь важно заметить: +Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на очистку. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность конкретного dapp. -- для исторической tx этот вызов тоже возвращается сразу -- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» -- для многих приложений именно здесь и стоит остановиться +Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом — function-call-ключ не может авторизовать `DeleteKey` — и отправьте через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Повторный запуск того же списка подтверждает удаление. Само подписание — стандартная near-api-js-история и не самая интересная часть аудита. -4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. +### Какая транзакция добавила этот `social.near` function-call-ключ и кто её авторизовал? + +Тот же nonce, что считает использование, заодно якорит `AddKey` во времени блоков: новые ключи стартуют примерно с `block_height * 10^6`, так что деление текущего nonce на миллион даёт плотное окно поиска. Один раз гидратируйте кандидатов — и ответ уже несёт достаточно, чтобы отличить прямой `AddKey` от делегированной (meta-tx) авторизации, то есть показать, *какой ключ подписал решение*, а не только какой аккаунт оплатил gas. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status, - receipts_outcome_count: (.result.receipts_outcome | length) - }' -``` +RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=mike.near +TARGET_PUBLIC_KEY=ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs -Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». +CURRENT_NONCE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg public_key "$TARGET_PUBLIC_KEY" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_access_key",account_id:$account_id,public_key:$public_key,finality:"final"} + }')" \ + | jq -r '.result.nonce')" -5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. +ADD_KEY_BLOCK=$((CURRENT_NONCE / 1000000)) -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transaction_handoff: .transactions[0].transaction_outcome.outcome.status - }' +TX_HASHES="$(curl -s "$TX_BASE_URL/v0/account" -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" \ + --argjson from $((ADD_KEY_BLOCK - 20)) --argjson to $((ADD_KEY_BLOCK + 5)) '{ + account_id: $account_id, is_real_signer: true, + from_tx_block_height: $from, to_tx_block_height: $to, desc: false, limit: 50 + }')" \ + | jq -c '[.account_txs[].transaction_hash]')" + +curl -s "$TX_BASE_URL/v0/transactions" -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES" '{tx_hashes: $tx_hashes}')" \ + | jq --arg target "$TARGET_PUBLIC_KEY" ' + [ .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? | .AddKey? | select(.public_key == $target) + | {mode: "direct", authorizing_public_key: $tx.transaction.public_key, permission: .access_key.permission}), + ($tx.transaction.actions[]? | .Delegate? | .delegate_action as $d + | $d.actions[]? | .AddKey? | select(.public_key == $target) + | {mode: "delegated", authorizing_public_key: $d.public_key, permission: .access_key.permission}) + ) + | { + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + signer_id: $tx.transaction.signer_id, + receiver_id: $tx.transaction.receiver_id, + add_key_receipt: ([$tx.receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]?; .AddKey.public_key? == $target)) + | {receipt_id: .receipt.receipt_id, receipt_block: .execution_outcome.block_height}][0]) + } + . + ]' ``` -Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» - -**Рекомендуемый паттерн** +Для ключа `ed25519:7GZg…` аккаунта `mike.near` (первый `social.near` FCK из аудита выше) это разрешается в транзакцию `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` на внешнем блоке tx `112057390`. Внешний signer — `app.herewallet.near`, это relayer HERE Wallet, и `mode: "delegated"` рассказывает остальную историю: relayer оплатил gas, но *авторизующий* ключ внутри Delegate — `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu`, full-access-ключ `mike.near`, который подписал сам `AddKey`. Это та разница meta-tx, которую верхнеуровневый `signer_id` в одиночку скрыл бы. -- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. -- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. -- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. - -## Механика аккаунтов и ключей +`add_key_receipt` замыкает картину: `AddKey` выполнился в блоке `112057392`, через два блока после внешней tx, потому что Delegate прыгает из shard relayer в shard целевого аккаунта. Расширьте окно `-20/+5`, если ключом с момента создания пользовались активно. -Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. +### Зарегистрировать FT-хранилище при необходимости и затем перевести токены -### Проверить и удалить старые function-call-ключи Near Social +Токены NEP-141 требуют, чтобы каждый получатель предварительно зарегистрировал storage на контракте, прежде чем сможет держать баланс. Два view-вызова авторитетно отвечают на вопрос регистрации *до* отправки — пропуск этой проверки и есть причина, по которой `ft_transfer` в итоге тихо возвращается отправителю. -Есть старые function-call-ключи для `social.near`? Сначала найдите нужный ключ, потом удалите один конкретный. +```bash +RPC_URL=https://rpc.testnet.fastnear.com +TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +RECEIVER_ACCOUNT_ID=mike.testnet - Ход - Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. +ACCOUNT_ARGS_B64="$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" - 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. - 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. - 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. +REGISTERED="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_of",args_base64:$args,finality:"final"} + }')" \ + | jq '(.result.result | implode | fromjson) != null')" -**Ход** +MIN_DEPOSIT="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_bounds",args_base64:"e30=",finality:"final"} + }')" \ + | jq -r '.result.result | implode | fromjson | .min')" -- Через сам RPC получаете полный список access key аккаунта. -- Сужаете этот список до function-call-ключей, привязанных к `social.near`. -- Точно проверяете один выбранный ключ перед удалением. -- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. +jq -n --argjson registered "$REGISTERED" --arg min "$MIN_DEPOSIT" '{ + registered: $registered, + min_storage_deposit_yocto: $min +}' +``` -Сразу важны два ограничения: +Для зафиксированного testnet-контракта `storage_balance_of({account_id: "mike.testnet"})` возвращает `null` (не зарегистрирован), а `storage_balance_bounds` возвращает `{min: "1250000000000000000000", max: "1250000000000000000000"}` — плоскую комиссию регистрации 0.00125 NEAR. Это собственный ответ контракта, и большего на read-стороне до записи не нужно. -- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. -- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». +Write-сторона — это две подписанных function call (near-api-js `transactions.functionCall` или любая NEAR-библиотека подписи работает одинаково): -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export SOCIAL_RECEIVER_ID=social.near -export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' -export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' -export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' -``` +- `storage_deposit({account_id: "", registration_only: true})` с депозитом `` yocto и 100 Tgas — пропустите, если `registered: true`. +- `ft_transfer({receiver_id: "", amount: "", memo: "..."})` с депозитом 1 yocto (требует NEP-141) и 100 Tgas. -1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. +Отправьте каждую подписанную транзакцию через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) с `wait_until: "FINAL"`. После этого подтвердите через собственный view-метод контракта — индексированная история не нужна, чтобы доказать, что перевод закрепился: ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"ft_balance_of",args_base64:$args,finality:"final"} }')" \ - | tee /tmp/fastnear-access-keys.json >/dev/null - -jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' - .result.keys[] - | select((.access_key.permission | type) == "object") - | select(.access_key.permission.FunctionCall.receiver_id == $receiver) - | { - public_key, - nonce: .access_key.nonce, - receiver_id: .access_key.permission.FunctionCall.receiver_id, - method_names: .access_key.permission.FunctionCall.method_names, - allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") - } -' /tmp/fastnear-access-keys.json + | jq '{receiver_balance: (.result.result | implode | fromjson)}' ``` -Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. +## Чтение контрактов и сырой state -2. Ещё раз проверьте конкретный ключ перед удалением. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$DELETE_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } - }')" \ - | jq '{nonce: .result.nonce, permission: .result.permission}' -``` +### Как прочитать сырое storage контракта напрямую? -3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. +Два RPC-метода отвечают на один и тот же вопрос о counter с разных слоёв: `view_state` достаёт сырые байты trie без запуска кода, а `call_function` запускает собственный view-метод контракта. Когда они совпадают, вы доказали, что view-метод контракта соответствует его сохранённому состоянию. ```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - is_function_call: true, - limit: 10 - }')" \ - | jq '{ - account_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_success - } - ] - }' -``` - -Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. +RPC_URL=https://rpc.testnet.fastnear.com +CONTRACT_ID=counter.near-examples.testnet -4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. +RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_state",account_id:$contract,prefix_base64:"U1RBVEU=",finality:"final"} + }')" \ + | jq -r '.result.values[0].value')" -Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. +RAW_I8="$(python3 -c "import base64,sys;print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" -```bash -SIGNED_TX_BASE64="$( - node --input-type=module <<'EOF' - -const { - ACCOUNT_ID, - NETWORK_ID = 'mainnet', - RPC_URL = 'https://rpc.mainnet.fastnear.com', - DELETE_PUBLIC_KEY, - FULL_ACCESS_PUBLIC_KEY, - FULL_ACCESS_PRIVATE_KEY, -} = process.env; - -for (const name of [ - 'ACCOUNT_ID', - 'DELETE_PUBLIC_KEY', - 'FULL_ACCESS_PUBLIC_KEY', - 'FULL_ACCESS_PRIVATE_KEY', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} +METHOD_VALUE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"get_num",args_base64:"e30=",finality:"final"} + }')" \ + | jq -r '.result.result | implode | fromjson')" -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} +jq -n --arg raw_b64 "$RAW_B64" --argjson raw_i8 "$RAW_I8" --argjson method "$METHOD_VALUE" '{ + raw_state_b64: $raw_b64, + raw_state_decoded: $raw_i8, + view_method_value: $method, + agree: ($raw_i8 == $method) +}' +``` -const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); -const derivedPublicKey = keyPair.getPublicKey().toString(); +Для живого counter `view_state` по ключу `STATE` (base64 `U1RBVEU=`) возвращает `"CQ=="` — один байт `0x09`, декодируется как signed i8 в `9`; `get_num` тоже возвращает `9`. Они совпадают, потому что контракт хранит `val: i8` по этому ключу. `signed=True` важен: отрицательный counter выглядел бы как `"/w=="` (байт `0xff` → i8 `-1`, а не u8 `255`). -if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { - throw new Error( - `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` - ); -} +`view_state` — правильный инструмент, когда у контракта нет view-метода для нужных данных, когда нужно сверить view-метод с реальным storage или когда нужна семья ключей, которую контракт не раскрывает публично. Для всего остального `call_function` требует меньше церемоний. Если следующий вопрос становится историческим, а не текущим, расширяйте поверхность до [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). -const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); +## NEAR Social и точные чтения BOS -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: ACCOUNT_ID, - public_key: FULL_ACCESS_PUBLIC_KEY, - finality: 'final', -}); +Оставайтесь на точных чтениях SocialDB и on-chain-проверках готовности — пока вопрос не станет историческим. -const block = await rpc('block', { finality: 'final' }); - -const transaction = transactions.createTransaction( - ACCOUNT_ID, - utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), - ACCOUNT_ID, - BigInt(accessKey.nonce) + 1n, - [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -)" -``` +### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? -5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. +`social.near` знает две вещи, о которых UI кошелька может только догадываться: сколько storage осталось у каждого аккаунта и разрешена ли делегированному signer запись под этим аккаунтом. Два view-вызова сворачивают вопрос готовности к одному boolean. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near # account you're writing under +SIGNER_ACCOUNT_ID=mike.near # account signing the transaction -6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. +STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -```bash -if curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } +STORAGE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$STORAGE_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"get_account_storage",args_base64:$args,finality:"final"} }')" \ - | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' - .result.keys[] - | select(.public_key == $public_key) - ' >/dev/null; then - echo "Key is still present: $DELETE_PUBLIC_KEY" + | jq '.result.result | implode | fromjson')" + +if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then + PERMISSION=true else - echo "Key deleted: $DELETE_PUBLIC_KEY" + PERM_ARGS_B64="$(jq -nc --arg pred "$SIGNER_ACCOUNT_ID" --arg key "$ACCOUNT_ID" '{predecessor_id:$pred,key:$key}' | base64 | tr -d '\n')" + PERMISSION="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$PERM_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"is_write_permission_granted",args_base64:$args,finality:"final"} + }')" \ + | jq '.result.result | implode | fromjson')" fi + +jq -n --argjson storage "$STORAGE" --argjson permission "$PERMISSION" \ + --arg account_id "$ACCOUNT_ID" --arg signer "$SIGNER_ACCOUNT_ID" '{ + account_id: $account_id, + signer_account_id: $signer, + storage: $storage, + permission_granted: $permission, + ready_to_publish: (($storage.available_bytes // 0) > 0 and $permission) + }' ``` -**Когда переходить дальше** +Для `mike.near`, подписывающего под собой, это возвращает `storage: {used_bytes: 139803, available_bytes: 83891}`, `permission_granted: true` (владельческая запись) и `ready_to_publish: true`. Если `storage` приходит как `null` или `available_bytes: 0`, аккаунту нужен `storage_deposit` на `social.near`, прежде чем новая запись сможет закрепиться. Если signer отличается от цели, ветка permission спрашивает `is_write_permission_granted({predecessor_id, key})` — тот же on-chain-ответ, который dapp видит, прежде чем писать от имени пользователя. Полную поверхность контракта см. в [SocialDB API](https://github.com/NearSocial/social-db#api). -Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. +### Что `mob.near/widget/Profile` содержит прямо сейчас? -### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? +SocialDB хранит BOS-виджеты как ключи `/widget/` на `social.near`. Один `keys` с типом возврата `BlockHeight` возвращает каталог плюс якоря последней записи по каждому виджету; один `get` возвращает точный исходник. -Ключ уже виден на аккаунте? Вернитесь к транзакции `AddKey`, которая его создала. +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mob.near +WIDGET_NAME=Profile - Ход - Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. +KEYS_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} +}' | base64 | tr -d '\n')" - 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. - 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. - 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$KEYS_ARGS" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"keys",args_base64:$args,finality:"final"} + }')" \ + | jq --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" ' + .result.result | implode | fromjson | .[$account_id].widget as $map + | { + total_widgets: ($map | length), + most_recently_written: ($map | to_entries | sort_by(-.value) | .[0:5] | map({widget: .key, last_write_block: .value})), + target_last_write_block: $map[$widget] + }' + +GET_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget)] +}' | base64 | tr -d '\n')" + +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$GET_ARGS" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"get",args_base64:$args,finality:"final"} + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" ' + .result.result | implode | fromjson | .[$account_id].widget[$widget] | split("\n")[0:20] | join("\n")' +``` -**Ход** +Для `mob.near` каталог показывает 264 виджета; `Profile` последний раз записывался в блоке `86494825` — годами ранее, стабильно с тех пор — и исходник начинается с `const accountId = props.accountId ?? context.accountId;`. Тип возврата `BlockHeight` ничего не стоит дополнительно и превращает листинг ключей в дешёвую проверку актуальности. Сохраните блок последней записи, если позже захотите доказать, *какая транзакция* записала именно эту версию — передайте его в [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs). -- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. -- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. -- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. -- Подтягиваете кандидата по транзакциям и различаете три разных ключа: - - ключ, который был добавлен - - public key верхнеуровневого signer - - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` +## Частые ошибки -Сразу важны три детали про nonce: +- Начинать в RPC, когда пользователю нужна сводка по активам или индексированная история. +- Забывать переключаться с обычного RPC на archival RPC для более старого state. +- Считать browser auth в UI документации продовым backend-паттерном. +- Оставаться в низкоуровневых вызовах статуса транзакции, когда вопрос уже стал forensic или историческим. -- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. -- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. -- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. +## Связанные страницы -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [Auth & Access](https://docs.fastnear.com/ru/auth) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) -# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: -# export ACCOUNT_ID=mike.near -# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' -``` +--- -1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. +## Снапшоты для валидаторов -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$TARGET_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } - }')" \ - | tee /tmp/key-origin-view.json >/dev/null +- HTML-маршрут: https://docs.fastnear.com/ru/snapshots +- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots.md -CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" -ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" -SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" -SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" +**Источник:** [https://docs.fastnear.com/ru/snapshots](https://docs.fastnear.com/ru/snapshots) -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_public_key "$TARGET_PUBLIC_KEY" \ - --argjson current_nonce "$CURRENT_NONCE" \ - --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ - --argjson search_from "$SEARCH_FROM" \ - --argjson search_to "$SEARCH_TO" \ - --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ - account_id: $account_id, - target_public_key: $target_public_key, - current_nonce: $current_nonce, - estimated_receipt_block: $estimated_receipt_block, - search_from_tx_block_height: $search_from, - search_to_tx_block_height: $search_to, - permission: ($permission | fromjson) - }' -``` +# Снапшоты блокчейна -Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. +Этот раздел — для операторов узлов, которые поднимают или восстанавливают инфраструктуру NEAR. Это не поверхность для прикладных данных. Если задача — читать балансы, историю, блоки или состояние контракта, используйте документацию API и RPC, а не сценарии со снапшотами. -2. Ищите историю аккаунта только внутри этого диапазона блоков. +:::warning[Бесплатные снапшоты устарели] +Бесплатные снапшоты данных nearcore больше не выпускаются. -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_tx_block_height "$SEARCH_FROM" \ - --argjson to_tx_block_height "$SEARCH_TO" '{ - account_id: $account_id, - is_real_signer: true, - from_tx_block_height: $from_tx_block_height, - to_tx_block_height: $to_tx_block_height, - desc: false, - limit: 50 - }')" \ - | tee /tmp/key-origin-candidates.json >/dev/null +Infrastructure Committee и Near One рекомендуют Epoch Sync вместе с децентрализованной синхронизацией состояния. Актуальные рекомендации и режим подъёма смотрите на [NEAR Nodes](https://near-nodes.io). +::: -jq '{ - txs_count, - candidate_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_signer, - is_real_signer, - is_predecessor, - is_receiver - } - ] -}' /tmp/key-origin-candidates.json -``` +## Используйте этот раздел, когда -Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. +- нужно поднять узел mainnet или testnet из данных снапшота +- идёт восстановление RPC- или архивного узла +- уже известно, что нужен путь загрузки снапшота FastNear -3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. +## Не используйте этот раздел, когда -```bash -TX_HASHES_JSON="$( - jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json -)" +- идёт запрос данных цепочки для приложения +- нужны свежие блоки, балансы, история или состояние контракта +- нужны общие рекомендации по продуктовому API, а не настройка оператором -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ - | tee /tmp/key-origin-transactions.json >/dev/null - -jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "direct", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $tx.transaction.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }), - ($tx.transaction.actions[]? - | .Delegate? - | .delegate_action as $delegate - | $delegate.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "delegated", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $delegate.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - tx_block_hash: $tx.execution_outcome.block_hash, - receiver_id: $tx.transaction.receiver_id - } + . -' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json -``` +В этих случаях используйте [Справочник 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). -Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. +## Перед загрузкой -Для примерного ключа `mike.near` выше совпадение оказывается делегированным: +- Сначала выберите сеть: mainnet или testnet. +- Решите, нужны обычные данные RPC или архивные. +- Убедитесь, что понимаете, где должны лежать горячие и холодные данные, прежде чем стартовать архивную загрузку. -- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` -- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` -- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` -- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` +- Установите `rclone` — скрипты загрузки от него зависят. -4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. +:::info[Установка `rclone`] +Установите `rclone` командой: ```bash -ADD_KEY_RECEIPT_ID="$( - jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | .receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) - | .receipt.receipt_id - ' /tmp/key-origin-transactions.json | head -n 1 -)" - -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receipt_block_height: .receipt.block_height, - tx_block_height: .receipt.tx_block_height, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - transaction_hash: .receipt.transaction_hash - }' +sudo -v ; curl https://rclone.org/install.sh | sudo bash ``` +::: + +## Что покрывает каждый путь -Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. +- **Mainnet** включает оптимизированный `fast-rpc`, обычный RPC и архивные пути загрузки для горячих и холодных данных. +- **Testnet** включает RPC и архивные пути снапшотов для операторов testnet. -**Когда переходить дальше** +Требования к узлам смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near), а исходники скриптов загрузки, которые используются в этих руководствах, — в [fastnear/static](https://github.com/fastnear/static). -Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. +## Нужен сценарий? -### Проверить регистрацию FT storage и затем перевести токены +Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для практических примеров: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. -Нужно отправить FT безопасно? Сначала проверьте storage registration получателя. +## Выберите сеть - Ход - Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. + - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) + - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) - 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. - 02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит. - 03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог. +--- -**Сеть** +## Примеры snapshot -- testnet +- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/examples.md -**Официальные ссылки** +**Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) -- [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) -- [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) +## Пути восстановления mainnet -В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. +Выберите один класс — optimized `fast-rpc`, standard RPC или archival — и выполняйте только команды этого пути. Смешивание классов приводит к несогласованным данным узла. -**Ход** +FastNear поддерживает эти скрипты ради скорости восстановления. Если в вашей среде требуется review изменений, скачайте скрипт и проверьте его перед запуском (вместо прямой передачи через pipe в `bash`). -- Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. -- При необходимости получаете минимальный размер storage deposit. -- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. -- Подтверждаете баланс получателя тем же view-методом самого контракта. +### Optimized mainnet `fast-rpc` ```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet -export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' -export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' -export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' +DATA_PATH=~/.near/data + +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash ``` -1. Проверьте, зарегистрирован ли получатель на FT-контракте. +### Standard mainnet RPC ```bash -STORAGE_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$STORAGE_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "storage_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/ft-storage-balance.json >/dev/null +DATA_PATH=~/.near/data -jq '{ - registered: ((.result.result | implode | fromjson) != null), - storage_balance: (.result.result | implode | fromjson) -}' /tmp/ft-storage-balance.json +curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ + | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet bash ``` -2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. +### Archival mainnet -```bash -MIN_STORAGE_YOCTO="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_CONTRACT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "storage_balance_bounds", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee /tmp/ft-storage-bounds.json \ - | jq -r '.result.result | implode | fromjson | .min' -)" - -printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" -``` - -3. Определите одну переиспользуемую функцию подписи для function-call к контракту. - -Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. - -```bash -sign_function_call() { - METHOD_NAME="$1" \ - ARGS_JSON="$2" \ - DEPOSIT_YOCTO="$3" \ - GAS_TGAS="$4" \ - node --input-type=module <<'EOF' - -const { - NETWORK_ID = 'testnet', - RPC_URL = 'https://rpc.testnet.fastnear.com', - TOKEN_CONTRACT_ID, - SENDER_ACCOUNT_ID, - SENDER_PUBLIC_KEY, - SENDER_PRIVATE_KEY, - METHOD_NAME, - ARGS_JSON, - DEPOSIT_YOCTO = '0', - GAS_TGAS = '100', -} = process.env; - -for (const name of [ - 'TOKEN_CONTRACT_ID', - 'SENDER_ACCOUNT_ID', - 'SENDER_PUBLIC_KEY', - 'SENDER_PRIVATE_KEY', - 'METHOD_NAME', - 'ARGS_JSON', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); -const signer = await InMemorySigner.fromKeyPair( - NETWORK_ID, - SENDER_ACCOUNT_ID, - keyPair -); - -const derivedPublicKey = keyPair.getPublicKey().toString(); -if (derivedPublicKey !== SENDER_PUBLIC_KEY) { - throw new Error( - `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: SENDER_ACCOUNT_ID, - public_key: SENDER_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const action = transactions.functionCall( - METHOD_NAME, - Buffer.from(ARGS_JSON), - BigInt(GAS_TGAS) * 10n ** 12n, - BigInt(DEPOSIT_YOCTO) -); - -const transaction = transactions.createTransaction( - SENDER_ACCOUNT_ID, - utils.PublicKey.fromString(SENDER_PUBLIC_KEY), - TOKEN_CONTRACT_ID, - BigInt(accessKey.nonce) + 1n, - [action], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - SENDER_ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -} -``` - -4. При необходимости сначала зарегистрируйте storage для получателя. - -```bash -if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then - SIGNED_TX_BASE64="$( - sign_function_call \ - storage_deposit \ - "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id, - registration_only: true - }')" \ - "$MIN_STORAGE_YOCTO" \ - 100 - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash - }' -fi -``` - -5. После готовности storage переведите FT. - -```bash -SIGNED_TX_BASE64="$( - sign_function_call \ - ft_transfer \ - "$(jq -nc \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ - receiver_id: $receiver_id, - amount: $amount, - memo: "FastNear RPC example" - }')" \ - 1 \ - 100 -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Подтвердите FT-баланс получателя тем же view-методом контракта. +Для archival нужны две загрузки из *одного и того же* среза снапшота. Зафиксируйте одно значение `LATEST` и переиспользуйте его и для hot-, и для cold-data — смешивание высот даёт внутренне несогласованный набор данных и удивляет nearcore на этапе настройки. ```bash -RECEIVER_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - receiver_balance: (.result.result | implode | fromjson) - }' -``` - -**Когда переходить дальше** - -Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. - -## Чтения контракта и сырое состояние - -Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» - -### Как прочитать сырое состояние контракта напрямую? - -Нужен raw storage, а не только view-метод? Читайте состояние напрямую, затем сравнивайте с view. - -Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`. Значение может меняться; важна последовательность чтений: сначала raw storage, потом проверка через view-метод. - -- `view_state` читает сырой ключ `STATE` прямо из storage контракта -- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API - - Ход - Сначала прочитайте storage напрямую, а уже потом дайте контракту подтвердить тот же ответ через view-метод. - - 01RPC view_state читает сырой ключ STATE, не запуская код контракта. - 02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта. - 03RPC call_function get_num — это удобная перепроверка того, что прямое чтение storage и view-метод по-прежнему дают один и тот же ответ. - -Здесь важнее ментальная модель, чем сам счётчик: - -- `view_state` — это прямое чтение storage из trie -- `call_function` исполняет read-only-метод контракта -- оба способа могут ответить на один и тот же вопрос, но делают разную работу - -```mermaid -flowchart LR - S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] - R --> D["Декодировать base64 + Borsh"] - D --> N["Знаковое значение счётчика"] - C["RPC call_function get_num"] --> J["JSON-результат метода"] - N --> X["Сравнить"] - J --> X - X --> A["Одно и то же текущее значение"] -``` +HOT_DATA_PATH=~/.near/data +COLD_DATA_PATH=/mnt/hdds/cold-data -**Ход** +LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" +echo "Latest archival mainnet snapshot block: $LATEST" -- Читаете сырой ключ `STATE` из storage контракта. -- Декодируете возвращённые байты в текущее знаковое значение счётчика. -- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. +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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash -```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export CONTRACT_ID=counter.near-examples.testnet -export STATE_PREFIX_BASE64=U1RBVEU= +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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash ``` -1. Сначала прочитайте сырое состояние контракта. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$CONTRACT_ID" \ - --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_state", - account_id: $account_id, - prefix_base64: $prefix_base64, - finality: "final" - } - }')" \ - | tee /tmp/counter-view-state.json >/dev/null +## Частые ошибки -jq '{ - block_height: .result.block_height, - key_base64: .result.values[0].key, - value_base64: .result.values[0].value -}' /tmp/counter-view-state.json +- Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. +- Выбирать archival-восстановление, когда достаточно standard или optimized RPC. +- Забывать про разделение hot/cold-хранилища для archival-данных. +- Переходить к командам до выбора сети и цели узла. -jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json -``` +## Связанные страницы -Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. +- [Обзор snapshot](https://docs.fastnear.com/ru/snapshots) +- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) +- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) +- [RPC Reference](https://docs.fastnear.com/ru/rpc) +- [NEAR Data API](https://docs.fastnear.com/ru/neardata) -2. Декодируйте байты значения в знаковое число счётчика. +--- -```bash -RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" +## mainnet -python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . +- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/mainnet +- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/mainnet.md -raw = base64.b64decode(sys.argv[1]) +**Источник:** [https://docs.fastnear.com/ru/snapshots/mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) -print(json.dumps({ - "value_base64": sys.argv[1], - "bytes": list(raw), - "hex": raw.hex(), - "signed_i8": int.from_bytes(raw, "little", signed=True), - "unsigned_u8": int.from_bytes(raw, "little", signed=False), -})) -PY -``` +# Mainnet -Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. +## Оптимизированный снапшот mainnet -Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. +Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и подходит для более узких задач. -Переиспользуемый рецепт здесь короткий: +Узлы с достаточными ресурсами могут использовать значение `$RPC_TYPE=fast-rpc`. По умолчанию используется `rpc`. -- `view_state` возвращает сырые байты в base64 -- вы декодируете эти байты по известной схеме хранения контракта -- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема +Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: -3. Теперь спросите контракт более привычным способом и сравните. +- `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` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$CONTRACT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_num", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee /tmp/counter-call-function.json >/dev/null +**Выполните эту команду, чтобы скачать RPC-снапшот mainnet:** -jq '{ - block_height: .result.block_height, - view_method_value: (.result.result | implode | fromjson) -}' /tmp/counter-call-function.json -``` +:::info +Будут заданы следующие переменные окружения: +- `DATA_PATH=~/.near/data` — стандартный путь nearcore +- `CHAIN_ID=mainnet` — явно выбирает данные mainnet +- `RPC_TYPE=fast-rpc` — включает оптимизированный режим +::: -4. Сравните оба ответа напрямую. +`RPC Mainnet Snapshot » ~/.near/data`: ```bash -RAW_STATE_NUMBER="$( - python3 - "$RAW_VALUE_BASE64" <<'PY' - -raw = base64.b64decode(sys.argv[1]) -print(int.from_bytes(raw, "little", signed=True)) -PY -)" - -VIEW_METHOD_NUMBER="$( - jq -r '.result.result | implode | fromjson' /tmp/counter-call-function.json -)" - -jq -n \ - --argjson raw_state "$RAW_STATE_NUMBER" \ - --argjson view_method "$VIEW_METHOD_NUMBER" '{ - raw_state: $raw_state, - view_method: $view_method, - agrees_now: ($raw_state == $view_method) - }' +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 ``` -Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: - -- `view_state` ответил на вопрос, прочитав storage напрямую -- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта - -**Когда переходить дальше** - -Используйте `view_state`, когда настоящий вопрос относится к точному storage, отсутствующему view-методу или проверке известного семейства ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). - -## Трассировка чанков и шардов - -Начинайте отсюда, когда вопрос уже не просто «транзакция прошла или нет?», а «какой именно чанк на шарде исполнил каждый шаг работы?» - -### Проследить, как сгенерированная `Transfer`-receipt переходит из одного чанка на шарде в другой - -Нужен cross-shard handoff, а не только исходная транзакция? Смотрите чанки. - -Зафиксированный mainnet-пример: - -- транзакции `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` от `7419369993.tg` к `game.hot.tg` с вызовом `l2_claim` -- исходному чанку `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` на шарде `11` в блоке `194623170` -- чанку первой receipt `FJWpAYzVXbZwqJUbGXELTnnBBkdvc6W8vWkwuUA3Zwz9` на шарде `11` в блоке `194623171` -- сгенерированной `Transfer`-receipt `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` -- конечному чанку `EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU` на шарде `6` в блоке `194623172` - - Ход - Сначала восстановите receipt-цепочку, потом напрямую посмотрите на сгенерированную receipt, а затем привяжите каждый шаг к тому чанку на шарде, который действительно нёс эту работу. - - 01RPC EXPERIMENTAL_tx_status быстро показывает граф receipts и в какие следующие блоки перешла работа. - 02RPC EXPERIMENTAL_receipt позволяет посмотреть на тело сгенерированной receipt напрямую, а не выводить его только из логов. - 03RPC chunk по блоку и шарду или по хешу чанка доказывает, какая именно единица исполнения на шарде нёсла каждый шаг. +## RPC-снапшот mainnet -Оба experimental-метода здесь очень уместны: `EXPERIMENTAL_tx_status` быстро находит граф receipts, а `EXPERIMENTAL_receipt` показывает тело сгенерированной receipt ещё до того, как вы привяжете её обратно к чанкам. +Это стандартный способ получить снапшот без оптимизированного режима из предыдущего раздела. -```mermaid -flowchart LR - A["Tx 8xrc...
блок 194623170
чанк Bfyd...
шард 11"] --> B["Receipt AFC2...
блок 194623171
чанк FJWp...
шард 11
логи ft_mint"] - B --> C["Сгенерированная receipt TtRn...
Transfer 1800930478788300000000 yoctoNEAR"] - C --> D["Чанк EPau...
блок 194623172
шард 6
receipt исполняется"] -``` +Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: -**Ход** +- `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` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. -- Сначала восстанавливаете receipt-цепочку из транзакции. -- Напрямую смотрите на тело сгенерированной `Transfer`-receipt. -- Используете координаты блока и шарда там, где они уже известны. -- Используете хеш чанка там, где другой инструмент уже выдал точный конечный чанк. +**Выполните эту команду, чтобы скачать RPC-снапшот mainnet:** -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_HASH=8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP -export SIGNER_ACCOUNT_ID=7419369993.tg -export ORIGIN_BLOCK_HEIGHT=194623170 -export ORIGIN_SHARD_ID=11 -export RECEIPT_BLOCK_HEIGHT=194623171 -export RECEIPT_SHARD_ID=11 -export GENERATED_RECEIPT_ID=TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4 -export DESTINATION_CHUNK_HASH=EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU -``` +:::info +Будут заданы следующие переменные окружения: +- `DATA_PATH=~/.near/data` — стандартный путь nearcore +- `CHAIN_ID=mainnet` — явно выбирает данные mainnet +::: -1. Начните с `EXPERIMENTAL_tx_status`, чтобы сначала увидеть граф receipts, а уже потом думать о чанках. +`RPC Mainnet Snapshot » ~/.near/data`: ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: [$tx_hash, $signer_account_id] - }')" \ - | tee /tmp/chunk-trace-status.json >/dev/null - -jq '{ - final_execution_status: .result.final_execution_status, - transaction_handoff: .result.transaction_outcome.outcome.status, - receipts: ( - .result.receipts_outcome - | map({ - receipt_id: .id, - executor_id: .outcome.executor_id, - block_hash, - status: .outcome.status - }) - ) -}' /tmp/chunk-trace-status.json +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 ``` -На что смотреть: - -- подписанная транзакция передаёт работу в receipt `AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y` -- позже в том же графе receipts исполняется `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` для `7419369993.tg` -- уже одного tx status достаточно, чтобы увидеть: настоящая работа продолжилась после исходной подписанной транзакции - -2. Посмотрите на сгенерированную receipt напрямую, чтобы доказать, что это действительно `Transfer`-receipt. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$GENERATED_RECEIPT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_receipt", - params: { - receipt_id: $receipt_id - } - }')" \ - | tee /tmp/chunk-trace-receipt.json >/dev/null - -jq '{ - predecessor_id: .result.predecessor_id, - receiver_id: .result.receiver_id, - signer_id: .result.receipt.Action.signer_id, - signer_public_key: .result.receipt.Action.signer_public_key, - actions: .result.receipt.Action.actions -}' /tmp/chunk-trace-receipt.json -``` +## Архивный снапшот mainnet -Именно здесь история по шардам становится конкретной: эта цепочка исполнения контракта сгенерировала `Transfer` action receipt от `system` к `7419369993.tg` с депозитом `1800930478788300000000`. +:::warning +**Требует много времени и места на диске.** -3. Используйте `chunk` по блоку и шарду, чтобы найти исходную подписанную транзакцию на шарде `11`. +Подготовьтесь к очень большому объёму загрузки и длительному времени выполнения. -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --argjson block_id "$ORIGIN_BLOCK_HEIGHT" \ - --argjson shard_id "$ORIGIN_SHARD_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - block_id: $block_id, - shard_id: $shard_id - } - }')" \ - | jq --arg tx_hash "$TX_HASH" '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created - }, - matching_transaction: ( - .result.transactions[] - | select(.hash == $tx_hash) - | { - hash, - signer_id, - receiver_id - } - ) - }' -``` +Размер снапшота составляет около 60 ТБ, и он содержит более 1 миллиона файлов. +::: -Это самый чистый use case для `chunk` по блоку и шарду: координаты уже известны, а вам нужна точная единица исполнения на шарде, которая несла исходную подписанную транзакцию. +Перед запуском скрипта загрузки можно задать следующие переменные окружения: -4. Оставайтесь на том же маршруте и посмотрите, как первая receipt исполняется в следующем блоке на том же шарде. +- `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` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --argjson block_id "$RECEIPT_BLOCK_HEIGHT" \ - --argjson shard_id "$RECEIPT_SHARD_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - block_id: $block_id, - shard_id: $shard_id - } - }')" \ - | jq '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created, - tx_root: .result.header.tx_root, - gas_used: .result.header.gas_used - }, - tx_count: (.result.transactions | length), - receipt_count: (.result.receipts | length), - matching_receipt: ( - .result.receipts[] - | select(.receipt_id == "AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y") - | { - receipt_id, - predecessor_id, - receiver_id - } - ) - }' -``` +По умолчанию скрипт ожидает следующие пути для данных: -Вот здесь chunks наконец становятся естественными: +- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` +- Cold data, которые можно хранить на HDD: `/mnt/nvme/data/cold-data` -- у чанка `tx_root = 11111111111111111111111111111111` -- `tx_count` равен `0` -- но шард всё равно жжёт gas и исполняет receipt `AFC2...` +**Выполните следующие команды, чтобы скачать архивный снапшот mainnet:** -То есть этот шард реально сделал работу в этом блоке, хотя новая подписанная транзакция прямо в самом чанке не появилась. +1. Получите высоту блока последнего снапшота: -5. Переключайтесь на `chunk` по хешу, когда другой инструмент уже выдал точный конечный чанк. +`Latest archival mainnet snapshot block`: ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg chunk_id "$DESTINATION_CHUNK_HASH" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - chunk_id: $chunk_id - } - }')" \ - | jq --arg receipt_id "$GENERATED_RECEIPT_ID" '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created, - tx_root: .result.header.tx_root, - gas_used: .result.header.gas_used - }, - tx_count: (.result.transactions | length), - receipt_count: (.result.receipts | length), - matching_receipt: ( - .result.receipts[] - | select(.receipt_id == $receipt_id) - | { - receipt_id, - predecessor_id, - receiver_id - } - ) - }' +LATEST=$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt") +echo "Latest snapshot block: $LATEST" ``` -Это и подтверждает cross-shard hop: - -- сгенерированная `Transfer`-receipt исполняется в чанке `EPau...` -- этот чанк живёт на шарде `6`, а не на шарде `11` -- подписанная транзакция стартовала на одном шарде, а следующая receipt завершилась уже на другом - -**Когда переходить дальше** - -Используйте [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда вы знаете координаты блока и шарда и хотите буквально спросить: «что этот шард исполнил в этом блоке?» Используйте [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. Используйте [EXPERIMENTAL_tx_status](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) и [EXPERIMENTAL_receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), когда настоящий вопрос относится к трассировке на уровне receipts. Если ещё нужны state changes и produced receipts, расширяйтесь в [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard). - -## Точные чтения NEAR Social и BOS - -Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. - -### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? - -Нужно понять «готово / не готово» до окна подписи? Проверьте storage и права до отправки. - - Ход - Спросите у social.near ровно о двух вещах, которые важны до подписи. - - 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. - 02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near. - 03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer. - -Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: - -- есть ли у целевого аккаунта storage на `social.near`? -- если есть, осталось ли там ещё место? -- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Ход** - -- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. -- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. -- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. -- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». +2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mike.near -export SIGNER_ACCOUNT_ID=mike.near -``` - -1. Сначала проверьте сам аккаунт signer. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } - }')" \ - | tee /tmp/social-publish-signer.json >/dev/null - -jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - signer_account_id: $signer_account_id, - amount: .result.amount, - locked: .result.locked, - storage_usage: .result.storage_usage -}' /tmp/social-publish-signer.json -``` - -Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. - -2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. - -```bash -SOCIAL_STORAGE_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_account_storage", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-account-storage.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - storage: (.result.result | implode | fromjson), - storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) -}' /tmp/social-account-storage.json -``` - -Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. - -3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. - -```bash -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - jq -n --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - signer_matches_target: true, - permission_granted: true, - reason: "owner write" - }' -else - WRITE_PERMISSION_ARGS_BASE64="$( - jq -nc \ - --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ - --arg key "$ACCOUNT_ID" '{ - predecessor_id: $predecessor_id, - key: $key - }' | base64 | tr -d '\n' - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - signer_matches_target: false, - permission_granted: (.result.result | implode | fromjson) - }' -fi -``` - -4. Сведите проверку storage и разрешения в один читаемый итог. - -```bash -AVAILABLE_BYTES="$( - jq -r ' - .result.result - | if length == 0 then "0" - else (implode | fromjson | .available_bytes // 0 | tostring) - end - ' /tmp/social-account-storage.json -)" - -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - PERMISSION_GRANTED=true -else - PERMISSION_GRANTED="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq -r '.result.result | implode | fromjson' - )" -fi - -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ - --argjson available_bytes "$AVAILABLE_BYTES" \ - --argjson permission_granted "$PERMISSION_GRANTED" '{ - account_id: $account_id, - signer_account_id: $signer_account_id, - storage_ready: ($available_bytes > 0), - permission_ready: $permission_granted, - ready_to_publish_now: (($available_bytes > 0) and $permission_granted) - }' -``` - -Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. - -**Когда переходить дальше** - -Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. - -### Что прямо сейчас содержит `mob.near/widget/Profile`? - -Нужен живой исходник виджета и последняя запись этого ключа? Оставайтесь на точных RPC-чтениях. - - Ход - Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. - - 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. - 02RPC call_function get читает точный исходник widget/Profile. - 03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples. - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Ход** - -- Спрашиваете у `social.near` каталог виджетов под `mob.near`. -- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. -- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. -- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mob.near -export WIDGET_NAME=Profile -``` - -1. Получите каталог виджетов и сохраните высоты блоков последней записи. - -```bash -WIDGET_KEYS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/widget/*")], - options: {return_type: "BlockHeight"} - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "keys", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-widget-keys.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | .[$account_id].widget - | to_entries - | sort_by(.value * -1) - | map({ - widget_name: .key, - last_write_block: .value - }) - | .[0:20] -' /tmp/social-widget-keys.json -``` - -2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. - -```bash -WIDGET_GET_ARGS_BASE64="$( - jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-widget-source.json >/dev/null - -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:25] - | join("\n") - ' /tmp/social-widget-source.json -``` - -3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. - -```bash -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - ' /tmp/social-widget-keys.json \ - | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" -``` - -На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. - -**Когда переходить дальше** - -Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. - -## Частые задачи - -### Проверить точное состояние аккаунта или ключа доступа - -**Начните здесь** - -- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для точных полей аккаунта. -- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. - -**Следующая страница при необходимости** - -- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» - -**Остановитесь, когда** - -- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. - -**Переходите дальше, когда** - -- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. -- Пользователя интересует не текущее состояние, а недавняя история активности. - -### Трассировать исполнение на уровне шарда через чанки - -**Начните здесь** - -- Начните с примера выше, если настоящий вопрос звучит как «какой чанк или шард вообще исполнил эту receipt?» -- [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда координаты блока и шарда уже известны. -- [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. - -**Следующая страница при необходимости** - -- [Experimental Receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), если нужно само тело сгенерированной receipt. -- [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard), если chunk payload уже недостаточен и ещё нужны state changes или produced receipts. -- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если вопрос превращается в более широкое async- или callback-расследование. - -**Остановитесь, когда** - -- Уже можно назвать, какой именно чанк и какой шард несли ту работу, которая была важна. - -**Переходите дальше, когда** - -- Пользователю нужны уже не детали исполнения на уровне шарда, а читаемая история транзакции. Тогда переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Проверить один точный блок или снимок состояния протокола - -**Начните здесь** - -- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. -- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» -- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. - -**Следующая страница при необходимости** - -- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. -- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» - -**Остановитесь, когда** - -- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. - -**Переходите дальше, когда** - -- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). -- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Что этот контракт возвращает прямо сейчас? - -**Начните здесь** - -- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» -- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. -- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. -- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» - -**Следующая страница при необходимости** - -- [FastNear API](https://docs.fastnear.com/ru/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. -- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» - -**Остановитесь, когда** - -- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. - -**Переходите дальше, когда** - -- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. -- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» - -### Отправить транзакцию и подтвердить результат - -**Начните здесь** - -- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. -- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. -- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. -- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» - -**Остановитесь, когда** - -- У вас уже есть результат отправки и нужный финальный статус. - -**Переходите дальше, когда** - -- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. -- Нужен уже не единичный статус, а более широкий сценарий расследования. - -## Частые ошибки - -- Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. -- Забывать переключаться с обычного RPC на архивный RPC для старого состояния. -- Воспринимать браузерную аутентификацию в интерфейсе документации как продовый паттерн для бэкенда. -- Продолжать пользоваться низкоуровневыми статусами транзакций, когда вопрос уже превратился в расследование или исторический разбор. - -## Полезные связанные страницы - -- [RPC Reference](https://docs.fastnear.com/ru/rpc) -- [Auth & Access](https://docs.fastnear.com/ru/auth) -- [FastNear API](https://docs.fastnear.com/ru/api) -- [Transactions API](https://docs.fastnear.com/ru/tx) -- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) -- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) - ---- - -## Снапшоты для валидаторов - -- HTML-маршрут: https://docs.fastnear.com/ru/snapshots -- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots.md - -**Источник:** [https://docs.fastnear.com/ru/snapshots](https://docs.fastnear.com/ru/snapshots) - -# Снапшоты блокчейна - -Этот раздел — для операторов узлов, которые поднимают или восстанавливают инфраструктуру NEAR. Это не поверхность для прикладных данных. Если задача — читать балансы, историю, блоки или состояние контракта, используйте документацию API и RPC, а не сценарии со снапшотами. - -:::warning[Бесплатные снапшоты устарели] -Бесплатные снапшоты данных nearcore больше не выпускаются. - -Infrastructure Committee и Near One рекомендуют Epoch Sync вместе с децентрализованной синхронизацией состояния. Актуальные рекомендации и режим подъёма смотрите на [NEAR Nodes](https://near-nodes.io). -::: - -## Используйте этот раздел, когда - -- нужно поднять узел mainnet или testnet из данных снапшота -- идёт восстановление RPC- или архивного узла -- уже известно, что нужен путь загрузки снапшота FastNear - -## Не используйте этот раздел, когда - -- идёт запрос данных цепочки для приложения -- нужны свежие блоки, балансы, история или состояние контракта -- нужны общие рекомендации по продуктовому API, а не настройка оператором - -В этих случаях используйте [Справочник 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 или архивные. -- Убедитесь, что понимаете, где должны лежать горячие и холодные данные, прежде чем стартовать архивную загрузку. - -- Установите `rclone` — скрипты загрузки от него зависят. - -:::info[Установка `rclone`] -Установите `rclone` командой: - -```bash -sudo -v ; curl https://rclone.org/install.sh | sudo bash -``` -::: - -## Что покрывает каждый путь - -- **Mainnet** включает оптимизированный `fast-rpc`, обычный RPC и архивные пути загрузки для горячих и холодных данных. -- **Testnet** включает RPC и архивные пути снапшотов для операторов testnet. - -Требования к узлам смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near), а исходники скриптов загрузки, которые используются в этих руководствах, — в [fastnear/static](https://github.com/fastnear/static). - -## Нужен сценарий? - -Используйте [примеры снапшотов](https://docs.fastnear.com/ru/snapshots/examples) для практических примеров: выбора между оптимизированным `fast-rpc`, стандартным восстановлением RPC и архивными путями с разделением горячих и холодных данных. - -## Выберите сеть - - - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) - - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) - ---- - -## Примеры Snapshot - -- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/examples -- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/examples.md - -**Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) - -## Быстрый старт - -Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. - -Эти helper-скрипты поддерживаются FastNear и оптимизированы под скорость восстановления. Если в вашей среде нужен review изменений, сначала скачайте скрипт, проверьте его и только потом запускайте, вместо прямого piping в `bash`. - -```bash -DATA_PATH=~/.near/data - -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ - | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash -``` - -## Пример - -### Выбрать и выполнить правильный сценарий восстановления mainnet - - Ход - Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него. - - 01Сначала решите, нужен ли вам optimized fast-rpc, обычный RPC или архивный режим. - 02Если нужен архив, сначала зафиксируйте одну точную высоту snapshot-блока и дальше переиспользуйте только её. - 03Выполняйте только команды выбранного пути и не смешивайте optimized, standard и archival шаги в одном сценарии. - -### Минимальная команда для optimized mainnet `fast-rpc` - -```bash -DATA_PATH=~/.near/data - -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ - | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash -``` - -### Минимальная команда для стандартного mainnet RPC - -```bash -DATA_PATH=~/.near/data - -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ - | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet bash -``` - -### Shell-сценарий архивного восстановления mainnet -Для архивного восстановления сначала получите одну высоту snapshot и переиспользуйте её для hot- и cold-data. - -**Ход** - -- Один раз получаете последнюю высоту архивного снапшота mainnet. -- Сохраняете её в `LATEST`. -- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. - -```bash -HOT_DATA_PATH=~/.near/data -COLD_DATA_PATH=/mnt/hdds/cold-data - -LATEST="$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt")" -echo "Latest archival mainnet snapshot block: $LATEST" - -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="$HOT_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" 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="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash -``` - -**Когда переходить дальше** - -Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. - -## Частые ошибки - -- Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. -- Выбирать архивное восстановление, когда достаточно обычного или optimized RPC. -- Забывать про разделение hot/cold-данных для архивного режима. -- Переходить к командам до выбора сети и цели узла. - -## Полезные связанные страницы - -- [Обзор снапшотов](https://docs.fastnear.com/ru/snapshots) -- [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) -- [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) -- [RPC Reference](https://docs.fastnear.com/ru/rpc) -- [NEAR Data API](https://docs.fastnear.com/ru/neardata) - ---- - -## mainnet - -- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/mainnet -- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/mainnet.md - -**Источник:** [https://docs.fastnear.com/ru/snapshots/mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) - -# 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` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -**Выполните эту команду, чтобы скачать 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 миллиона файлов. -::: - -Перед запуском скрипта загрузки можно задать следующие переменные окружения: - -- `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` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -По умолчанию скрипт ожидает следующие пути для данных: - -- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` -- Cold data, которые можно хранить на HDD: `/mnt/nvme/data/cold-data` - -**Выполните следующие команды, чтобы скачать архивный снапшот mainnet:** - -1. Получите высоту блока последнего снапшота: - -`Latest archival mainnet snapshot block`: - -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` - -2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. - -:::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` — указывает блок снапшота -::: - -`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 -``` - ---- - -## testnet - -- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/testnet -- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/testnet.md - -**Источник:** [https://docs.fastnear.com/ru/snapshots/testnet](https://docs.fastnear.com/ru/snapshots/testnet) - -# Testnet - -## RPC-снапшот 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 -::: - -`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 - -:::warning -**Требует много времени и места на диске.** - -Подготовьтесь к большому объёму загрузки и длительному времени выполнения. -::: - -Перед запуском скрипта загрузки можно задать следующие переменные окружения: - -- `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` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -По умолчанию скрипт ожидает следующий путь для данных: - -- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` - -**Выполните следующие команды, чтобы скачать архивный снапшот testnet:** - -1. Получите высоту блока последнего снапшота: - -`Latest archival testnet snapshot block`: - -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/testnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` - -2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. - -:::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 -``` - ---- - -## API переводов - -- HTML-маршрут: https://docs.fastnear.com/ru/transfers -- Markdown-маршрут: https://docs.fastnear.com/ru/transfers.md - -**Источник:** [https://docs.fastnear.com/ru/transfers](https://docs.fastnear.com/ru/transfers) - -# API переводов - -API переводов — это самая узкая поверхность истории FastNear. Стартуйте здесь, когда вопрос именно о движении активов, а не о более широкой истории исполнения за этим движением. - -## Базовый URL - -```bash title="Transfers API Mainnet" -https://transfers.main.fastnear.com -``` - -Эта поверхность сейчас доступна только в mainnet. `?network=testnet` не переключает бэкенд. - -## Используйте этот API, когда - -- нужна история переводов NEAR или FT-токенов по аккаунту -- строятся ленты кошелька или представления активности только по переводам -- отвечаете на вопросы поддержки или комплаенса про отправку и получение - -## Не стартуйте здесь, когда - -- нужна более широкая история транзакций или квитанций -- нужны балансы, активы, NFT или представления стейкинга -- нужен трафик testnet - -Используйте [Транзакции API](https://docs.fastnear.com/ru/tx) для более широкой истории исполнения и [FastNear API](https://docs.fastnear.com/ru/api) для ответов в стиле состояния аккаунта. - -## Минимально полезные входы - -- `account_id` -- опциональные фильтры: актив, направление, сумма или время -- нужно пользователю несколько событий или более длинный обзор истории - -## Рабочий цикл по умолчанию - -1. Начните с [Запроса переводов](https://docs.fastnear.com/ru/transfers/query), используя самый узкий набор фильтров, который всё ещё отвечает на вопрос. -2. Читайте возвращённые события как историю только переводов. Не пересобирайте полную хронологию квитанций, пока пользователь не попросит. -3. Переиспользуйте непрозрачный `resume_token` ровно в том виде, в каком его вернул сервис, при листании дальше. -4. Остановитесь, как только можете ответить, кто, что, когда и в каком активе отправил. - -## Аутентификация и доступность - -- Публичные чтения истории переводов часто работают и без ключа. -- Если вы стандартизируете один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. -- Ответы включают непрозрачный `resume_token` для пагинации. -- Сервис сейчас доступен только в mainnet. - -## Расширяйтесь, только если - -- пользователь начинает спрашивать о квитанциях или действиях кроме переводов -- пользователь хочет более широкий контекст транзакции за переводом -- пользователь на самом деле спрашивает о балансах или текущих активах, а не о движении - -Тогда расширяйтесь на [Транзакции API](https://docs.fastnear.com/ru/tx) или [FastNear API](https://docs.fastnear.com/ru/api), а не перегружайте представление переводов. - -## Типовые стартовые страницы - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) — лента по аккаунту с фильтрами по направлению, активу, сумме и времени - -## Нужен сценарий? - -Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для практических примеров: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. - -## Устранение неполадок - -### Нужны полные метаданные транзакции - -Переходите на [Транзакции API](https://docs.fastnear.com/ru/tx), если одной истории переводов недостаточно. - -### `resume_token` перестал работать - -Считайте токен непрозрачным и переиспользуйте его только с тем же эндпоинтом и фильтрами, которые его вернули. - ---- - -## Примеры Transfers API - -- HTML-маршрут: https://docs.fastnear.com/ru/transfers/examples -- Markdown-маршрут: https://docs.fastnear.com/ru/transfers/examples.md - -**Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) - -## Пример - -### Отфильтровать и листать ленту переводов одного аккаунта - - Ход - Сначала соберите саму ленту аккаунта, а `receipt` поднимайте только тогда, когда одна строка действительно требует истории исполнения. - - 01POST /v0/transfers даёт первую страницу отфильтрованной ленты одного аккаунта. - 02jq поднимает сами строки плюс resume_token, чтобы вы могли продолжать листать ту же ленту. - 03POST /v0/receipt — это уже необязательный следующий шаг, если одной строке нужна её история исполнения. - -**Сеть** - -- только mainnet - -**Ход** - -- Забираете первую страницу одной отфильтрованной ленты переводов для выбранного аккаунта. -- Используете сами параметры ленты как главный учебный материал: `account_id`, `direction`, `asset_id`, `min_amount`, `desc` и `limit`. -- Сначала смотрите на строки ответа и `resume_token`, а не прыгаете сразу в историю исполнения. -- Только если какая-то строка действительно требует дополнительной истории, переиспользуете её `receipt_id` в Transactions API. - -```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -ASSET_ID=native:near -MIN_AMOUNT=1000000000000000000000000 - -curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - min_amount: $min_amount, - desc: true, - limit: 10 - }')" \ - | tee /tmp/transfers-feed.json >/dev/null - -jq '{ - resume_token, - transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - asset_id, - amount, - human_amount, - usd_amount, - other_account_id, - block_height - } - ] -}' /tmp/transfers-feed.json -``` - -Необязательный следующий шаг: если одной строке всё-таки нужна её точка исполнения, поднимите её `receipt_id` и один раз перейдите в Transactions API. - -```bash -RECEIPT_ID="$(jq -r '.transfers[0].receipt_id' /tmp/transfers-feed.json)" - -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' -``` - -**Когда переходить дальше** - -Запрос переводов напрямую отвечает на первый вопрос: как сейчас выглядит отфильтрованная лента этого аккаунта и как её продолжать без потери места? Только после того как сама лента подскажет, какая строка действительно важна, имеет смысл переходить по `receipt_id` и забирать историю исполнения из `/tx`. - -## Частые задачи - -### Отфильтровать ленту переводов одного аккаунта - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом и самым узким стабильным набором фильтров для ленты: направление, актив, сумма и порядок. - -**Следующая страница при необходимости** - -- Уточните те же фильтры по активу или сумме, если в первой странице всё ещё слишком много лишних строк. - -**Остановитесь, когда** - -- Уже можно объяснить, как выглядит эта отфильтрованная лента и как листать её дальше. - -**Переходите дальше, когда** - -- Одна конкретная строка уже требует истории исполнения или следа по receipt. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Листать ленту переводов дальше и не потерять своё место - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. - -**Следующая страница при необходимости** - -- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. -- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. - -**Остановитесь, когда** - -- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. - -**Переходите дальше, когда** - -- Пользователь просит метаданные транзакции сверх самих переводов. -- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). - -### Перейти от истории переводов к полному расследованию транзакции - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. - -**Следующая страница при необходимости** - -- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. - -**Остановитесь, когда** - -- Уже определено правильное событие перевода и понятно, какой API открывать следующим. - -**Переходите дальше, когда** - -- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. - -## Частые ошибки - -- Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. -- Считать историю переводов полной историей исполнения. -- Переиспользовать `resume_token` с другими фильтрами. - -## Полезные связанные страницы - -- [Transfers API](https://docs.fastnear.com/ru/transfers) -- [Transactions API](https://docs.fastnear.com/ru/tx) -- [FastNear API](https://docs.fastnear.com/ru/api) -- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) -- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) - ---- - -## Транзакции API - -- HTML-маршрут: https://docs.fastnear.com/ru/tx -- Markdown-маршрут: https://docs.fastnear.com/ru/tx.md - -**Источник:** [https://docs.fastnear.com/ru/tx](https://docs.fastnear.com/ru/tx) - -# Транзакции API - -Транзакции API — это поверхность истории. Используйте её, когда нужны индексированные представления транзакций или квитанций без постоянного опроса сырых RPC-методов и ручного объединения результатов. - -## Базовые URL - -```bash title="Transactions API Mainnet" -https://tx.main.fastnear.com -``` - -```bash title="Transactions API Testnet" -https://tx.test.fastnear.com -``` - -## Лучше всего подходит для - -- лент активности аккаунта; -- инструментов отладки и поддержки; -- поиска транзакций и квитанций по хешу; -- запросов по блокам и диапазонам блоков. - -## Когда его не стоит использовать - -- Используйте [FastNear API](https://docs.fastnear.com/ru/api), когда нужны балансы, NFT, стейкинг или поиск по публичному ключу. -- Используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), когда нужно каноническое поведение узла или отправка транзакций. - -## Аутентификация и доступность - -- Публичные запросы по истории часто работают и без ключа. -- Если ваша интеграция стандартизирует один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. -- Для testnet используйте `https://tx.test.fastnear.com`; поиск квитанций там тоже доступен. -- Сервис предназначен для индексированного доступа к истории, а не для отправки транзакций. - -## С чего обычно начинают - -- [Транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) — когда вы уже знаете идентификатор транзакции. -- [История аккаунта](https://docs.fastnear.com/ru/tx/account) — для лент активности и отладки аккаунта. -- [Поиск квитанции](https://docs.fastnear.com/ru/tx/receipt) — для расследования цепочки исполнения. -- [Диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) — когда нужен ограниченный по диапазону просмотр истории. - -## Нужен сценарий? - -Используйте [примеры API транзакций](https://docs.fastnear.com/ru/tx/examples) для практических примеров: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. - -## Устранение неполадок - -### Я ожидал, что здесь можно отправлять транзакции - -Это семейство предназначено для индексированных запросов по истории, а не для отправки подписанных транзакций. Для отправки используйте сырой RPC. - -### Мне нужны пояснения по пагинации - -`/v0/account` использует непрозрачный `resume_token`, а `/v0/blocks` опирается на диапазон и лимит. Повторно используйте непрозрачные токены ровно в том виде, в каком их вернул сервис. - -### Мне нужен только один канонический результат статуса транзакции из RPC - -Используйте сырой RPC вместо индексированного семейства истории. - ---- - -## Примеры Transactions API - -- HTML-маршрут: https://docs.fastnear.com/ru/tx/examples -- Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples.md - -**Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) - -## Быстрый старт - -Начните с одного tx hash и сначала получите самый короткий читаемый ответ. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, - receipt_count: (.transactions[0].receipts | length) - }' -``` - -## С чего начать - -### У меня есть один хеш транзакции. Что вообще произошло? +:::info +Будут заданы следующие переменные окружения: +- `DATA_TYPE=hot-data` — выбирает загрузку Hot data +- `DATA_PATH=~/.near/data` — стандартный путь nearcore +- `CHAIN_ID=mainnet` — явно выбирает данные mainnet +- `BLOCK=$LATEST` — указывает блок снапшота +::: - Ход - Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно. +`Archival Mainnet Snapshot (hot-data) » ~/.near/data`: - 01POST /v0/transactions даёт signer, receiver, типы действий, высоту блока и первую receipt-точку передачи. - 02RPC EXPERIMENTAL_tx_status нужен только для точной протокольной семантики успеха. - 03POST /v0/receipt имеет смысл только тогда, когда именно первая receipt становится новой опорной точкой. +```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. -- хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- высота включающего блока: `194263342` -- ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` +:::info +Будут заданы следующие переменные окружения: +- `DATA_TYPE=cold-data` — выбирает загрузку Cold data +- `DATA_PATH=/mnt/hdds/cold-data` — путь для размещения cold data. **Обратите внимание:** конфигурация nearcore должна указывать на тот же путь для cold data. +- `CHAIN_ID=mainnet` — явно выбирает данные mainnet +- `BLOCK=$LATEST` — указывает блок снапшота +::: -Короткий ответ: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. +`Archival Mainnet Snapshot (cold-data) » /mnt/hdds/cold-data`: -```mermaid -flowchart LR - H["Один tx hash
AdgNifPY..."] --> T["Получаем транзакцию"] - T --> A["Читаем signer, receiver, actions, block"] - A --> S["Короткая человеческая история"] - T -. "если потом понадобится" .-> R["Первая receipt
5GhZcpfK..."] +```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 ``` -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Читаемая история транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с хеша транзакции и печатаем signer, receiver, включающий блок, список действий и handoff в первую receipt | Даёт самый быстрый читаемый ответ на вопрос «что вообще сделала эта транзакция?» | -| Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | -| Переход к receipt | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | +--- -#### Shell-сценарий: от хеша транзакции к человеческой истории +## testnet -**Ход** +- HTML-маршрут: https://docs.fastnear.com/ru/snapshots/testnet +- Markdown-маршрут: https://docs.fastnear.com/ru/snapshots/testnet.md -- Получаете транзакцию по хешу и печатаете её основные поля. -- Подтверждаете финальный статус только если нужны точные RPC-семантики. -- Сохраняете первую receipt только как необязательный следующий шаг. +**Источник:** [https://docs.fastnear.com/ru/snapshots/testnet](https://docs.fastnear.com/ru/snapshots/testnet) -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp -SIGNER_ACCOUNT_ID=mike.near -``` +# Testnet -1. Получите транзакцию и распечатайте базовую историю. +## RPC-снапшот testnet -```bash -FIRST_RECEIPT_ID="$( - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/basic-tx-story.json \ - | jq -r '.transactions[0].transaction_outcome.outcome.status.SuccessReceiptId' -)" +Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и нужен для более узких задач. -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/basic-tx-story.json +Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: -# Ожидаемый список действий: ["Transfer"] -# Ожидаемая первая receipt: 5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq -``` +- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) +- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) +- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) +- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) +- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) +- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. -2. Если нужны точные RPC-семантики статуса, подтвердите их через `EXPERIMENTAL_tx_status`. +**Выполните эту команду, чтобы скачать RPC-снапшот testnet:** -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' -``` +:::info +Будут заданы следующие переменные окружения: +- `DATA_PATH=~/.near/data` — стандартный путь nearcore +- `CHAIN_ID=testnet` — явно выбирает данные testnet +::: -3. Если следующий вопрос уже звучит как «что это была за первая receipt?», один раз перейдите по ней и остановитесь. +`RPC Testnet Snapshot » ~/.near/data`: ```bash -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$FIRST_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash - }' +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 ``` -Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. +## Архивный снапшот testnet -**Когда переходить дальше** +:::warning +**Требует много времени и места на диске.** -`POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. +Подготовьтесь к большому объёму загрузки и длительному времени выполнения. +::: -### Какая receipt выдала этот лог или event? +Перед запуском скрипта загрузки можно задать следующие переменные окружения: - Ход - Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога. +- `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` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - 01POST /v0/transactions даёт полный индексированный список receipt для одного tx hash, включая receipt-логи. - 02jq сужает этот список до receipt, в логах которых встречается нужный вам фрагмент. - 03Как только совпадение осталось одно, сохраняйте его receipt_id, executor и имя метода как точный ответ. +По умолчанию скрипт ожидает следующий путь для данных: -Для этого зафиксированного mainnet-примера используйте: +- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` -- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` -- фрагмент лога: `Refund` -- ожидаемый matching `receipt_id`: `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` -- ожидаемый executor: `wrap.near` -- ожидаемый метод: `ft_resolve_transfer` +**Выполните следующие команды, чтобы скачать архивный снапшот testnet:** -В этой транзакции есть две logged receipt внутри одной истории: +1. Получите высоту блока последнего снапшота: -- ранний лог `Transfer ...` на receipt с `ft_transfer_call` -- более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` +`Latest archival testnet snapshot block`: -```mermaid -flowchart LR - T["Один tx hash
2KhhB1uD..."] --> L["Читаем все receipt-логи"] - L --> X["Ищем фрагмент:
Refund"] - X --> R["Точная receipt
9sLHQpaG..."] - R --> A["Ответ:
wrap.near / ft_resolve_transfer"] +```bash +LATEST=$(curl -s "https://snapshot.neardata.xyz/testnet/archival/latest.txt") +echo "Latest snapshot block: $LATEST" ``` -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Атрибуция лога | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | -| Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | - -#### Shell-сценарий атрибуции лога +2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. -**Ход** +:::info +Будут заданы следующие переменные окружения: +- `DATA_TYPE=hot-data` — выбирает загрузку Hot data +- `DATA_PATH=~/.near/data` — стандартный путь nearcore +- `CHAIN_ID=testnet` — явно выбирает сеть testnet +- `BLOCK=$LATEST` — указывает блок снапшота +::: -- Один раз получаете транзакцию и сохраняете список её receipt. -- Фильтруете receipt по одному фрагменту лога. -- Останавливаетесь, как только у вас есть один точный `receipt_id`, один executor и одно имя метода. +`Archival Testnet Snapshot (hot-data) » ~/.near/data`: ```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL -LOG_FRAGMENT=Refund +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 ``` -1. Получите транзакцию и сохраните список receipt. +--- -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/log-attribution-transaction.json >/dev/null -``` +## API переводов -2. Отфильтруйте список receipt до логов, которые содержат нужный вам фрагмент. +- HTML-маршрут: https://docs.fastnear.com/ru/transfers +- Markdown-маршрут: https://docs.fastnear.com/ru/transfers.md -```bash -jq --arg fragment "$LOG_FRAGMENT" '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id - }, - matching_receipts: [ - .transactions[0].receipts[] - | select(any(.execution_outcome.outcome.logs[]?; contains($fragment))) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - block_height: .execution_outcome.block_height, - logs: .execution_outcome.outcome.logs - } - ] -}' /tmp/log-attribution-transaction.json +**Источник:** [https://docs.fastnear.com/ru/transfers](https://docs.fastnear.com/ru/transfers) -# На что смотреть: -# - фрагмент `Refund` совпадает ровно с одной receipt -# - это receipt 9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w -# - receipt исполнилась на wrap.near -# - имя метода — ft_resolve_transfer -``` +# API переводов -3. Если хотите увидеть все logged receipt рядом, распечатайте только те receipt, где вообще были логи. +API переводов — это самая узкая поверхность истории FastNear. Стартуйте здесь, когда вопрос именно о движении активов, а не о более широкой истории исполнения за этим движением. -```bash -jq '{ - logged_receipts: [ - .transactions[0].receipts[] - | select((.execution_outcome.outcome.logs | length) > 0) - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - logs: .execution_outcome.outcome.logs - } - ] -}' /tmp/log-attribution-transaction.json +## Базовый URL + +```bash title="Transfers API Mainnet" +https://transfers.main.fastnear.com ``` -Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. +Эта поверхность сейчас доступна только в mainnet. `?network=testnet` не переключает бэкенд. -**Когда переходить дальше** +## Используйте этот API, когда -Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. +- нужна история переводов NEAR или FT-токенов по аккаунту +- строятся ленты кошелька или представления активности только по переводам +- отвечаете на вопросы поддержки или комплаенса про отправку и получение -### Превратить один страшный receipt ID из логов в понятную человеческую историю +## Не стартуйте здесь, когда -Есть только `receipt_id` из логов или трассы? Сначала разрешите сам receipt, затем восстановите родительскую транзакцию. +- нужна более широкая история транзакций или квитанций +- нужны балансы, активы, NFT или представления стейкинга +- нужен трафик testnet -Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. +Используйте [Транзакции API](https://docs.fastnear.com/ru/tx) для более широкой истории исполнения и [FastNear API](https://docs.fastnear.com/ru/api) для ответов в стиле состояния аккаунта. - Ход - Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой. +## Минимально полезные входы - 01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt. - 02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий. - 03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола». +- `account_id` +- опциональные фильтры: актив, направление, сумма или время +- нужно пользователю несколько событий или более длинный обзор истории -Зафиксированный receipt из логов: +## Рабочий цикл по умолчанию -- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` -- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- высота блока транзакции: `194263342` -- высота блока исполнения receipt: `194263343` +1. Начните с [Запроса переводов](https://docs.fastnear.com/ru/transfers/query), используя самый узкий набор фильтров, который всё ещё отвечает на вопрос. +2. Читайте возвращённые события как историю только переводов. Не пересобирайте полную хронологию квитанций, пока пользователь не попросит. +3. Переиспользуйте непрозрачный `resume_token` ровно в том виде, в каком его вернул сервис, при листании дальше. +4. Остановитесь, как только можете ответить, кто, что, когда и в каком активе отправил. -Короткий ответ: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. +## Аутентификация и доступность -```mermaid -flowchart LR - L["Один страшный receipt ID
5GhZcpfK..."] --> R["Ищем receipt"] - R --> T["Восстанавливаем tx hash
AdgNifPY..."] - T --> S["Читаем действия транзакции"] - S --> H["Человеческая история:
mike.near отправил 5 NEAR в global-counter.mike.near"] -``` +- Публичные чтения истории переводов часто работают и без ключа. +- Если вы стандартизируете один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. +- Ответы включают непрозрачный `resume_token` для пагинации. +- Сервис сейчас доступен только в mainnet. -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и печатаем аккаунты, блок исполнения, флаг успеха и связанный хеш транзакции | Даёт самый короткий путь от сырого receipt ID к пониманию, что вообще за объект перед вами | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | -| Каноническое продолжение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | +## Расширяйтесь, только если -#### Shell-сценарий: от страшного receipt ID к человеческой истории +- пользователь начинает спрашивать о квитанциях или действиях кроме переводов +- пользователь хочет более широкий контекст транзакции за переводом +- пользователь на самом деле спрашивает о балансах или текущих активах, а не о движении -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' -``` +Тогда расширяйтесь на [Транзакции API](https://docs.fastnear.com/ru/tx) или [FastNear API](https://docs.fastnear.com/ru/api), а не перегружайте представление переводов. -1. Разрешите receipt и поймите, что за объект вы смотрите. +## Типовые стартовые страницы -```bash -TX_HASH="$( - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | tee /tmp/receipt-lookup.json \ - | jq -r '.receipt.transaction_hash' -)" +- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) — лента по аккаунту с фильтрами по направлению, активу, сумме и времени -jq '{ - receipt: { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - receipt_type: .receipt.receipt_type, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash, - tx_block_height: .receipt.tx_block_height - } -}' /tmp/receipt-lookup.json -``` +## Нужен сценарий? -2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. +Используйте [Transfers API Examples](https://docs.fastnear.com/ru/transfers/examples) для практических примеров: узкие поиски переводов, пагинация через `resume_token` и переход к более широкому расследованию транзакций. -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/receipt-parent-transaction.json >/dev/null - -jq '{ - transaction: { - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transfer_deposit_yocto: ( - .transactions[0].transaction.actions[0].Transfer.deposit // null - ) - }, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/receipt-parent-transaction.json -``` +## Устранение неполадок -3. Сведите это к одному человеческому предложению. +### Нужны полные метаданные транзакции -```bash -jq -r ' - def zeros($n): - reduce range(0; $n) as $i (""; . + "0"); - def yocto_to_near($yocto): - ($yocto | tostring) as $digits - | if ($digits | length) <= 24 then - ("0." + zeros(24 - ($digits | length)) + $digits) - else - ($digits[0:(($digits | length) - 24)] + "." + $digits[-24:]) - end - | sub("0+$"; "") - | sub("\\.$"; ""); - .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил \(yocto_to_near($tx.transaction.actions[0].Transfer.deposit)) NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." -' /tmp/receipt-parent-transaction.json -``` +Переходите на [Транзакции API](https://docs.fastnear.com/ru/tx), если одной истории переводов недостаточно. -Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. +### `resume_token` перестал работать -В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. +Считайте токен непрозрачным и переиспользуйте его только с тем же эндпоинтом и фильтрами, которые его вернули. -**Когда переходить дальше** +--- -`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. +## Примеры Transfers API -## Ошибки и async +- HTML-маршрут: https://docs.fastnear.com/ru/transfers/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/transfers/examples.md -Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. +**Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) -### Доказать, что одно неудачное действие сорвало весь пакет +## Пример -Нужно проверить, закрепились ли ранние действия в неудачном батче? Используйте этот testnet-пример с `CreateAccount -> Transfer -> AddKey -> FunctionCall`. +### Отфильтровать и листать ленту переводов одного аккаунта -В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. +`/v0/transfers` возвращает отфильтрованную ленту плюс `resume_token`, который вы переиспользуете *без изменения фильтров*, чтобы продолжать листать. В каждой строке уже есть `human_amount`, `usd_amount`, `transaction_id` и `receipt_id` — большинство audit-вопросов закрываются без второго запроса. - Ход - Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов. +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=root.near - 01POST /v0/transactions показывает упорядоченный пакет ровно в том виде, в каком его подписал signer. - 02RPC EXPERIMENTAL_tx_status показывает падающий FunctionCall и точную причину отказа на уровне протокола. - 03RPC view_account по предполагаемому новому аккаунту доказывает, закрепились ли вообще ранние create, fund и add-key действия. +FEED="$(curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + direction: "receiver", + asset_id: "native:near", + min_amount: "1000000000000000000000000", + desc: true, + limit: 10 + }')")" -**Официальные ссылки** +echo "$FEED" | jq '{ + resume_token, + transfers: [.transfers[] | {block_height, amount, human_amount, usd_amount, other_account_id, transaction_id, receipt_id}] +}' +``` -- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) -- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) +Для зафиксированного аккаунта это возвращает недавние входящие native-NEAR переводы не меньше 1 NEAR — в примерных строках видны native-переводы с `escrow.ai.near` и уже посчитанным USD. Чтобы получить следующую страницу, отправьте то же тело с верхнеуровневым `resume_token: ""`; изменение любого другого фильтра делает токен недействительным. -Этот зафиксированный сбой был получен в **testnet 18 апреля 2026 года**: +Когда одной строке нужна точка исполнения, возьмите её `receipt_id` и сразу обратитесь к `/v0/receipt`: -- хеш транзакции: `CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M` -- аккаунт signer: `temp.mike.testnet` -- целевой новый аккаунт: `rollback-mo4vmkig.temp.mike.testnet` -- высота включающего блока: `246365118` -- хеш включающего блока: `6f5zTKDqQRwrxMywzvxeRvYcCERJmAnatJaqUEtQYUNM` -- порядок действий: `CreateAccount -> Transfer -> AddKey -> FunctionCall` -- упавший метод: `definitely_missing_method` -- RPC-ошибка: `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet` +```bash +RECEIPT_ID="$(echo "$FEED" | jq -r '.transfers[0].receipt_id')" -```mermaid -flowchart LR - T["Одна подписанная транзакция"] --> A["CreateAccount"] - A --> B["Transfer 0.01 NEAR"] - B --> C["AddKey"] - C --> D["FunctionCall definitely_missing_method()"] - D --> E["Сбой: CodeDoesNotExist"] - E --> R["Весь пакет не закрепился"] - R --> N["Новый аккаунт не появился"] - R --> K["Новый ключ не закрепился"] - R --> F["У получателя нет профинансированного состояния"] +curl -s "$TX_BASE_URL/v0/receipt" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '.receipt | {receipt_id, transaction_hash, receiver_id, predecessor_id, tx_block_height, is_success}' ``` -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Задуманный пакет | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированный хеш транзакции и печатаем упорядоченный список действий, получателя и метаданные включающего блока | Показывает, что именно signer пытался сделать, ещё до разговора о том, что закрепилось | -| Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | -| Доказательство по состоянию после исполнения | RPC [`query(view_account)`](https://docs.fastnear.com/ru/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | +Это тот же переход, что описан в [Превратить один неказистый receipt ID из логов в человекочитаемую историю](https://docs.fastnear.com/ru/tx/examples#%D0%BF%D1%80%D0%B5%D0%B2%D1%80%D0%B0%D1%82%D0%B8%D1%82%D1%8C-%D0%BE%D0%B4%D0%B8%D0%BD-%D0%BD%D0%B5%D0%BA%D0%B0%D0%B7%D0%B8%D1%81%D1%82%D1%8B%D0%B9-receipt-id-%D0%B8%D0%B7-%D0%BB%D0%BE%D0%B3%D0%BE%D0%B2-%D0%B2-%D1%87%D0%B5%D0%BB%D0%BE%D0%B2%D0%B5%D0%BA%D0%BE%D1%87%D0%B8%D1%82%D0%B0%D0%B5%D0%BC%D1%83%D1%8E-%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D1%8E) — один запрос возвращает и квитанцию, и её родительскую транзакцию целиком. -Индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. +## Частые ошибки -#### Shell-сценарий неудачной транзакции с пакетом действий +- Использовать Transfers API, когда пользователю на самом деле нужны балансы, активы или сводки аккаунта. +- Считать историю переводов полной историей исполнения. +- Переиспользовать `resume_token` с другими фильтрами. -**Ход** +## Связанные страницы -- Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. -- Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. -- Через один RPC-запрос к состоянию после исполнения доказываете, что новый аккаунт так и не появился после finality. +- [Transfers API](https://docs.fastnear.com/ru/transfers) +- [Transactions API](https://docs.fastnear.com/ru/tx) +- [FastNear API](https://docs.fastnear.com/ru/api) +- [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) -```bash -TX_BASE_URL=https://tx.test.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com -TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M -SIGNER_ACCOUNT_ID=temp.mike.testnet -NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet -``` +--- -1. Получите транзакцию и распечатайте задуманный пакет действий. +## Транзакции API -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/failed-batch-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height, - included_block_hash: .transactions[0].execution_outcome.block_hash - }, - batch: { - action_count: (.transactions[0].transaction.actions | length), - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - final_function_call_method_name: ( - .transactions[0].transaction.actions[3].FunctionCall.method_name - ) - }, - first_receipt_handoff: .transactions[0].transaction_outcome.outcome.status -}' /tmp/failed-batch-transaction.json +- HTML-маршрут: https://docs.fastnear.com/ru/tx +- Markdown-маршрут: https://docs.fastnear.com/ru/tx.md -# Ожидаемый порядок действий: -# 1. CreateAccount -# 2. Transfer -# 3. AddKey -# 4. FunctionCall -``` +**Источник:** [https://docs.fastnear.com/ru/tx](https://docs.fastnear.com/ru/tx) -2. Запросите RPC transaction status и посмотрите точную верхнеуровневую ошибку. +# Транзакции API -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/failed-batch-rpc-status.json >/dev/null +Транзакции API — это поверхность истории. Используйте её, когда нужны индексированные представления транзакций или квитанций без постоянного опроса сырых RPC-методов и ручного объединения результатов. + +## Базовые URL -jq '{ - final_execution_status: .result.final_execution_status, - failed_action_index: .result.status.Failure.ActionError.index, - failure: .result.status.Failure.ActionError.kind.FunctionCallError.CompilationError.CodeDoesNotExist -}' /tmp/failed-batch-rpc-status.json +```bash title="Transactions API Mainnet" +https://tx.main.fastnear.com +``` -# Ожидаемый failed_action_index: 3 -# Ожидаемый failure account_id: rollback-mo4vmkig.temp.mike.testnet +```bash title="Transactions API Testnet" +https://tx.test.fastnear.com ``` -3. Запросите предполагаемый новый аккаунт после finality и докажите, что его всё ещё нет. +## Лучше всего подходит для + +- лент активности аккаунта; +- инструментов отладки и поддержки; +- поиска транзакций и квитанций по хешу; +- запросов по блокам и диапазонам блоков. + +## Когда его не стоит использовать -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } - }')" \ - | tee /tmp/failed-batch-view-account.json >/dev/null +- Используйте [FastNear API](https://docs.fastnear.com/ru/api), когда нужны балансы, NFT, стейкинг или поиск по публичному ключу. +- Используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), когда нужно каноническое поведение узла или отправка транзакций. -jq '{ - error: .error.cause.name, - message: .error.data, - requested_account_id: .error.cause.info.requested_account_id, - proof_block_height: .error.cause.info.block_height -}' /tmp/failed-batch-view-account.json +## Аутентификация и доступность -# Ожидаемая ошибка: "UNKNOWN_ACCOUNT" -``` +- Публичные запросы по истории часто работают и без ключа. +- Если ваша интеграция стандартизирует один FastNear API-ключ на всех поверхностях FastNear, используйте здесь тот же формат через заголовок или параметр запроса. +- Для testnet используйте `https://tx.test.fastnear.com`; поиск квитанций там тоже доступен. +- Сервис предназначен для индексированного доступа к истории, а не для отправки транзакций. -Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. +## С чего обычно начинают -**Когда переходить дальше** +- [Транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) — когда вы уже знаете идентификатор транзакции. +- [История аккаунта](https://docs.fastnear.com/ru/tx/account) — для лент активности и отладки аккаунта. +- [Поиск квитанции](https://docs.fastnear.com/ru/tx/receipt) — для расследования цепочки исполнения. +- [Диапазон блоков](https://docs.fastnear.com/ru/tx/blocks) — когда нужен ограниченный по диапазону просмотр истории. -Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. +## Нужен сценарий? -### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? +Используйте [примеры API транзакций](https://docs.fastnear.com/ru/tx/examples) для практических примеров: поиска транзакций, расследования квитанций, истории аккаунта и анализа диапазонов блоков. -Нужно доказать, что позже упал отдельный cross-contract receipt, хотя первый вызов выглядел успешным? Используйте этот зафиксированный testnet-пример. +## Устранение неполадок -Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. +### Я ожидал, что здесь можно отправлять транзакции - Ход - Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась. +Это семейство предназначено для индексированных запросов по истории, а не для отправки подписанных транзакций. Для отправки используйте сырой RPC. - 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. - 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. - 03Как только эти два представления сходятся на одном и том же разрезе истории, остановитесь. Этот пример держится за сохранённые исторические свидетельства, а не за живой read состояния роутера. +### Мне нужны пояснения по пагинации -**Официальные ссылки** +`/v0/account` использует непрозрачный `resume_token`, а `/v0/blocks` опирается на диапазон и лимит. Повторно используйте непрозрачные токены ровно в том виде, в каком их вернул сервис. -- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) -- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) +### Мне нужен только один канонический результат статуса транзакции из RPC -Этот зафиксированный асинхронный сбой был получен в **testnet 18 апреля 2026 года**: +Используйте сырой RPC вместо индексированного семейства истории. -- хеш транзакции: `AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz` -- аккаунт signer: `temp.mike.testnet` -- первый контракт-получатель: `seq-dr.mike.testnet` -- аккаунт detached-цели: `asyncfail-in2hwikn.temp.mike.testnet` -- блок включения транзакции: `246368568` -- успешная первая receipt: `6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS` в блоке `246368569` -- более поздняя упавшая receipt: `2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es` в блоке `246368570` -- первый метод: `kickoff_append` -- более поздний упавший метод: `append` -- верхнеуровневый RPC `status`: `SuccessValue` +--- -```mermaid -flowchart LR - T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] - R --> D["Detached cross-contract receipt
append(...)"] - D --> F["Более поздний сбой
CodeDoesNotExist"] - T -. "внешняя транзакция всё равно завершается" .-> X["RPC top-level status
SuccessValue"] -``` +## Примеры Transactions -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | -| Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | +- HTML-маршрут: https://docs.fastnear.com/ru/tx/examples +- Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples.md -Успех receipt не транзитивен. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. +**Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) -#### Shell-сценарий более позднего сбоя receipt +## Начните здесь -**Ход** +### У меня один хеш транзакции. Что произошло? -- Читаете транзакцию и её таймлайн receipt из индексированного представления. -- Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. -- Останавливаетесь, как только эти два сохранённых представления сходятся на одном и том же разрезе истории. +Вставьте хеш в `POST /v0/transactions` — один ответ обычно содержит всю историю. ```bash -TX_BASE_URL=https://tx.test.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com -TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz -SIGNER_ACCOUNT_ID=temp.mike.testnet -FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS -FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height, + actions: (.transactions[0].transaction.actions | map(if type == "string" then . else keys[0] end)), + first_receipt_id: .transactions[0].execution_outcome.outcome.status.SuccessReceiptId, + receipt_count: (.transactions[0].receipts | length) + }' ``` -1. Получите транзакцию и распечатайте таймлайн receipt по порядку блоков. +Для зафиксированного хеша `mike.near` отправил один `Transfer` на `global-counter.mike.near` в блоке `194263342`, с передачей в receipt `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq`. Если `receipt_count > 1` или следующий вопрос касается поведения на уровне receipt, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие) или [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt). + +### Какой receipt испустил этот лог или событие? + +Выведите список всех receipt транзакции с логами и флагом, содержат ли их логи ваш фрагмент. Совпадение доказывается, а не угадывается: у зафиксированной транзакции один receipt логирует `Transfer`, другой — `Refund`, и только сторона `Refund` переключается в `true`. ```bash +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +LOG_FRAGMENT=Refund + curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/later-receipt-failure-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - tx_handoff: .transactions[0].transaction_outcome.outcome.status - }, - receipts: [ - .transactions[0].receipts[] - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - block_height: .execution_outcome.block_height, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system_transfer"), - status: .execution_outcome.outcome.status - } - ] -}' /tmp/later-receipt-failure-transaction.json - -# На что смотреть: -# - первая receipt контракта на seq-dr.mike.testnet успешно прошла в блоке 246368569 -# - более поздняя receipt append(...) упала в блоке 246368570 + | jq --arg fragment "$LOG_FRAGMENT" ' + [ + .transactions[0].receipts[] + | select((.execution_outcome.outcome.logs | length) > 0) + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0] + | if type == "string" then . else (.FunctionCall.method_name // keys[0]) end), + matches_fragment: any(.execution_outcome.outcome.logs[]?; contains($fragment)), + logs: .execution_outcome.outcome.logs + } + ]' ``` -2. Запросите RPC transaction status и сравните верхнеуровневую историю с более поздней упавшей receipt. +Фрагмент `Refund` атрибутируется receipt `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` на `wrap.near`, метод `ft_resolve_transfer`. Логи receipt живут на receipts, а не на транзакции, поэтому одного прохода достаточно — более глубокая async-трассировка не нужна. + +### Превратить один неказистый receipt ID из логов в человекочитаемую историю + +`POST /v0/receipt` возвращает запись receipt **и** его полную родительскую транзакцию в одном ответе, поэтому единственного запроса хватает на всю историю — дополнительный `/v0/transactions` не нужен. ```bash -curl -s "$RPC_URL" \ +TX_BASE_URL=https://tx.main.fastnear.com +RECEIPT_ID=5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq + +curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block: .receipt.block_height, + tx_block: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }, + parent_transaction: { + signer_id: .transaction.transaction.signer_id, + receiver_id: .transaction.transaction.receiver_id, + action_types: (.transaction.transaction.actions | map(if type == "string" then . else keys[0] end)) } - }')" \ - | tee /tmp/later-receipt-failure-rpc.json >/dev/null - -jq \ - --arg first_receipt_id "$FIRST_RECEIPT_ID" \ - --arg failed_receipt_id "$FAILED_RECEIPT_ID" '{ - top_level_status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status, - first_contract_receipt: ( - .result.receipts_outcome[] - | select(.id == $first_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - logs: .outcome.logs, - status: .outcome.status - } - ), - later_failed_receipt: ( - .result.receipts_outcome[] - | select(.id == $failed_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - status: .outcome.status - } - ) - }' /tmp/later-receipt-failure-rpc.json - -# На что смотреть: -# - top_level_status всё ещё равен SuccessValue -# - первая receipt контракта залогировала dishonest_router:kickoff:late-failure -# - более поздняя receipt append(...) упала с CodeDoesNotExist + }' ``` -Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. - -**Когда переходить дальше** +Для зафиксированного receipt это возвращает `Action`-receipt от `mike.near` к `global-counter.mike.near`, который успешно выполнился в блоке `194263343`, через один блок после попадания родительской транзакции `AdgNifPY…`, — один `Transfer` (5 NEAR, в сыром `.transaction.transaction.actions` видимо как `5000000000000000000000000` yocto). Если интересным якорем становится родительская транзакция, хеш у вас уже есть — переиспользуйте его в [У меня один хеш транзакции. Что произошло?](#у-меня-один-хеш-транзакции-что-произошло). -Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. +## Сбои и async -### Дошёл ли callback вообще? +### Доказать, что один провалившийся action откатил весь batch -Нужно проверить, вернулся ли callback в исходный контракт? Начните с этого mainnet-примера. +Один batch отправил `CreateAccount → Transfer → AddKey → FunctionCall`, и финальный вызов попал в отсутствующий метод. Индексированная запись транзакции уже несёт упорядоченный batch *и* точный сбой на уровне receipt, поэтому одного запроса хватает, чтобы ответить «что пытались и что сломалось»; проверка через `view_account` затем доказывает, что предыдущие actions откатились. - Ход - Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а. +```bash +TX_BASE_URL=https://tx.test.fastnear.com +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M +NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet - 01POST /v0/transactions показывает downstream-вызов и более поздний receipt, который возвращается в исходный контракт. - 02jq сужает этот список receipt до одного downstream-вызова и одного callback-receipt. - 03RPC EXPERIMENTAL_tx_status нужен только как дополнительное подтверждение, если вам важны канонический результат callback-а и его логи. +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq '{ + action_types: (.transactions[0].transaction.actions | map(if type == "string" then . else keys[0] end)), + final_method: .transactions[0].transaction.actions[3].FunctionCall.method_name, + tx_handoff: .transactions[0].execution_outcome.outcome.status, + receipt_failure: ( + first( + .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) + | .execution_outcome.outcome.status.Failure.ActionError + ) + ) + }' +``` -Зафиксированный mainnet-пример с callback замечен **19 апреля 2026 года**: +Статус на уровне транзакции — `SuccessReceiptId`: транзакция успешно передала свои batched actions в receipt. Сбой лежит слоем ниже на этом receipt: `index: 3` (именно `FunctionCall`), вид `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet`. `SuccessReceiptId` в tx-outcome означает «handoff прошёл», а не «всё завершилось» — реальная ловушка, если смотреть только на статус уровня транзакции. -- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` -- аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` -- исходный контракт: `wrap.near` -- downstream-receiver: `v2.ref-finance.near` -- верхнеуровневый метод: `ft_transfer_call` -- downstream-метод: `ft_on_transfer` -- callback-метод: `ft_resolve_transfer` -- блок транзакции: `194692298` -- блок downstream-receipt: `194692300` -- блок callback-receipt: `194692301` +Теперь докажите откат предыдущих actions: спросите аккаунт, который batch *пытался* создать: -```mermaid -flowchart LR - T["Одна mainnet-транзакция
ft_transfer_call на wrap.near"] --> D["Downstream-receipt
v2.ref-finance.near.ft_on_transfer"] - D --> F["Receiver упал
E51: contract paused"] - F --> C["Callback-receipt обратно в wrap.near
ft_resolve_transfer"] - C --> R["Лог refund на wrap.near"] +```bash +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ + jsonrpc: "2.0", id: "fastnear", method: "query", + params: {request_type: "view_account", account_id: $account_id, finality: "final"} + }')" \ + | jq '{error: .error.cause.name, requested_account_id: .error.cause.info.requested_account_id}' ``` -Здесь хорошо видна одна полезная деталь NEAR: downstream-сбой не означает, что callback исчез. В этом случае `v2.ref-finance.near` уронил свой `ft_on_transfer`, но `wrap.near` всё равно позже получил `ft_resolve_transfer` и залогировал refund. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | -| Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | - -#### Shell-сценарий проверки callback-а +`UNKNOWN_ACCOUNT` — это и есть доказательство. Если бы `CreateAccount` закрепился, `view_account` вернул бы результат; раз нет — предыдущие `Transfer` и `AddKey` из того же batched-receipt тоже не закрепились. -**Ход** +### Почему этот вызов контракта выглядел успешным, но потом receipt упал? -- Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. -- Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. -- Останавливаетесь сразу, как только можете сказать, вернулся ли callback и что он сделал. +Одна транзакция может закончиться тем, что внешний handoff рапортует `SuccessReceiptId`, а дочерний receipt при этом тихо падает — это и есть async-модель NEAR, и `/v0/transactions` выдаёт весь timeline за один запрос. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL -SENDER_ACCOUNT_ID=7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72 -ORIGIN_CONTRACT_ID=wrap.near -DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near -``` - -1. Получите транзакцию и сохраните receipt-цепочку. -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/callback-check-transaction.json >/dev/null -``` - -2. Сначала ответьте на самый короткий полезный вопрос: вернулся ли callback вообще? - -```bash -jq --arg origin "$ORIGIN_CONTRACT_ID" ' - [ - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - ] | length > 0 -' /tmp/callback-check-transaction.json + | jq '{ + tx_handoff: .transactions[0].execution_outcome.outcome.status, + outer_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + descendant_failures: [ + .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) + | { + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + block_height: .execution_outcome.block_height, + failure: .execution_outcome.outcome.status.Failure + } + ], + receipt_timeline: [ + .transactions[0].receipts[] + | { + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + status_class: (.execution_outcome.outcome.status | keys[0]) + } + ] + }' ``` -3. Если ответ `true`, распечатайте downstream-receipt вместе с callback-receipt. - -```bash - -CALLBACK_RECEIPT_ID="$( - jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | .receipt.receipt_id - ) - ' /tmp/callback-check-transaction.json -)" +Для зафиксированной транзакции mainnet `tx_handoff` — `SuccessReceiptId`: транзакция чисто запустила свой первый receipt. Если смотреть только сюда, можно назвать это победой. `descendant_failures` рассказывают вторую историю: `ft_on_transfer` на `v2.ref-finance.near` упал с `E51: contract paused` — DEX был на паузе во время этого свопа и не мог принять wrapped NEAR. А `receipt_timeline` показывает, как история разрешилась: callback `ft_resolve_transfer` на `wrap.near` всё равно отработал и вывел лог `Refund`, вернув wrapped NEAR отправителю. -jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - tx_block_height: .transactions[0].execution_outcome.block_height - }, - downstream_receipt: ( - first( - .transactions[0].receipts[] - | select(.receipt.receiver_id == $downstream) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - status: .execution_outcome.outcome.status, - block_height: .execution_outcome.block_height - } - ) - ), - callback_receipt: ( - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: .receipt.receipt.Action.actions[0].FunctionCall.method_name, - logs: .execution_outcome.outcome.logs, - status: .execution_outcome.outcome.status, - block_height: .execution_outcome.block_height - } - ) - ), - callback_ran: ( - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | true - ) // false - ) -}' /tmp/callback-check-transaction.json +Успех receipt не транзитивен. Протокол может чисто отдать handoff и при этом увидеть, как отцеплённая работа провалится позже. Если ваше приложение «выглядело успешным», но деньги всё равно вернулись, пройдите этот же timeline — разделение видно на индексированном ответе без отдельного RPC status-запроса. Чтобы отдельно проверить, что ваш callback отработал, см. [Отработал ли мой callback?](#отработал-ли-мой-callback). -# На что смотреть: -# - downstream-receipt выполнил ft_on_transfer на v2.ref-finance.near -# - более поздний callback-receipt выполнил ft_resolve_transfer на wrap.near -# - callback_ran равно true, даже несмотря на downstream-сбой -``` +### Отработал ли мой callback? -4. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. +Кросс-контрактные вызовы NEAR возвращаются через callback-receipt на исходном контракте. Отработал ли этот callback — это одна строка с `any(...)` против индексированного списка receipts; а полная история refund выпадает из того же ответа. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg sender_account_id "$SENDER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $sender_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/callback-check-rpc.json >/dev/null - -jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ - top_level_status: .result.status, - callback_receipt: ( - first( - .result.receipts_outcome[] - | select(.id == $callback_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - logs: .outcome.logs, - status: .outcome.status - } - ) - ) -}' /tmp/callback-check-rpc.json +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL +ORIGIN_CONTRACT_ID=wrap.near +CALLBACK_METHOD=ft_resolve_transfer -# На что смотреть: -# - downstream ft_on_transfer receipt упал на v2.ref-finance.near -# - wrap.near всё равно позже получил ft_resolve_transfer -# - лог callback-а показывает refund обратно отправителю +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ + | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ + top_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + callback_ran: any( + .transactions[0].receipts[]; + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback + ), + receipt_chain: [ + .transactions[0].receipts[] + | { + receiver_id: .receipt.receiver_id, + method: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + block: .execution_outcome.block_height, + status: (.execution_outcome.outcome.status | keys[0]), + logs: .execution_outcome.outcome.logs + } + ] + }' ``` -**Когда переходить дальше** - -Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. +Для зафиксированной транзакции `ft_transfer_call` на `wrap.near` передаёт управление в `ft_on_transfer` на `v2.ref-finance.near`, который **падает**. Callback `ft_resolve_transfer` всё равно выполняется на `wrap.near` и логирует `Refund 7278020378457059679767103 from v2.ref-finance.near to …` обратно отправителю — поэтому `callback_ran: true`, несмотря на сбой дочернего receipt. Сбой ниже по цепочке не мешает исходному контракту увидеть свой callback; так async-обработка ошибок NEAR остаётся восстанавливаемой. Строки с `method: "system"` — это рантайм-возвраты газа, а не логика контракта. Чтобы привязать один из этих логов к породившему его receipt, см. [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). ## Частые ошибки -- Пытаться отправлять транзакцию через history API вместо сырого RPC. +- Пытаться отправить транзакцию через history-API вместо raw RPC. - Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. -- Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». +- Спускаться в raw RPC до того, как индексированная история ответила на читаемый вопрос «что произошло?». -## Полезные связанные страницы +## Связанные страницы - [Transactions API](https://docs.fastnear.com/ru/tx) - [RPC Reference](https://docs.fastnear.com/ru/rpc) - [FastNear API](https://docs.fastnear.com/ru/api) - [NEAR Data API](https://docs.fastnear.com/ru/neardata) - [Berry Club: живая доска и один путь исторической реконструкции](https://docs.fastnear.com/ru/tx/examples/berry-club) -- [OutLayer: связать одну транзакцию запроса с одним ответом воркера](https://docs.fastnear.com/ru/tx/examples/outlayer) - [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) @@ -6110,114 +3231,6 @@ for (const drawTx of drawTransactionsOldestFirst) { --- -## OutLayer: что сделала эта пара request/resolution? - -- HTML-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer -- Markdown-маршрут: https://docs.fastnear.com/ru/tx/examples/outlayer.md - -**Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) - -{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как прочитать один caller-side запрос OutLayer вместе с более поздним worker-side resolution, а затем разобрать finish-receipts только если это действительно нужно. */} - -# OutLayer: что сделала эта пара request/resolution? - -Используйте этот walkthrough, когда вопрос звучит так: «что сделал этот запрос OutLayer, какое более позднее resolution к нему относится и нужно ли мне вообще смотреть на finish-receipts?» - -Оставайтесь в пределах публичных chain-данных: прочитайте request tx, прочитайте более поздний resolution tx и только потом решайте, нужны ли вам finish-receipts. - -## Компактный shell-сценарий - -Эта пара работала 18 апреля 2026 года: - -- caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` -- worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` - -### 1. Основной ответ: сразу раскройте request tx и resolution tx вместе - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -REQUEST_TX_HASH=AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4 -WORKER_TX_HASH=AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs - -curl -sS "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg request_tx_hash "$REQUEST_TX_HASH" --arg worker_tx_hash "$WORKER_TX_HASH" '{ - tx_hashes: [$request_tx_hash, $worker_tx_hash] - }')" \ - | tee /tmp/outlayer-pair.json >/dev/null - -jq '{ - transactions: [ - .transactions[] - | { - hash: .transaction.hash, - signer_id: .transaction.signer_id, - receiver_id: .transaction.receiver_id, - methods: [ - .transaction.actions[] - | .FunctionCall.method_name? - | select(. != null) - ], - first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) - } - ] -}' /tmp/outlayer-pair.json -``` - -Это и есть основной ответ: один request tx, один более поздний resolution tx и читаемые signer-, receiver-, method- и log-доказательства для обеих транзакций. - -### Необязательное продолжение: Что сделал finish-путь? - -```bash -jq --arg worker_tx_hash "$WORKER_TX_HASH" ' - .transactions[] - | select(.transaction.hash == $worker_tx_hash) - | { - worker_tx_hash: .transaction.hash, - receipts: [ - .receipts[] - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - actions: [.receipt.receipt.Action.actions[] | keys[0]], - logs: .execution_outcome.outcome.logs - } - ] - } -' /tmp/outlayer-pair.json -``` - -Смотрите на самое маленькое читаемое доказательство finish-пути: - -- `FunctionCall`-receipts, которые продолжают finish-путь -- логи списания вроде `[[yNEAR charged: "..."]]` -- последующие `Transfer`-receipts, которые похожи на refund или settlement-движение - -Именно здесь receipts становятся нужной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции здесь относятся друг к другу?» - -### Необязательный шаг: сначала найдите два хеша - -```bash -curl -sS "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data '{"account_id":"outlayer.near","desc":true,"limit":10}' \ - | jq '{ - txs_count, - recent_hashes: [.account_txs[:10][] | .transaction_hash] - }' -``` - -Используйте это только если вы ещё не знаете пару хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` превращает их в читаемый ответ. - -## Полезные связанные страницы - -- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) -- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) -- [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) - ---- - ## Расширенный поиск записи SocialDB - HTML-маршрут: https://docs.fastnear.com/ru/tx/socialdb-proofs @@ -6227,15 +3240,15 @@ curl -sS "$TX_BASE_URL/v0/account" \ # Расширенный поиск записи SocialDB -Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому поиску записи. +Используйте эту страницу только тогда, когда отправная точка — уже читаемое значение SocialDB из `api.near.social`, а следующий вопрос относится к историческому поиску записи. -Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" +Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос звучит как «какая запись сделала это читаемое значение SocialDB истинным?». ## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` -Используйте этот сценарий, когда читаемый факт уже звучит как "текущее `profile.name` равно `Mike Purvis`", а остаётся только вопрос, какая запись сделала это поле истинным. +Используйте этот сценарий, когда читаемый факт уже звучит как «текущее `profile.name` равно `Mike Purvis`», а остаётся вопрос, какая запись сделала это поле истинным. -Здесь достаточно сохранить один важный нюанс SocialDB: для исторического provenance правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. +Это единственный нюанс SocialDB, который стоит запомнить: для исторического доказательства правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. Для этого живого якоря: @@ -6247,70 +3260,57 @@ curl -sS "$TX_BASE_URL/v0/account" \ ### Shell-сценарий -1. Прочитайте поле из NEAR Social и сохраните block записи на уровне поля. +1. Прочитайте поле из NEAR Social и сохраните блок записи на уровне поля. ```bash SOCIAL_API_BASE_URL=https://api.near.social TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com ACCOUNT_ID=mike.near PROFILE_FIELD=profile/name -PROFILE_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-profile-name.json \ - | jq -r --arg account_id "$ACCOUNT_ID" \ - '.[ $account_id ].profile.name[":block"]' -)" +PROFILE="$(curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')")" -jq --arg account_id "$ACCOUNT_ID" '{ +echo "$PROFILE" | jq --arg account_id "$ACCOUNT_ID" '{ current_name: .[$account_id].profile.name[""], field_block_height: .[$account_id].profile.name[":block"], parent_profile_block_height: .[$account_id].profile[":block"] -}' /tmp/mike-profile-name.json +}' + +PROFILE_BLOCK_HEIGHT="$(echo "$PROFILE" | jq -r --arg account_id "$ACCOUNT_ID" '.[$account_id].profile.name[":block"]')" ``` -2. Переиспользуйте этот block уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. +2. Переиспользуйте этот блок уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. ```bash -PROFILE_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-profile-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" +BLOCK_RECEIPTS="$(curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')")" -jq --arg account_id "$ACCOUNT_ID" '{ +echo "$BLOCK_RECEIPTS" | jq --arg account_id "$ACCOUNT_ID" '{ profile_receipt: ( first( .block_receipts[] | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | { - receipt_id, - transaction_hash, - block_height, - tx_block_height - } + | {receipt_id, transaction_hash, block_height, tx_block_height} ) ) -}' /tmp/mike-profile-block.json +}' + +PROFILE_TX_HASH="$(echo "$BLOCK_RECEIPTS" | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )')" ``` 3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. @@ -6319,34 +3319,27 @@ jq --arg account_id "$ACCOUNT_ID" '{ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-profile-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), - description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), - tags: ( - .args - | @base64d - | fromjson - | .data["mike.near"].profile.tags - | keys - ) - } - ) -}' /tmp/mike-profile-transaction.json + | jq --arg account_id "$ACCOUNT_ID" '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | (.args | @base64d | fromjson | .data[$account_id].profile) as $profile + | { + method_name, + profile_name: $profile.name, + description: $profile.description, + tags: ($profile.tags | keys) + } + ) + }' ``` -Это и есть весь паттерн lookup-а: читаемое значение, block уровня поля, мост через receipt и payload транзакции. +Это и есть весь паттерн lookup: читаемое значение, блок уровня поля, мост через receipt и payload транзакции. Тот же мост работает и для других читаемых значений SocialDB: diff --git a/static/ru/llms.txt b/static/ru/llms.txt index 085ce72..f973254 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -16,10 +16,10 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Практические примеры для проверки точных storage-key, чтения индексированной истории записей и подтверждения текущего состояния через RPC. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Практические примеры KV FastData: scoped-записи, история ключа и переход к точному состоянию. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Недавние чтения по блокам и шардам для мониторинга активности контракта, подтверждения оптимистичных наблюдений и проверки изменений на уровне шарда. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. -- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Практические примеры FastNear RPC для точных проверок состояния, анализа блоков, вызовов контрактов и отправки транзакций. +- [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Практические примеры RPC: проверки состояния, инспекция блоков, чтение контрактов и отправка транзакций. - [API переводов](https://docs.fastnear.com/ru/transfers.md): История переводов NEAR и FT-токенов по аккаунтам для продуктовых лент и инструментов расследования. - [Транзакции API](https://docs.fastnear.com/ru/tx.md): Индексированные запросы по транзакциям, квитанциям, истории аккаунтов и истории блоков для FastNear. @@ -32,20 +32,19 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп ## Другие гайды -- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Практические примеры FastNear API для поиска аккаунтов, проверки активов, NFT-gating и классификации стейкинга. +- [Примеры API](https://docs.fastnear.com/ru/api/examples.md): Практические примеры FastNear API: поиск аккаунта по ключу, просмотр активов и классификация стейкинга. - [Руководство по интернационализации](https://docs.fastnear.com/ru/internationalization.md): Руководство для сопровождающих по добавлению локалей Docusaurus, локализованных оверлеев FastNear и безопасного для discovery процесса перевода. -- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Практические примеры для мониторинга недавней активности контракта, подтверждения optimistic-наблюдений и проверки изменений на уровне shard. +- [Примеры NEAR Data](https://docs.fastnear.com/ru/neardata/examples.md): Практические примеры NEAR Data: живой мониторинг, optimistic-проверки и доказательство на уровне shard. - [redocly-config](https://docs.fastnear.com/ru/redocly-config.md): Исторические заметки о прежнем бэкенде Redocly и о том, где он всё ещё важен для проверки документации FastNear. -- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Практические примеры для поиска переводов, пагинации через resume_token и перехода к истории транзакций. -- [Примеры Transactions API](https://docs.fastnear.com/ru/tx/examples.md): Практические расследования транзакций для типовых задач разработчика. +- [Примеры Transfers API](https://docs.fastnear.com/ru/transfers/examples.md): Практические примеры: фильтрация ленты переводов, пагинация и переход к истории транзакций. +- [Примеры Transactions](https://docs.fastnear.com/ru/tx/examples.md): Практические расследования транзакций: хеши, receipts, async-сбои и callback. - [Berry Club: как читать живую доску и разбирать одну эпоху](https://docs.fastnear.com/ru/tx/examples/berry-club.md): Прочитайте живую доску Berry Club через RPC get_lines, а затем используйте Transactions API, чтобы восстановить одну более раннюю эпоху. -- [OutLayer: что сделала эта пара request/resolution?](https://docs.fastnear.com/ru/tx/examples/outlayer.md): Используйте Transactions API, чтобы прочитать один caller-side запрос OutLayer, одно более позднее worker-side resolution и обращаться к finish-receipts только когда это действительно нужно. - [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs.md): Один короткий расширенный сценарий, который начинается с читаемого значения SocialDB и восстанавливает транзакцию записи за ним. ## Снапшоты - [Снапшоты для валидаторов](https://docs.fastnear.com/ru/snapshots.md): Пути загрузки снапшотов FastNear для подъёма и восстановления узлов NEAR. -- [Примеры Snapshot](https://docs.fastnear.com/ru/snapshots/examples.md): Практические примеры восстановления для выбора правильного пути через FastNear snapshots. +- [Примеры snapshot](https://docs.fastnear.com/ru/snapshots/examples.md): Практические примеры восстановления узла: optimized, standard и archival. - [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. diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index 29eed18..80bcf7e 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -1,24 +1,10 @@ **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) -Используйте NEAR Data, когда вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. - ## Примеры -### Был ли мой контракт затронут в последнем финализированном блоке? - - Ход - Сначала дайте NEAR Data ответить на задачу мониторинга, а уже потом сохраняйте tx hash или receipt ID для следующей поверхности, если это вообще понадобится. - - 01last-block-final находит самую новую финализированную высоту. - 02block даёт один недавний гидратированный документ блока с уже присоединёнными данными по shard. - 03Суммируйте прямые транзакции, входящие receipts, результаты выполнения и state_changes для нужного контракта. Считайте state_changes самым сильным сигналом того, что контракт действительно изменился. - -Такой сценарий вполне честно может вернуть `touched: false`, если блок тихий. Это тоже полезный ответ: в самом новом финализированном блоке сейчас нет ничего, что требовало бы более глубокого разбора. +Каждый гидратированный документ блока NEAR Data несёт транзакции, receipts, результаты исполнения и state changes с разбивкой по shard. Три сценария ниже используют один `bash`-помощник, который сворачивает эти четыре сигнала в одну сводку с полями для перехода дальше. Определите его один раз и прогоняйте блоки через него: ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near - contract_touch_summary() { jq -r --arg contract "$1" ' [ .shards[] | { @@ -53,156 +39,80 @@ contract_touch_summary() { sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) }' } +``` -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" +### Был ли мой контракт затронут в последнем финализированном блоке? + +`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок; пройдите по нему и направьте результат сразу в помощник. -printf 'Latest finalized block: %s\n' "$FINAL_LOCATION" +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ +curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ | contract_touch_summary "$TARGET_CONTRACT" ``` -Читайте ответ так: - -- `touched: false` означает, что самый новый финализированный блок не упомянул и не изменил контракт ни одним из отслеживаемых способов. -- `sample_tx_hash` означает, что у вас уже есть хороший якорь для следующего шага на `/tx`. -- `sample_receipt_id` без tx hash обычно означает, что контракт появился в цепочке через receipts, и NEAR Data уже сэкономила вам более дешёвый этап мониторинга. +Читайте `touched: false` как полный и однозначный ответ для тихого блока. При `true` поля перехода (`sample_tx_hash`, `sample_receipt_id`) сразу ведут вас в [/tx/examples](https://docs.fastnear.com/ru/tx/examples) за человекочитаемой историей. Один запрос заменяет ручной просмотр chunks — и учтите: `touched: true` с `state_changes: 0` — это реальная форма: receipt может попасть в chunk, не вызвав в том же блоке мутации состояния. ### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? - Ход - Используйте один и тот же словарь contract-touch на обеих поверхностях, чтобы сравнение было честным. - - 01last-block-optimistic находит самую новую optimistic-высоту. - 02block-optimistic показывает ранний сигнал для того же контракта. - 03block на той же высоте либо подтверждает то же наблюдение, либо показывает, что finality ещё не догнала. - -Если finality уже догнала, optimistic- и finalized-сводки могут совпасть сразу. Это тоже полезно: ранний сигнал уже попал в стабильную историю. +Optimistic-блоки живут по адресу `/v0/block_opt/{height}`; как только finality догоняет (обычно в пределах одного блока, ~1 с на mainnet), та же высота становится доступна и по `/v0/block/{height}`. Прогоните помощник на обеих и сравните. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near - -contract_touch_summary() { - jq -r --arg contract "$1" ' - [ .shards[] | { - shard_id, - direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) - } - | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) - ] as $rows - | { - height: .block.header.height, - hash: .block.header.hash, - contract: $contract, - touched: (($rows | length) > 0), - shards: ($rows | map(.shard_id)), - evidence: { - direct_txs: (($rows | map(.direct_txs) | add) // 0), - incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), - execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), - state_changes: (($rows | map(.state_changes) | add) // 0) - } - }' -} - OPT_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' + | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" - OPT_HEIGHT="${OPT_LOCATION##*/}" -printf 'Latest optimistic block: %s\n' "$OPT_LOCATION" +echo "Optimistic view at $OPT_HEIGHT:" +curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | contract_touch_summary "$TARGET_CONTRACT" -curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" \ - | tee /tmp/neardata-optimistic-block.json \ - | contract_touch_summary "$TARGET_CONTRACT" - -curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT" \ - | tee /tmp/neardata-final-same-height.json >/dev/null - -if jq -e 'type == "null"' /tmp/neardata-final-same-height.json >/dev/null; then - printf 'Finalized block %s is not available yet; finality has not caught up.\n' "$OPT_HEIGHT" +echo "Finalized view at $OPT_HEIGHT:" +FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" +if [ "$(echo "$FINAL" | jq 'type')" = '"null"' ]; then + echo "finality has not caught up to $OPT_HEIGHT yet" else - printf 'Finalized block %s is already available; compare the stable answer below.\n' "$OPT_HEIGHT" - contract_touch_summary "$TARGET_CONTRACT" < /tmp/neardata-final-same-height.json + echo "$FINAL" | contract_touch_summary "$TARGET_CONTRACT" fi ``` -Практический вывод такой: - -- optimistic — это ранний сигнал, на который цикл мониторинга может быстро отреагировать; -- finalized — это стабильный ответ, который уже можно показывать пользователям или использовать в устойчивой автоматизации. +На здоровой сети обе сводки совпадают сразу; ценность — в самом шаблоне, а не в драматичной разнице. Цикл мониторинга, который реагирует на optimistic-сигнал, знает: тот же ответ — на один блок от надёжного. Ветку `finality has not caught up` используйте, когда действительно нужно отличить «увидено optimistically» от «подтверждено» — во время стресса сети этот разрыв расширяется. ### Какой shard действительно изменил мой контракт в этом блоке? - Ход - Сначала используйте весь блок, чтобы найти нужный shard, а затем дайте block-shard доказать само изменение. - - 01Просканируйте список shard внутри финализированного блока и найдите state_changes по вашему контракту. - 02Откройте только тот shard, который действительно изменил контракт. - 03Сохраните совпадающие state_changes и нужные результаты исполнения как доказательство на уровне shard. - -На момент написания недавний финализированный блок `194727131` дал чистый живой пример для `intents.near`: контракт сначала появился как входящий receipt на shard `8`, а затем действительно выполнился и изменил состояние на shard `7`. - -Если для вашей задачи нужен более свежий блок, переиспользуйте ту же сводку из первого примера на нескольких соседних финализированных высотах, а затем подставьте найденную высоту в тот же вызов `block-shard`. +Блоки тонкие — в большинстве финализированных блоков нет мутаций состояния ни для одного конкретного контракта. Идите назад от финализированной головы, пока помощник не покажет `state_changes > 0`, затем откройте «победивший» shard через `/v0/block/{height}/shard/{shard_id}` ради самого payload мутации. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near -EXAMPLE_HEIGHT=194727131 - -curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT" \ - | tee /tmp/neardata-block-194727131.json \ - | jq --arg contract "$TARGET_CONTRACT" '[ - .shards[] | { - shard_id, - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) - } - | select(.incoming_receipts + .execution_outcomes + .state_changes > 0) - ]' - -curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT/shard/7" \ +HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" +TARGET_HEIGHT="" +WINNING_SHARD="" + +for OFFSET in 0 1 2 3 4 5 6 7 8 9; do + H=$((HEAD - OFFSET)) + SUMMARY="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" | contract_touch_summary "$TARGET_CONTRACT")" + if [ "$(echo "$SUMMARY" | jq '.evidence.state_changes')" -gt 0 ]; then + TARGET_HEIGHT=$H + WINNING_SHARD="$(echo "$SUMMARY" | jq -r '.shards[0]')" + echo "$SUMMARY" + break + fi +done + +curl -s "$NEARDATA_BASE_URL/v0/block/$TARGET_HEIGHT/shard/$WINNING_SHARD" \ | jq --arg contract "$TARGET_CONTRACT" '{ shard_id, chunk_hash: .chunk.header.chunk_hash, - matching_state_changes: [ - .state_changes[] - | select(.change.account_id? == $contract) - | {type, cause, account_id: .change.account_id} - ][0:2], - matching_execution_outcomes: [ - .receipt_execution_outcomes[] - | select(.execution_outcome.outcome.executor_id == $contract) - | { - receipt_id: .execution_outcome.id, - executor_id: .execution_outcome.outcome.executor_id, - status: .execution_outcome.outcome.status, - predecessor_id: .receipt.predecessor_id - } - ][0:2] + matching_state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause_type: (.cause | keys[0]), account_id: .change.account_id}][0:3], + matching_execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0]), predecessor_id: .receipt.predecessor_id}][0:3] }' ``` -Практическое правило здесь простое: - -- используйте `block`, когда первый вопрос звучит как «какой shard вообще важен?»; -- используйте `block-shard`, когда настоящий вопрос уже стал таким: «покажи мне сам payload shard, который изменил состояние». +На mainnet `intents.near` стабильно выполняется на shard 7, поэтому обход назад обычно попадает в цель за несколько блоков. Payload shard затем называет конкретные типы state-change (`account_update`, `data_update` и т. п.) и результаты исполнения receipt, которые их породили, — shard-локальное доказательство без догадок. Для менее активных контрактов расширьте диапазон `OFFSET`. -## Когда пора расширять поверхность +## Когда расширить поверхность - Используйте [Transactions API](https://docs.fastnear.com/ru/tx), когда у вас уже есть `tx_hash` и нужен человекочитаемый рассказ о транзакции. -- Используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. +- Используйте [RPC Reference](https://docs.fastnear.com/ru/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. - Используйте [Block Headers](https://docs.fastnear.com/ru/neardata/block-headers), когда нужна только динамика head/finality, а не проверка contract-touch. diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index 29eed18..80bcf7e 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -1,24 +1,10 @@ **Источник:** [https://docs.fastnear.com/ru/neardata/examples](https://docs.fastnear.com/ru/neardata/examples) -Используйте NEAR Data, когда вопрос касается недавней активности сети: появился ли контракт в самом новом семействе блоков, пережил ли optimistic-сигнал finality и какой shard действительно понёс изменение. - ## Примеры -### Был ли мой контракт затронут в последнем финализированном блоке? - - Ход - Сначала дайте NEAR Data ответить на задачу мониторинга, а уже потом сохраняйте tx hash или receipt ID для следующей поверхности, если это вообще понадобится. - - 01last-block-final находит самую новую финализированную высоту. - 02block даёт один недавний гидратированный документ блока с уже присоединёнными данными по shard. - 03Суммируйте прямые транзакции, входящие receipts, результаты выполнения и state_changes для нужного контракта. Считайте state_changes самым сильным сигналом того, что контракт действительно изменился. - -Такой сценарий вполне честно может вернуть `touched: false`, если блок тихий. Это тоже полезный ответ: в самом новом финализированном блоке сейчас нет ничего, что требовало бы более глубокого разбора. +Каждый гидратированный документ блока NEAR Data несёт транзакции, receipts, результаты исполнения и state changes с разбивкой по shard. Три сценария ниже используют один `bash`-помощник, который сворачивает эти четыре сигнала в одну сводку с полями для перехода дальше. Определите его один раз и прогоняйте блоки через него: ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near - contract_touch_summary() { jq -r --arg contract "$1" ' [ .shards[] | { @@ -53,156 +39,80 @@ contract_touch_summary() { sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) }' } +``` -FINAL_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/final" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' -)" +### Был ли мой контракт затронут в последнем финализированном блоке? + +`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок; пройдите по нему и направьте результат сразу в помощник. -printf 'Latest finalized block: %s\n' "$FINAL_LOCATION" +```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near -curl -s "$NEARDATA_BASE_URL$FINAL_LOCATION" \ - | tee /tmp/neardata-final-block.json \ +curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ | contract_touch_summary "$TARGET_CONTRACT" ``` -Читайте ответ так: - -- `touched: false` означает, что самый новый финализированный блок не упомянул и не изменил контракт ни одним из отслеживаемых способов. -- `sample_tx_hash` означает, что у вас уже есть хороший якорь для следующего шага на `/tx`. -- `sample_receipt_id` без tx hash обычно означает, что контракт появился в цепочке через receipts, и NEAR Data уже сэкономила вам более дешёвый этап мониторинга. +Читайте `touched: false` как полный и однозначный ответ для тихого блока. При `true` поля перехода (`sample_tx_hash`, `sample_receipt_id`) сразу ведут вас в [/tx/examples](https://docs.fastnear.com/ru/tx/examples) за человекочитаемой историей. Один запрос заменяет ручной просмотр chunks — и учтите: `touched: true` с `state_changes: 0` — это реальная форма: receipt может попасть в chunk, не вызвав в том же блоке мутации состояния. ### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? - Ход - Используйте один и тот же словарь contract-touch на обеих поверхностях, чтобы сравнение было честным. - - 01last-block-optimistic находит самую новую optimistic-высоту. - 02block-optimistic показывает ранний сигнал для того же контракта. - 03block на той же высоте либо подтверждает то же наблюдение, либо показывает, что finality ещё не догнала. - -Если finality уже догнала, optimistic- и finalized-сводки могут совпасть сразу. Это тоже полезно: ранний сигнал уже попал в стабильную историю. +Optimistic-блоки живут по адресу `/v0/block_opt/{height}`; как только finality догоняет (обычно в пределах одного блока, ~1 с на mainnet), та же высота становится доступна и по `/v0/block/{height}`. Прогоните помощник на обеих и сравните. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near - -contract_touch_summary() { - jq -r --arg contract "$1" ' - [ .shards[] | { - shard_id, - direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) - } - | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) - ] as $rows - | { - height: .block.header.height, - hash: .block.header.hash, - contract: $contract, - touched: (($rows | length) > 0), - shards: ($rows | map(.shard_id)), - evidence: { - direct_txs: (($rows | map(.direct_txs) | add) // 0), - incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), - execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), - state_changes: (($rows | map(.state_changes) | add) // 0) - } - }' -} - OPT_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ - | awk 'tolower($1) == "location:" {print $2}' \ - | tr -d '\r' + | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" - OPT_HEIGHT="${OPT_LOCATION##*/}" -printf 'Latest optimistic block: %s\n' "$OPT_LOCATION" +echo "Optimistic view at $OPT_HEIGHT:" +curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | contract_touch_summary "$TARGET_CONTRACT" -curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" \ - | tee /tmp/neardata-optimistic-block.json \ - | contract_touch_summary "$TARGET_CONTRACT" - -curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT" \ - | tee /tmp/neardata-final-same-height.json >/dev/null - -if jq -e 'type == "null"' /tmp/neardata-final-same-height.json >/dev/null; then - printf 'Finalized block %s is not available yet; finality has not caught up.\n' "$OPT_HEIGHT" +echo "Finalized view at $OPT_HEIGHT:" +FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" +if [ "$(echo "$FINAL" | jq 'type')" = '"null"' ]; then + echo "finality has not caught up to $OPT_HEIGHT yet" else - printf 'Finalized block %s is already available; compare the stable answer below.\n' "$OPT_HEIGHT" - contract_touch_summary "$TARGET_CONTRACT" < /tmp/neardata-final-same-height.json + echo "$FINAL" | contract_touch_summary "$TARGET_CONTRACT" fi ``` -Практический вывод такой: - -- optimistic — это ранний сигнал, на который цикл мониторинга может быстро отреагировать; -- finalized — это стабильный ответ, который уже можно показывать пользователям или использовать в устойчивой автоматизации. +На здоровой сети обе сводки совпадают сразу; ценность — в самом шаблоне, а не в драматичной разнице. Цикл мониторинга, который реагирует на optimistic-сигнал, знает: тот же ответ — на один блок от надёжного. Ветку `finality has not caught up` используйте, когда действительно нужно отличить «увидено optimistically» от «подтверждено» — во время стресса сети этот разрыв расширяется. ### Какой shard действительно изменил мой контракт в этом блоке? - Ход - Сначала используйте весь блок, чтобы найти нужный shard, а затем дайте block-shard доказать само изменение. - - 01Просканируйте список shard внутри финализированного блока и найдите state_changes по вашему контракту. - 02Откройте только тот shard, который действительно изменил контракт. - 03Сохраните совпадающие state_changes и нужные результаты исполнения как доказательство на уровне shard. - -На момент написания недавний финализированный блок `194727131` дал чистый живой пример для `intents.near`: контракт сначала появился как входящий receipt на shard `8`, а затем действительно выполнился и изменил состояние на shard `7`. - -Если для вашей задачи нужен более свежий блок, переиспользуйте ту же сводку из первого примера на нескольких соседних финализированных высотах, а затем подставьте найденную высоту в тот же вызов `block-shard`. +Блоки тонкие — в большинстве финализированных блоков нет мутаций состояния ни для одного конкретного контракта. Идите назад от финализированной головы, пока помощник не покажет `state_changes > 0`, затем откройте «победивший» shard через `/v0/block/{height}/shard/{shard_id}` ради самого payload мутации. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz -TARGET_CONTRACT=intents.near -EXAMPLE_HEIGHT=194727131 - -curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT" \ - | tee /tmp/neardata-block-194727131.json \ - | jq --arg contract "$TARGET_CONTRACT" '[ - .shards[] | { - shard_id, - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length) - } - | select(.incoming_receipts + .execution_outcomes + .state_changes > 0) - ]' - -curl -s "$NEARDATA_BASE_URL/v0/block/$EXAMPLE_HEIGHT/shard/7" \ +HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" +TARGET_HEIGHT="" +WINNING_SHARD="" + +for OFFSET in 0 1 2 3 4 5 6 7 8 9; do + H=$((HEAD - OFFSET)) + SUMMARY="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" | contract_touch_summary "$TARGET_CONTRACT")" + if [ "$(echo "$SUMMARY" | jq '.evidence.state_changes')" -gt 0 ]; then + TARGET_HEIGHT=$H + WINNING_SHARD="$(echo "$SUMMARY" | jq -r '.shards[0]')" + echo "$SUMMARY" + break + fi +done + +curl -s "$NEARDATA_BASE_URL/v0/block/$TARGET_HEIGHT/shard/$WINNING_SHARD" \ | jq --arg contract "$TARGET_CONTRACT" '{ shard_id, chunk_hash: .chunk.header.chunk_hash, - matching_state_changes: [ - .state_changes[] - | select(.change.account_id? == $contract) - | {type, cause, account_id: .change.account_id} - ][0:2], - matching_execution_outcomes: [ - .receipt_execution_outcomes[] - | select(.execution_outcome.outcome.executor_id == $contract) - | { - receipt_id: .execution_outcome.id, - executor_id: .execution_outcome.outcome.executor_id, - status: .execution_outcome.outcome.status, - predecessor_id: .receipt.predecessor_id - } - ][0:2] + matching_state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause_type: (.cause | keys[0]), account_id: .change.account_id}][0:3], + matching_execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0]), predecessor_id: .receipt.predecessor_id}][0:3] }' ``` -Практическое правило здесь простое: - -- используйте `block`, когда первый вопрос звучит как «какой shard вообще важен?»; -- используйте `block-shard`, когда настоящий вопрос уже стал таким: «покажи мне сам payload shard, который изменил состояние». +На mainnet `intents.near` стабильно выполняется на shard 7, поэтому обход назад обычно попадает в цель за несколько блоков. Payload shard затем называет конкретные типы state-change (`account_update`, `data_update` и т. п.) и результаты исполнения receipt, которые их породили, — shard-локальное доказательство без догадок. Для менее активных контрактов расширьте диапазон `OFFSET`. -## Когда пора расширять поверхность +## Когда расширить поверхность - Используйте [Transactions API](https://docs.fastnear.com/ru/tx), когда у вас уже есть `tx_hash` и нужен человекочитаемый рассказ о транзакции. -- Используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. +- Используйте [RPC Reference](https://docs.fastnear.com/ru/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. - Используйте [Block Headers](https://docs.fastnear.com/ru/neardata/block-headers), когда нужна только динамика head/finality, а не проверка contract-touch. diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index bd24dac..1784303 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -2,1857 +2,365 @@ # Примеры RPC -Начинайте с RPC-метода, который отвечает на вопрос. Отправляйте через `broadcast_tx_async`, отслеживайте через `tx` и расширяйте разбор только когда действительно нужны дерево receipts, raw state или трассировка по шардам. +Начинайте с RPC-метода, который отвечает на вопрос. Используйте `tx`, чтобы отследить включение и финальность по хешу транзакции, и расширяйте поверхность только когда нужны дерево receipts, сырой state или трассировка на уровне shard. -## Отправка и отслеживание транзакции +## Включение транзакции и финальность -### Отправить транзакцию и затем проследить её от хеша до финального исполнения +### Отследить транзакцию от хеша до финальности -Нужен стандартный путь отправки через RPC? Отправляйте через `broadcast_tx_async`, затем опрашивайте `tx`. К `EXPERIMENTAL_tx_status` переходите только если нужно дерево receipts. - -Зафиксированная mainnet-транзакция: - -- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- signer: `mike.near` -- receiver: `social.near` -- высота блока включения: `79574923` -- высота блока исполнения receipt для записи в SocialDB: `79574924` - - Ход - Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. - - 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. - 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. - 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. - -**Точки выбора** - -- какой эндпоинт отправки брать первым -- что опрашивать после того, как у вас появился tx hash -- как `wait_until` связан с included-, optimistic- и final-гарантиями -- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` - -```mermaid -flowchart LR - S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] - A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] - T --> F["Транзакция полностью завершена"] - T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] - F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] -``` - -| Метод | Когда использовать | Что вернётся | Роль здесь | -| --- | --- | --- | --- | -| [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | -| [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | -| [`broadcast_tx_commit`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | -| [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | -| [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | - -**Карта статусов и ожидания** - -Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. - -| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | -| --- | --- | --- | -| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | -| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | -| `INCLUDED_FINAL` | блок включения уже финален | `tx` | -| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | -| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | - -Используйте методы так: - -- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash -- используйте `tx` как обычный цикл опроса -- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу - -**Ход** - -- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. -- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. -- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. -- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. +Есть tx hash? Опрашивайте `tx` с минимальным порогом `wait_until`, который отвечает на ваш вопрос. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near -RECEIVER_ID=social.near -``` - -1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow +SIGNER_ACCOUNT_ID=mike.testnet -```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data '{ - "jsonrpc": "2.0", - "id": "fastnear", - "method": "broadcast_tx_async", - "params": ["BASE64_SIGNED_TX"] - }' \ - | jq . -``` - -В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. - -2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "INCLUDED_FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' -``` - -Что здесь важно заметить: - -- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality -- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения -- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt - -3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - receipts_outcome_count: (.result.receipts_outcome | length) - }' -``` - -Что здесь важно заметить: - -- для исторической tx этот вызов тоже возвращается сразу -- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» -- для многих приложений именно здесь и стоит остановиться - -4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" --arg signer_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", id: "fastnear", method: "tx", + params: {tx_hash: $tx_hash, sender_account_id: $signer_id, wait_until: "INCLUDED"} + }')" \ | jq '{ + asked: "INCLUDED", final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status, + status_class: (.result.status | keys[0]), receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». - -5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transaction_handoff: .transactions[0].transaction_outcome.outcome.status - }' -``` - -Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» - -**Рекомендуемый паттерн** - -- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. -- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. -- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. - -## Механика аккаунтов и ключей - -Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. +Для зафиксированной исторической транзакции (1-yocto self-transfer от `mike.testnet`) ответ возвращается как `FINAL`, хотя мы спрашивали `INCLUDED`. Правило такое: **`wait_until` — это минимальный порог, а не целевой**. Узел возвращает тот этап, которого транзакция действительно достигла: для исторической всегда `FINAL`; для полётной выбирайте `INCLUDED`, когда достаточно включения и нужен самый ранний возврат, или `FINAL`, когда реальный вопрос звучит «завершилась ли?». -### Проверить и удалить старые function-call-ключи Near Social +Два перехода отсюда: -Есть старые function-call-ключи для `social.near`? Сначала найдите нужный ключ, потом удалите один конкретный. +- **Отправляете в реальном времени?** [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) возвращает хеш сразу после того, как узел принял payload — отслеживайте отдельно через `tx`. [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) одновременно отправляет и блокируется на выбранном `wait_until` в одном запросе. +- **Нужно дерево receipts, а не только outcome?** `tx` уже включает `receipts_outcome`; расширяйте поверхность до [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) только тогда, когда дополнительно нужны сырые записи receipts. - Ход - Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. +## Инспекция блока на tip - 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. - 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. - 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. +### Описать первый action первой транзакции на текущем tip -**Ход** - -- Через сам RPC получаете полный список access key аккаунта. -- Сужаете этот список до function-call-ключей, привязанных к `social.near`. -- Точно проверяете один выбранный ключ перед удалением. -- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. - -Сразу важны два ограничения: - -- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. -- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». +Пройдите `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export SOCIAL_RECEIVER_ID=social.near -export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' -export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' -export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' -``` +RPC_URL=https://rpc.mainnet.fastnear.com +EMPTY_TX_ROOT=11111111111111111111111111111111 -1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. +BLOCK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ + | jq -r '.result.sync_info.latest_block_hash')" -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } +CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_hash "$BLOCK_HASH" '{ + jsonrpc:"2.0",id:"fastnear",method:"block",params:{block_id:$block_hash} }')" \ - | tee /tmp/fastnear-access-keys.json >/dev/null - -jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' - .result.keys[] - | select((.access_key.permission | type) == "object") - | select(.access_key.permission.FunctionCall.receiver_id == $receiver) - | { - public_key, - nonce: .access_key.nonce, - receiver_id: .access_key.permission.FunctionCall.receiver_id, - method_names: .access_key.permission.FunctionCall.method_names, - allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") - } -' /tmp/fastnear-access-keys.json -``` + | jq -r --arg empty "$EMPTY_TX_ROOT" ' + first(.result.chunks[] | select(.tx_root != $empty) | .chunk_hash) // empty')" -Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. - -2. Ещё раз проверьте конкретный ключ перед удалением. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$DELETE_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } +if [ -z "$CHUNK_HASH" ]; then + echo "tip block had no transactions in any chunk — rerun on the next head" +else + curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg chunk_hash "$CHUNK_HASH" '{ + jsonrpc:"2.0",id:"fastnear",method:"chunk",params:{chunk_id:$chunk_hash} }')" \ - | jq '{nonce: .result.nonce, permission: .result.permission}' + | jq '{ + chunk_shard: .result.header.shard_id, + chunk_height: .result.header.height_included, + first_tx: { + hash: .result.transactions[0].hash, + signer_id: .result.transactions[0].signer_id, + receiver_id: .result.transactions[0].receiver_id + }, + first_action: ( + .result.transactions[0].actions[0] as $a + | if ($a | type) == "string" then {kind: $a} + elif $a.FunctionCall then {kind: "FunctionCall", method_name: $a.FunctionCall.method_name} + else {kind: ($a | keys[0])} end + ) + }' +fi ``` -3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. +Живой запуск возвращает первый chunk текущего tip, первую транзакцию и первый action — часто это `FunctionCall` на контракте моста или tg-бота (mainnet активен). Tip-блок может быть валидным и при этом не содержать транзакций ни в одном chunk — поэтому ветка с пустым результатом остаётся; это честный ответ для тихого момента сети. -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - is_function_call: true, - limit: 10 - }')" \ - | jq '{ - account_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_success - } - ] - }' -``` - -Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. +## Механика аккаунтов и ключей -4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. +### Аудит старых function-call-ключей Near Social -Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. +У создателей накапливаются Social function-call-ключи от каждого кошелька и каждого BOS-шлюза, которым они пользовались. `view_access_key_list` возвращает их все; один фильтр сужает до `social.near`, а **младшие шесть цифр nonce** заодно служат счётчиком использования — новые ключи стартуют с `block_height * 10^6` и инкрементируются на единицу за каждую транзакцию. ```bash -SIGNED_TX_BASE64="$( - node --input-type=module <<'EOF' - -const { - ACCOUNT_ID, - NETWORK_ID = 'mainnet', - RPC_URL = 'https://rpc.mainnet.fastnear.com', - DELETE_PUBLIC_KEY, - FULL_ACCESS_PUBLIC_KEY, - FULL_ACCESS_PRIVATE_KEY, -} = process.env; - -for (const name of [ - 'ACCOUNT_ID', - 'DELETE_PUBLIC_KEY', - 'FULL_ACCESS_PUBLIC_KEY', - 'FULL_ACCESS_PRIVATE_KEY', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); -const derivedPublicKey = keyPair.getPublicKey().toString(); - -if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { - throw new Error( - `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: ACCOUNT_ID, - public_key: FULL_ACCESS_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const transaction = transactions.createTransaction( - ACCOUNT_ID, - utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), - ACCOUNT_ID, - BigInt(accessKey.nonce) + 1n, - [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -)" -``` - -5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +RECEIVER_ID=social.near -```bash curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. - -```bash -if curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_access_key_list",account_id:$account_id,finality:"final"} }')" \ - | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' - .result.keys[] - | select(.public_key == $public_key) - ' >/dev/null; then - echo "Key is still present: $DELETE_PUBLIC_KEY" -else - echo "Key deleted: $DELETE_PUBLIC_KEY" -fi + | jq --arg receiver "$RECEIVER_ID" ' + { + total_keys: (.result.keys | length), + social_fcks: [ + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + created_near_block: (.access_key.nonce / 1000000 | floor), + tx_count: (.access_key.nonce % 1000000), + method_names: (.access_key.permission.FunctionCall.method_names | if . == [] then "ANY" else . end), + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } + ] | sort_by(.tx_count) + }' ``` -**Когда переходить дальше** - -Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. - -### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? - -Ключ уже виден на аккаунте? Вернитесь к транзакции `AddKey`, которая его создала. - - Ход - Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. - - 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. - 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. - 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. - -**Ход** - -- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. -- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. -- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. -- Подтягиваете кандидата по транзакциям и различаете три разных ключа: - - ключ, который был добавлен - - public key верхнеуровневого signer - - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` - -Сразу важны три детали про nonce: +Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на очистку. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность конкретного dapp. -- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. -- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. -- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. +Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом — function-call-ключ не может авторизовать `DeleteKey` — и отправьте через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Повторный запуск того же списка подтверждает удаление. Само подписание — стандартная near-api-js-история и не самая интересная часть аудита. -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' - -# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: -# export ACCOUNT_ID=mike.near -# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' -``` +### Какая транзакция добавила этот `social.near` function-call-ключ и кто её авторизовал? -1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. +Тот же nonce, что считает использование, заодно якорит `AddKey` во времени блоков: новые ключи стартуют примерно с `block_height * 10^6`, так что деление текущего nonce на миллион даёт плотное окно поиска. Один раз гидратируйте кандидатов — и ответ уже несёт достаточно, чтобы отличить прямой `AddKey` от делегированной (meta-tx) авторизации, то есть показать, *какой ключ подписал решение*, а не только какой аккаунт оплатил gas. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$TARGET_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } - }')" \ - | tee /tmp/key-origin-view.json >/dev/null - -CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" -ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" -SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" -SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" - -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_public_key "$TARGET_PUBLIC_KEY" \ - --argjson current_nonce "$CURRENT_NONCE" \ - --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ - --argjson search_from "$SEARCH_FROM" \ - --argjson search_to "$SEARCH_TO" \ - --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ - account_id: $account_id, - target_public_key: $target_public_key, - current_nonce: $current_nonce, - estimated_receipt_block: $estimated_receipt_block, - search_from_tx_block_height: $search_from, - search_to_tx_block_height: $search_to, - permission: ($permission | fromjson) - }' -``` +RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=mike.near +TARGET_PUBLIC_KEY=ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs -Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. +CURRENT_NONCE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg public_key "$TARGET_PUBLIC_KEY" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_access_key",account_id:$account_id,public_key:$public_key,finality:"final"} + }')" \ + | jq -r '.result.nonce')" -2. Ищите историю аккаунта только внутри этого диапазона блоков. +ADD_KEY_BLOCK=$((CURRENT_NONCE / 1000000)) -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_tx_block_height "$SEARCH_FROM" \ - --argjson to_tx_block_height "$SEARCH_TO" '{ - account_id: $account_id, - is_real_signer: true, - from_tx_block_height: $from_tx_block_height, - to_tx_block_height: $to_tx_block_height, - desc: false, - limit: 50 +TX_HASHES="$(curl -s "$TX_BASE_URL/v0/account" -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" \ + --argjson from $((ADD_KEY_BLOCK - 20)) --argjson to $((ADD_KEY_BLOCK + 5)) '{ + account_id: $account_id, is_real_signer: true, + from_tx_block_height: $from, to_tx_block_height: $to, desc: false, limit: 50 }')" \ - | tee /tmp/key-origin-candidates.json >/dev/null - -jq '{ - txs_count, - candidate_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_signer, - is_real_signer, - is_predecessor, - is_receiver - } - ] -}' /tmp/key-origin-candidates.json -``` - -Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. - -3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. - -```bash -TX_HASHES_JSON="$( - jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json -)" - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ - | tee /tmp/key-origin-transactions.json >/dev/null - -jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "direct", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $tx.transaction.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }), - ($tx.transaction.actions[]? - | .Delegate? - | .delegate_action as $delegate - | $delegate.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) + | jq -c '[.account_txs[].transaction_hash]')" + +curl -s "$TX_BASE_URL/v0/transactions" -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES" '{tx_hashes: $tx_hashes}')" \ + | jq --arg target "$TARGET_PUBLIC_KEY" ' + [ .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? | .AddKey? | select(.public_key == $target) + | {mode: "direct", authorizing_public_key: $tx.transaction.public_key, permission: .access_key.permission}), + ($tx.transaction.actions[]? | .Delegate? | .delegate_action as $d + | $d.actions[]? | .AddKey? | select(.public_key == $target) + | {mode: "delegated", authorizing_public_key: $d.public_key, permission: .access_key.permission}) + ) | { - authorization_mode: "delegated", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $delegate.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - tx_block_hash: $tx.execution_outcome.block_hash, - receiver_id: $tx.transaction.receiver_id - } + . -' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + signer_id: $tx.transaction.signer_id, + receiver_id: $tx.transaction.receiver_id, + add_key_receipt: ([$tx.receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]?; .AddKey.public_key? == $target)) + | {receipt_id: .receipt.receipt_id, receipt_block: .execution_outcome.block_height}][0]) + } + . + ]' ``` -Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. +Для ключа `ed25519:7GZg…` аккаунта `mike.near` (первый `social.near` FCK из аудита выше) это разрешается в транзакцию `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` на внешнем блоке tx `112057390`. Внешний signer — `app.herewallet.near`, это relayer HERE Wallet, и `mode: "delegated"` рассказывает остальную историю: relayer оплатил gas, но *авторизующий* ключ внутри Delegate — `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu`, full-access-ключ `mike.near`, который подписал сам `AddKey`. Это та разница meta-tx, которую верхнеуровневый `signer_id` в одиночку скрыл бы. -Для примерного ключа `mike.near` выше совпадение оказывается делегированным: +`add_key_receipt` замыкает картину: `AddKey` выполнился в блоке `112057392`, через два блока после внешней tx, потому что Delegate прыгает из shard relayer в shard целевого аккаунта. Расширьте окно `-20/+5`, если ключом с момента создания пользовались активно. -- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` -- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` -- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` -- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` +### Зарегистрировать FT-хранилище при необходимости и затем перевести токены -4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. +Токены NEP-141 требуют, чтобы каждый получатель предварительно зарегистрировал storage на контракте, прежде чем сможет держать баланс. Два view-вызова авторитетно отвечают на вопрос регистрации *до* отправки — пропуск этой проверки и есть причина, по которой `ft_transfer` в итоге тихо возвращается отправителю. ```bash -ADD_KEY_RECEIPT_ID="$( - jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | .receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) - | .receipt.receipt_id - ' /tmp/key-origin-transactions.json | head -n 1 -)" - -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receipt_block_height: .receipt.block_height, - tx_block_height: .receipt.tx_block_height, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - transaction_hash: .receipt.transaction_hash - }' -``` - -Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. - -**Когда переходить дальше** - -Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. +RPC_URL=https://rpc.testnet.fastnear.com +TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +RECEIVER_ACCOUNT_ID=mike.testnet -### Проверить регистрацию FT storage и затем перевести токены - -Нужно отправить FT безопасно? Сначала проверьте storage registration получателя. - - Ход - Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. - - 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. - 02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит. - 03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог. - -**Сеть** - -- testnet - -**Официальные ссылки** - -- [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) -- [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) - -В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. - -**Ход** - -- Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. -- При необходимости получаете минимальный размер storage deposit. -- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. -- Подтверждаете баланс получателя тем же view-методом самого контракта. - -```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet -export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' -export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' -export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' -``` +ACCOUNT_ARGS_B64="$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -1. Проверьте, зарегистрирован ли получатель на FT-контракте. - -```bash -STORAGE_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$STORAGE_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "storage_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/ft-storage-balance.json >/dev/null - -jq '{ - registered: ((.result.result | implode | fromjson) != null), - storage_balance: (.result.result | implode | fromjson) -}' /tmp/ft-storage-balance.json -``` - -2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. - -```bash -MIN_STORAGE_YOCTO="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_CONTRACT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "storage_balance_bounds", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee /tmp/ft-storage-bounds.json \ - | jq -r '.result.result | implode | fromjson | .min' -)" - -printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" -``` - -3. Определите одну переиспользуемую функцию подписи для function-call к контракту. - -Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. - -```bash -sign_function_call() { - METHOD_NAME="$1" \ - ARGS_JSON="$2" \ - DEPOSIT_YOCTO="$3" \ - GAS_TGAS="$4" \ - node --input-type=module <<'EOF' - -const { - NETWORK_ID = 'testnet', - RPC_URL = 'https://rpc.testnet.fastnear.com', - TOKEN_CONTRACT_ID, - SENDER_ACCOUNT_ID, - SENDER_PUBLIC_KEY, - SENDER_PRIVATE_KEY, - METHOD_NAME, - ARGS_JSON, - DEPOSIT_YOCTO = '0', - GAS_TGAS = '100', -} = process.env; - -for (const name of [ - 'TOKEN_CONTRACT_ID', - 'SENDER_ACCOUNT_ID', - 'SENDER_PUBLIC_KEY', - 'SENDER_PRIVATE_KEY', - 'METHOD_NAME', - 'ARGS_JSON', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); -const signer = await InMemorySigner.fromKeyPair( - NETWORK_ID, - SENDER_ACCOUNT_ID, - keyPair -); - -const derivedPublicKey = keyPair.getPublicKey().toString(); -if (derivedPublicKey !== SENDER_PUBLIC_KEY) { - throw new Error( - `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: SENDER_ACCOUNT_ID, - public_key: SENDER_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const action = transactions.functionCall( - METHOD_NAME, - Buffer.from(ARGS_JSON), - BigInt(GAS_TGAS) * 10n ** 12n, - BigInt(DEPOSIT_YOCTO) -); - -const transaction = transactions.createTransaction( - SENDER_ACCOUNT_ID, - utils.PublicKey.fromString(SENDER_PUBLIC_KEY), - TOKEN_CONTRACT_ID, - BigInt(accessKey.nonce) + 1n, - [action], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - SENDER_ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -} -``` - -4. При необходимости сначала зарегистрируйте storage для получателя. - -```bash -if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then - SIGNED_TX_BASE64="$( - sign_function_call \ - storage_deposit \ - "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id, - registration_only: true - }')" \ - "$MIN_STORAGE_YOCTO" \ - 100 - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash - }' -fi -``` - -5. После готовности storage переведите FT. - -```bash -SIGNED_TX_BASE64="$( - sign_function_call \ - ft_transfer \ - "$(jq -nc \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ - receiver_id: $receiver_id, - amount: $amount, - memo: "FastNear RPC example" - }')" \ - 1 \ - 100 -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } +REGISTERED="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_of",args_base64:$args,finality:"final"} }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Подтвердите FT-баланс получателя тем же view-методом контракта. - -```bash -RECEIVER_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - receiver_balance: (.result.result | implode | fromjson) - }' -``` - -**Когда переходить дальше** - -Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. - -## Чтения контракта и сырое состояние - -Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» - -### Как прочитать сырое состояние контракта напрямую? - -Нужен raw storage, а не только view-метод? Читайте состояние напрямую, затем сравнивайте с view. - -Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`. Значение может меняться; важна последовательность чтений: сначала raw storage, потом проверка через view-метод. + | jq '(.result.result | implode | fromjson) != null')" -- `view_state` читает сырой ключ `STATE` прямо из storage контракта -- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API - - Ход - Сначала прочитайте storage напрямую, а уже потом дайте контракту подтвердить тот же ответ через view-метод. - - 01RPC view_state читает сырой ключ STATE, не запуская код контракта. - 02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта. - 03RPC call_function get_num — это удобная перепроверка того, что прямое чтение storage и view-метод по-прежнему дают один и тот же ответ. - -Здесь важнее ментальная модель, чем сам счётчик: - -- `view_state` — это прямое чтение storage из trie -- `call_function` исполняет read-only-метод контракта -- оба способа могут ответить на один и тот же вопрос, но делают разную работу - -```mermaid -flowchart LR - S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] - R --> D["Декодировать base64 + Borsh"] - D --> N["Знаковое значение счётчика"] - C["RPC call_function get_num"] --> J["JSON-результат метода"] - N --> X["Сравнить"] - J --> X - X --> A["Одно и то же текущее значение"] -``` - -**Ход** - -- Читаете сырой ключ `STATE` из storage контракта. -- Декодируете возвращённые байты в текущее знаковое значение счётчика. -- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. - -```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export CONTRACT_ID=counter.near-examples.testnet -export STATE_PREFIX_BASE64=U1RBVEU= -``` - -1. Сначала прочитайте сырое состояние контракта. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$CONTRACT_ID" \ - --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_state", - account_id: $account_id, - prefix_base64: $prefix_base64, - finality: "final" - } - }')" \ - | tee /tmp/counter-view-state.json >/dev/null - -jq '{ - block_height: .result.block_height, - key_base64: .result.values[0].key, - value_base64: .result.values[0].value -}' /tmp/counter-view-state.json - -jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json -``` - -Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. - -2. Декодируйте байты значения в знаковое число счётчика. - -```bash -RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" - -python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . - -raw = base64.b64decode(sys.argv[1]) - -print(json.dumps({ - "value_base64": sys.argv[1], - "bytes": list(raw), - "hex": raw.hex(), - "signed_i8": int.from_bytes(raw, "little", signed=True), - "unsigned_u8": int.from_bytes(raw, "little", signed=False), -})) -PY -``` - -Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. - -Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. - -Переиспользуемый рецепт здесь короткий: - -- `view_state` возвращает сырые байты в base64 -- вы декодируете эти байты по известной схеме хранения контракта -- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема - -3. Теперь спросите контракт более привычным способом и сравните. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$CONTRACT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_num", - args_base64: "e30=", - finality: "final" - } +MIN_DEPOSIT="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_bounds",args_base64:"e30=",finality:"final"} }')" \ - | tee /tmp/counter-call-function.json >/dev/null - -jq '{ - block_height: .result.block_height, - view_method_value: (.result.result | implode | fromjson) -}' /tmp/counter-call-function.json -``` + | jq -r '.result.result | implode | fromjson | .min')" -4. Сравните оба ответа напрямую. - -```bash -RAW_STATE_NUMBER="$( - python3 - "$RAW_VALUE_BASE64" <<'PY' - -raw = base64.b64decode(sys.argv[1]) -print(int.from_bytes(raw, "little", signed=True)) -PY -)" - -VIEW_METHOD_NUMBER="$( - jq -r '.result.result | implode | fromjson' /tmp/counter-call-function.json -)" - -jq -n \ - --argjson raw_state "$RAW_STATE_NUMBER" \ - --argjson view_method "$VIEW_METHOD_NUMBER" '{ - raw_state: $raw_state, - view_method: $view_method, - agrees_now: ($raw_state == $view_method) - }' +jq -n --argjson registered "$REGISTERED" --arg min "$MIN_DEPOSIT" '{ + registered: $registered, + min_storage_deposit_yocto: $min +}' ``` -Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: +Для зафиксированного testnet-контракта `storage_balance_of({account_id: "mike.testnet"})` возвращает `null` (не зарегистрирован), а `storage_balance_bounds` возвращает `{min: "1250000000000000000000", max: "1250000000000000000000"}` — плоскую комиссию регистрации 0.00125 NEAR. Это собственный ответ контракта, и большего на read-стороне до записи не нужно. -- `view_state` ответил на вопрос, прочитав storage напрямую -- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта - -**Когда переходить дальше** - -Используйте `view_state`, когда настоящий вопрос относится к точному storage, отсутствующему view-методу или проверке известного семейства ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). - -## Трассировка чанков и шардов - -Начинайте отсюда, когда вопрос уже не просто «транзакция прошла или нет?», а «какой именно чанк на шарде исполнил каждый шаг работы?» - -### Проследить, как сгенерированная `Transfer`-receipt переходит из одного чанка на шарде в другой - -Нужен cross-shard handoff, а не только исходная транзакция? Смотрите чанки. - -Зафиксированный mainnet-пример: - -- транзакции `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` от `7419369993.tg` к `game.hot.tg` с вызовом `l2_claim` -- исходному чанку `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` на шарде `11` в блоке `194623170` -- чанку первой receipt `FJWpAYzVXbZwqJUbGXELTnnBBkdvc6W8vWkwuUA3Zwz9` на шарде `11` в блоке `194623171` -- сгенерированной `Transfer`-receipt `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` -- конечному чанку `EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU` на шарде `6` в блоке `194623172` - - Ход - Сначала восстановите receipt-цепочку, потом напрямую посмотрите на сгенерированную receipt, а затем привяжите каждый шаг к тому чанку на шарде, который действительно нёс эту работу. - - 01RPC EXPERIMENTAL_tx_status быстро показывает граф receipts и в какие следующие блоки перешла работа. - 02RPC EXPERIMENTAL_receipt позволяет посмотреть на тело сгенерированной receipt напрямую, а не выводить его только из логов. - 03RPC chunk по блоку и шарду или по хешу чанка доказывает, какая именно единица исполнения на шарде нёсла каждый шаг. - -Оба experimental-метода здесь очень уместны: `EXPERIMENTAL_tx_status` быстро находит граф receipts, а `EXPERIMENTAL_receipt` показывает тело сгенерированной receipt ещё до того, как вы привяжете её обратно к чанкам. - -```mermaid -flowchart LR - A["Tx 8xrc...
блок 194623170
чанк Bfyd...
шард 11"] --> B["Receipt AFC2...
блок 194623171
чанк FJWp...
шард 11
логи ft_mint"] - B --> C["Сгенерированная receipt TtRn...
Transfer 1800930478788300000000 yoctoNEAR"] - C --> D["Чанк EPau...
блок 194623172
шард 6
receipt исполняется"] -``` +Write-сторона — это две подписанных function call (near-api-js `transactions.functionCall` или любая NEAR-библиотека подписи работает одинаково): -**Ход** +- `storage_deposit({account_id: "", registration_only: true})` с депозитом `` yocto и 100 Tgas — пропустите, если `registered: true`. +- `ft_transfer({receiver_id: "", amount: "", memo: "..."})` с депозитом 1 yocto (требует NEP-141) и 100 Tgas. -- Сначала восстанавливаете receipt-цепочку из транзакции. -- Напрямую смотрите на тело сгенерированной `Transfer`-receipt. -- Используете координаты блока и шарда там, где они уже известны. -- Используете хеш чанка там, где другой инструмент уже выдал точный конечный чанк. +Отправьте каждую подписанную транзакцию через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) с `wait_until: "FINAL"`. После этого подтвердите через собственный view-метод контракта — индексированная история не нужна, чтобы доказать, что перевод закрепился: ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_HASH=8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP -export SIGNER_ACCOUNT_ID=7419369993.tg -export ORIGIN_BLOCK_HEIGHT=194623170 -export ORIGIN_SHARD_ID=11 -export RECEIPT_BLOCK_HEIGHT=194623171 -export RECEIPT_SHARD_ID=11 -export GENERATED_RECEIPT_ID=TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4 -export DESTINATION_CHUNK_HASH=EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU -``` - -1. Начните с `EXPERIMENTAL_tx_status`, чтобы сначала увидеть граф receipts, а уже потом думать о чанках. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: [$tx_hash, $signer_account_id] - }')" \ - | tee /tmp/chunk-trace-status.json >/dev/null - -jq '{ - final_execution_status: .result.final_execution_status, - transaction_handoff: .result.transaction_outcome.outcome.status, - receipts: ( - .result.receipts_outcome - | map({ - receipt_id: .id, - executor_id: .outcome.executor_id, - block_hash, - status: .outcome.status - }) - ) -}' /tmp/chunk-trace-status.json -``` - -На что смотреть: - -- подписанная транзакция передаёт работу в receipt `AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y` -- позже в том же графе receipts исполняется `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` для `7419369993.tg` -- уже одного tx status достаточно, чтобы увидеть: настоящая работа продолжилась после исходной подписанной транзакции - -2. Посмотрите на сгенерированную receipt напрямую, чтобы доказать, что это действительно `Transfer`-receipt. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$GENERATED_RECEIPT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_receipt", - params: { - receipt_id: $receipt_id - } +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"ft_balance_of",args_base64:$args,finality:"final"} }')" \ - | tee /tmp/chunk-trace-receipt.json >/dev/null - -jq '{ - predecessor_id: .result.predecessor_id, - receiver_id: .result.receiver_id, - signer_id: .result.receipt.Action.signer_id, - signer_public_key: .result.receipt.Action.signer_public_key, - actions: .result.receipt.Action.actions -}' /tmp/chunk-trace-receipt.json + | jq '{receiver_balance: (.result.result | implode | fromjson)}' ``` -Именно здесь история по шардам становится конкретной: эта цепочка исполнения контракта сгенерировала `Transfer` action receipt от `system` к `7419369993.tg` с депозитом `1800930478788300000000`. +## Чтение контрактов и сырой state -3. Используйте `chunk` по блоку и шарду, чтобы найти исходную подписанную транзакцию на шарде `11`. +### Как прочитать сырое storage контракта напрямую? -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --argjson block_id "$ORIGIN_BLOCK_HEIGHT" \ - --argjson shard_id "$ORIGIN_SHARD_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - block_id: $block_id, - shard_id: $shard_id - } - }')" \ - | jq --arg tx_hash "$TX_HASH" '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created - }, - matching_transaction: ( - .result.transactions[] - | select(.hash == $tx_hash) - | { - hash, - signer_id, - receiver_id - } - ) - }' -``` - -Это самый чистый use case для `chunk` по блоку и шарду: координаты уже известны, а вам нужна точная единица исполнения на шарде, которая несла исходную подписанную транзакцию. - -4. Оставайтесь на том же маршруте и посмотрите, как первая receipt исполняется в следующем блоке на том же шарде. +Два RPC-метода отвечают на один и тот же вопрос о counter с разных слоёв: `view_state` достаёт сырые байты trie без запуска кода, а `call_function` запускает собственный view-метод контракта. Когда они совпадают, вы доказали, что view-метод контракта соответствует его сохранённому состоянию. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --argjson block_id "$RECEIPT_BLOCK_HEIGHT" \ - --argjson shard_id "$RECEIPT_SHARD_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - block_id: $block_id, - shard_id: $shard_id - } - }')" \ - | jq '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created, - tx_root: .result.header.tx_root, - gas_used: .result.header.gas_used - }, - tx_count: (.result.transactions | length), - receipt_count: (.result.receipts | length), - matching_receipt: ( - .result.receipts[] - | select(.receipt_id == "AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y") - | { - receipt_id, - predecessor_id, - receiver_id - } - ) - }' -``` +RPC_URL=https://rpc.testnet.fastnear.com +CONTRACT_ID=counter.near-examples.testnet -Вот здесь chunks наконец становятся естественными: - -- у чанка `tx_root = 11111111111111111111111111111111` -- `tx_count` равен `0` -- но шард всё равно жжёт gas и исполняет receipt `AFC2...` - -То есть этот шард реально сделал работу в этом блоке, хотя новая подписанная транзакция прямо в самом чанке не появилась. +RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_state",account_id:$contract,prefix_base64:"U1RBVEU=",finality:"final"} + }')" \ + | jq -r '.result.values[0].value')" -5. Переключайтесь на `chunk` по хешу, когда другой инструмент уже выдал точный конечный чанк. +RAW_I8="$(python3 -c "import base64,sys;print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg chunk_id "$DESTINATION_CHUNK_HASH" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - chunk_id: $chunk_id - } +METHOD_VALUE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"get_num",args_base64:"e30=",finality:"final"} }')" \ - | jq --arg receipt_id "$GENERATED_RECEIPT_ID" '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created, - tx_root: .result.header.tx_root, - gas_used: .result.header.gas_used - }, - tx_count: (.result.transactions | length), - receipt_count: (.result.receipts | length), - matching_receipt: ( - .result.receipts[] - | select(.receipt_id == $receipt_id) - | { - receipt_id, - predecessor_id, - receiver_id - } - ) - }' -``` - -Это и подтверждает cross-shard hop: + | jq -r '.result.result | implode | fromjson')" -- сгенерированная `Transfer`-receipt исполняется в чанке `EPau...` -- этот чанк живёт на шарде `6`, а не на шарде `11` -- подписанная транзакция стартовала на одном шарде, а следующая receipt завершилась уже на другом +jq -n --arg raw_b64 "$RAW_B64" --argjson raw_i8 "$RAW_I8" --argjson method "$METHOD_VALUE" '{ + raw_state_b64: $raw_b64, + raw_state_decoded: $raw_i8, + view_method_value: $method, + agree: ($raw_i8 == $method) +}' +``` -**Когда переходить дальше** +Для живого counter `view_state` по ключу `STATE` (base64 `U1RBVEU=`) возвращает `"CQ=="` — один байт `0x09`, декодируется как signed i8 в `9`; `get_num` тоже возвращает `9`. Они совпадают, потому что контракт хранит `val: i8` по этому ключу. `signed=True` важен: отрицательный counter выглядел бы как `"/w=="` (байт `0xff` → i8 `-1`, а не u8 `255`). -Используйте [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда вы знаете координаты блока и шарда и хотите буквально спросить: «что этот шард исполнил в этом блоке?» Используйте [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. Используйте [EXPERIMENTAL_tx_status](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) и [EXPERIMENTAL_receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), когда настоящий вопрос относится к трассировке на уровне receipts. Если ещё нужны state changes и produced receipts, расширяйтесь в [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard). +`view_state` — правильный инструмент, когда у контракта нет view-метода для нужных данных, когда нужно сверить view-метод с реальным storage или когда нужна семья ключей, которую контракт не раскрывает публично. Для всего остального `call_function` требует меньше церемоний. Если следующий вопрос становится историческим, а не текущим, расширяйте поверхность до [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). -## Точные чтения NEAR Social и BOS +## NEAR Social и точные чтения BOS -Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. +Оставайтесь на точных чтениях SocialDB и on-chain-проверках готовности — пока вопрос не станет историческим. ### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? -Нужно понять «готово / не готово» до окна подписи? Проверьте storage и права до отправки. - - Ход - Спросите у social.near ровно о двух вещах, которые важны до подписи. - - 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. - 02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near. - 03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer. - -Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: - -- есть ли у целевого аккаунта storage на `social.near`? -- если есть, осталось ли там ещё место? -- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Ход** - -- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. -- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. -- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. -- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». +`social.near` знает две вещи, о которых UI кошелька может только догадываться: сколько storage осталось у каждого аккаунта и разрешена ли делегированному signer запись под этим аккаунтом. Два view-вызова сворачивают вопрос готовности к одному boolean. ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mike.near -export SIGNER_ACCOUNT_ID=mike.near -``` +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near # account you're writing under +SIGNER_ACCOUNT_ID=mike.near # account signing the transaction -1. Сначала проверьте сам аккаунт signer. +STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } +STORAGE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$STORAGE_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"get_account_storage",args_base64:$args,finality:"final"} }')" \ - | tee /tmp/social-publish-signer.json >/dev/null - -jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - signer_account_id: $signer_account_id, - amount: .result.amount, - locked: .result.locked, - storage_usage: .result.storage_usage -}' /tmp/social-publish-signer.json -``` - -Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. - -2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. - -```bash -SOCIAL_STORAGE_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_account_storage", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-account-storage.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - storage: (.result.result | implode | fromjson), - storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) -}' /tmp/social-account-storage.json -``` - -Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. - -3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. - -```bash -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - jq -n --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - signer_matches_target: true, - permission_granted: true, - reason: "owner write" - }' -else - WRITE_PERMISSION_ARGS_BASE64="$( - jq -nc \ - --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ - --arg key "$ACCOUNT_ID" '{ - predecessor_id: $predecessor_id, - key: $key - }' | base64 | tr -d '\n' - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - signer_matches_target: false, - permission_granted: (.result.result | implode | fromjson) - }' -fi -``` - -4. Сведите проверку storage и разрешения в один читаемый итог. - -```bash -AVAILABLE_BYTES="$( - jq -r ' - .result.result - | if length == 0 then "0" - else (implode | fromjson | .available_bytes // 0 | tostring) - end - ' /tmp/social-account-storage.json -)" + | jq '.result.result | implode | fromjson')" if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - PERMISSION_GRANTED=true + PERMISSION=true else - PERMISSION_GRANTED="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq -r '.result.result | implode | fromjson' - )" + PERM_ARGS_B64="$(jq -nc --arg pred "$SIGNER_ACCOUNT_ID" --arg key "$ACCOUNT_ID" '{predecessor_id:$pred,key:$key}' | base64 | tr -d '\n')" + PERMISSION="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$PERM_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"is_write_permission_granted",args_base64:$args,finality:"final"} + }')" \ + | jq '.result.result | implode | fromjson')" fi -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ - --argjson available_bytes "$AVAILABLE_BYTES" \ - --argjson permission_granted "$PERMISSION_GRANTED" '{ +jq -n --argjson storage "$STORAGE" --argjson permission "$PERMISSION" \ + --arg account_id "$ACCOUNT_ID" --arg signer "$SIGNER_ACCOUNT_ID" '{ account_id: $account_id, - signer_account_id: $signer_account_id, - storage_ready: ($available_bytes > 0), - permission_ready: $permission_granted, - ready_to_publish_now: (($available_bytes > 0) and $permission_granted) + signer_account_id: $signer, + storage: $storage, + permission_granted: $permission, + ready_to_publish: (($storage.available_bytes // 0) > 0 and $permission) }' ``` -Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. - -**Когда переходить дальше** - -Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. - -### Что прямо сейчас содержит `mob.near/widget/Profile`? - -Нужен живой исходник виджета и последняя запись этого ключа? Оставайтесь на точных RPC-чтениях. - - Ход - Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. - - 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. - 02RPC call_function get читает точный исходник widget/Profile. - 03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples. - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Ход** - -- Спрашиваете у `social.near` каталог виджетов под `mob.near`. -- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. -- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. -- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mob.near -export WIDGET_NAME=Profile -``` +Для `mike.near`, подписывающего под собой, это возвращает `storage: {used_bytes: 139803, available_bytes: 83891}`, `permission_granted: true` (владельческая запись) и `ready_to_publish: true`. Если `storage` приходит как `null` или `available_bytes: 0`, аккаунту нужен `storage_deposit` на `social.near`, прежде чем новая запись сможет закрепиться. Если signer отличается от цели, ветка permission спрашивает `is_write_permission_granted({predecessor_id, key})` — тот же on-chain-ответ, который dapp видит, прежде чем писать от имени пользователя. Полную поверхность контракта см. в [SocialDB API](https://github.com/NearSocial/social-db#api). -1. Получите каталог виджетов и сохраните высоты блоков последней записи. - -```bash -WIDGET_KEYS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/widget/*")], - options: {return_type: "BlockHeight"} - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "keys", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-widget-keys.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | .[$account_id].widget - | to_entries - | sort_by(.value * -1) - | map({ - widget_name: .key, - last_write_block: .value - }) - | .[0:20] -' /tmp/social-widget-keys.json -``` - -2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. - -```bash -WIDGET_GET_ARGS_BASE64="$( - jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-widget-source.json >/dev/null - -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:25] - | join("\n") - ' /tmp/social-widget-source.json -``` +### Что `mob.near/widget/Profile` содержит прямо сейчас? -3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. +SocialDB хранит BOS-виджеты как ключи `/widget/` на `social.near`. Один `keys` с типом возврата `BlockHeight` возвращает каталог плюс якоря последней записи по каждому виджету; один `get` возвращает точный исходник. ```bash -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - ' /tmp/social-widget-keys.json \ - | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mob.near +WIDGET_NAME=Profile + +KEYS_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} +}' | base64 | tr -d '\n')" + +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$KEYS_ARGS" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"keys",args_base64:$args,finality:"final"} + }')" \ + | jq --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" ' + .result.result | implode | fromjson | .[$account_id].widget as $map + | { + total_widgets: ($map | length), + most_recently_written: ($map | to_entries | sort_by(-.value) | .[0:5] | map({widget: .key, last_write_block: .value})), + target_last_write_block: $map[$widget] + }' + +GET_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget)] +}' | base64 | tr -d '\n')" + +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$GET_ARGS" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"get",args_base64:$args,finality:"final"} + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" ' + .result.result | implode | fromjson | .[$account_id].widget[$widget] | split("\n")[0:20] | join("\n")' ``` -На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. - -**Когда переходить дальше** - -Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. - -## Частые задачи - -### Проверить точное состояние аккаунта или ключа доступа - -**Начните здесь** - -- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для точных полей аккаунта. -- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. - -**Следующая страница при необходимости** - -- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» - -**Остановитесь, когда** - -- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. - -**Переходите дальше, когда** - -- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. -- Пользователя интересует не текущее состояние, а недавняя история активности. - -### Трассировать исполнение на уровне шарда через чанки - -**Начните здесь** - -- Начните с примера выше, если настоящий вопрос звучит как «какой чанк или шард вообще исполнил эту receipt?» -- [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда координаты блока и шарда уже известны. -- [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. - -**Следующая страница при необходимости** - -- [Experimental Receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), если нужно само тело сгенерированной receipt. -- [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard), если chunk payload уже недостаточен и ещё нужны state changes или produced receipts. -- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если вопрос превращается в более широкое async- или callback-расследование. - -**Остановитесь, когда** - -- Уже можно назвать, какой именно чанк и какой шард несли ту работу, которая была важна. - -**Переходите дальше, когда** - -- Пользователю нужны уже не детали исполнения на уровне шарда, а читаемая история транзакции. Тогда переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Проверить один точный блок или снимок состояния протокола - -**Начните здесь** - -- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. -- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» -- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. - -**Следующая страница при необходимости** - -- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. -- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» - -**Остановитесь, когда** - -- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. - -**Переходите дальше, когда** - -- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). -- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Что этот контракт возвращает прямо сейчас? - -**Начните здесь** - -- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» -- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. -- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. -- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» - -**Следующая страница при необходимости** - -- [FastNear API](https://docs.fastnear.com/ru/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. -- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» - -**Остановитесь, когда** - -- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. - -**Переходите дальше, когда** - -- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. -- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» - -### Отправить транзакцию и подтвердить результат - -**Начните здесь** - -- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. -- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. -- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. -- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» - -**Остановитесь, когда** - -- У вас уже есть результат отправки и нужный финальный статус. - -**Переходите дальше, когда** - -- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. -- Нужен уже не единичный статус, а более широкий сценарий расследования. +Для `mob.near` каталог показывает 264 виджета; `Profile` последний раз записывался в блоке `86494825` — годами ранее, стабильно с тех пор — и исходник начинается с `const accountId = props.accountId ?? context.accountId;`. Тип возврата `BlockHeight` ничего не стоит дополнительно и превращает листинг ключей в дешёвую проверку актуальности. Сохраните блок последней записи, если позже захотите доказать, *какая транзакция* записала именно эту версию — передайте его в [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs). ## Частые ошибки -- Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. -- Забывать переключаться с обычного RPC на архивный RPC для старого состояния. -- Воспринимать браузерную аутентификацию в интерфейсе документации как продовый паттерн для бэкенда. -- Продолжать пользоваться низкоуровневыми статусами транзакций, когда вопрос уже превратился в расследование или исторический разбор. +- Начинать в RPC, когда пользователю нужна сводка по активам или индексированная история. +- Забывать переключаться с обычного RPC на archival RPC для более старого state. +- Считать browser auth в UI документации продовым backend-паттерном. +- Оставаться в низкоуровневых вызовах статуса транзакции, когда вопрос уже стал forensic или историческим. -## Полезные связанные страницы +## Связанные страницы - [RPC Reference](https://docs.fastnear.com/ru/rpc) - [Auth & Access](https://docs.fastnear.com/ru/auth) diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index bd24dac..1784303 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -2,1857 +2,365 @@ # Примеры RPC -Начинайте с RPC-метода, который отвечает на вопрос. Отправляйте через `broadcast_tx_async`, отслеживайте через `tx` и расширяйте разбор только когда действительно нужны дерево receipts, raw state или трассировка по шардам. +Начинайте с RPC-метода, который отвечает на вопрос. Используйте `tx`, чтобы отследить включение и финальность по хешу транзакции, и расширяйте поверхность только когда нужны дерево receipts, сырой state или трассировка на уровне shard. -## Отправка и отслеживание транзакции +## Включение транзакции и финальность -### Отправить транзакцию и затем проследить её от хеша до финального исполнения +### Отследить транзакцию от хеша до финальности -Нужен стандартный путь отправки через RPC? Отправляйте через `broadcast_tx_async`, затем опрашивайте `tx`. К `EXPERIMENTAL_tx_status` переходите только если нужно дерево receipts. - -Зафиксированная mainnet-транзакция: - -- хеш транзакции: `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- signer: `mike.near` -- receiver: `social.near` -- высота блока включения: `79574923` -- высота блока исполнения receipt для записи в SocialDB: `79574924` - - Ход - Сначала быстро отправьте, затем идите по более простому статусному пути и переходите к дереву receipts только когда общего статуса уже недостаточно. - - 01RPC broadcast_tx_async — это способ отправки с минимальной задержкой, когда клиент сам будет отслеживать статус дальше. - 02RPC tx — это базовый способ опроса статуса для гарантий включения, optimistic finality и полного завершения. - 03RPC EXPERIMENTAL_tx_status — это уже более глубокое продолжение, когда нужен не общий статус, а дерево receipts. - -**Точки выбора** - -- какой эндпоинт отправки брать первым -- что опрашивать после того, как у вас появился tx hash -- как `wait_until` связан с included-, optimistic- и final-гарантиями -- когда пора перестать использовать `tx` и перейти на `EXPERIMENTAL_tx_status` - -```mermaid -flowchart LR - S["Подписываем транзакцию"] --> A["broadcast_tx_async
возвращает tx hash"] - A --> T["Polling через tx
INCLUDED_FINAL -> FINAL"] - T --> F["Транзакция полностью завершена"] - T -. "только при необходимости" .-> E["EXPERIMENTAL_tx_status
дерево receipts + outcomes"] - F -. "необязательная читаемая история" .-> X["POST /v0/transactions"] -``` - -| Метод | Когда использовать | Что вернётся | Роль здесь | -| --- | --- | --- | --- | -| [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) | клиент сам будет отслеживать транзакцию после отправки | только tx hash | **базовый путь отправки** | -| [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) | вы хотите, чтобы узел сам подождал до выбранного порога | результат tx до уровня `wait_until` | блокирующая альтернатива | -| [`broadcast_tx_commit`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit) | у вас старый код или важен быстрый режим “одним вызовом” | результат исполнения с commit-ожиданием | устаревшее удобство | -| [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) | у вас уже есть tx hash и нужно понять, насколько далеко всё продвинулось | статус и outcomes на выбранном пороге | **базовый путь отслеживания** | -| [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | вам уже нужно дерево receipts или более богатая async-история | полное дерево receipts и детальные outcomes | только глубокое продолжение | - -**Карта статусов и ожидания** - -Значения `wait_until` — это пороги ожидания, а не один постоянный статус транзакции, который стоит считать единственно правильным. Слово `pending` всё ещё полезно в человеческом разговоре, но здесь оно означает только одно: транзакция уже отправлена клиентом, но ещё не включена в блок. - -| Фаза или порог | Что это значит на практике | Лучшая RPC-поверхность | -| --- | --- | --- | -| до включения (`pending`) | клиент уже отправил tx, но она ещё не заякорена в блоке | собственное состояние клиента плюс логика повторов и пауз | -| `INCLUDED` | транзакция уже в блоке, но сам блок ещё может быть не финальным | `tx` | -| `INCLUDED_FINAL` | блок включения уже финален | `tx` | -| `EXECUTED_OPTIMISTIC` | исполнение уже произошло с optimistic finality | `tx` или `send_tx` | -| `FINAL` | всё релевантное исполнение завершилось и финализировалось | по умолчанию `tx`, а `EXPERIMENTAL_tx_status` — если нужна более глубокая детализация | - -Используйте методы так: - -- используйте `broadcast_tx_async`, когда для продолжения вам достаточно tx hash -- используйте `tx` как обычный цикл опроса -- используйте `EXPERIMENTAL_tx_status`, когда следующий вопрос относится уже к дереву receipts, а не к общему статусу - -**Ход** - -- Показываете, как выглядела бы живая отправка через `broadcast_tx_async`. -- Опрашиваете зафиксированную tx через `tx` на двух порогах: `INCLUDED_FINAL` и `FINAL`. -- Только после этого смотрите ту же tx через `EXPERIMENTAL_tx_status`. -- Необязательно переходите в Transactions API, если дальше уже нужна человеческая история. +Есть tx hash? Опрашивайте `tx` с минимальным порогом `wait_until`, который отвечает на ваш вопрос. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb -SIGNER_ACCOUNT_ID=mike.near -RECEIVER_ID=social.near -``` - -1. Если бы это был живой клиентский сценарий, вы бы отправили транзакцию через `broadcast_tx_async` и сохранили возвращённый хеш. +RPC_URL=https://rpc.testnet.fastnear.com +TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow +SIGNER_ACCOUNT_ID=mike.testnet -```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ - --data '{ - "jsonrpc": "2.0", - "id": "fastnear", - "method": "broadcast_tx_async", - "params": ["BASE64_SIGNED_TX"] - }' \ - | jq . -``` - -В реальном приложении именно в этот момент вы перестаёте ждать завершения отправки и переходите к отслеживанию по tx hash. - -2. Опрашивайте `tx` на первом пороге, который уже отвечает на вопрос пользователя. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "INCLUDED_FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' -``` - -Что здесь важно заметить: - -- на живой транзакции этот порог полезен, когда важно понять, что включение уже безопасно с точки зрения finality -- на этой исторической tx ответ приходит сразу, потому что она давно прошла фазу включения -- `transaction_outcome.outcome.status` всё равно показывает, что исходное действие передало управление в исполнение через receipt - -3. Опрашивайте снова, но уже с `FINAL`, когда нужна завершённая история транзакции, а не просто безопасное включение. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "tx", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - receipts_outcome_count: (.result.receipts_outcome | length) - }' -``` - -Что здесь важно заметить: - -- для исторической tx этот вызов тоже возвращается сразу -- в реальном цикле опроса именно этот порог отвечает на вопрос «транзакция уже действительно завершена?» -- для многих приложений именно здесь и стоит остановиться - -4. Переходите к `EXPERIMENTAL_tx_status` только тогда, когда вам уже нужно более богатое дерево receipts. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ + --data "$(jq -nc --arg tx_hash "$TX_HASH" --arg signer_id "$SIGNER_ACCOUNT_ID" '{ + jsonrpc: "2.0", id: "fastnear", method: "tx", + params: {tx_hash: $tx_hash, sender_account_id: $signer_id, wait_until: "INCLUDED"} + }')" \ | jq '{ + asked: "INCLUDED", final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status, + status_class: (.result.status | keys[0]), receipts_outcome_count: (.result.receipts_outcome | length) }' ``` -Сюда стоит идти, когда вопрос меняется с «дошло ли всё до конца?» на «покажи мне дерево receipts и полную async-историю исполнения». - -5. Необязательно: переходите в Transactions API только если дальше нужна именно читаемая история. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transaction_handoff: .transactions[0].transaction_outcome.outcome.status - }' -``` - -Этот последний шаг специально сделан необязательным. Для отправки и отслеживания RPC-правды уже достаточно. Это просто читаемая история на тот случай, если следующий вопрос уже звучит как «что именно произошло?», а не «насколько далеко продвинулась tx?» - -**Рекомендуемый паттерн** - -- Используйте `broadcast_tx_async` плюс опрос через `tx`, если хотите максимум клиентского контроля и самую быструю обратную связь. -- Используйте `send_tx`, когда вам действительно нужен один блокирующий вызов, который подождёт до выбранного порога. -- Используйте `EXPERIMENTAL_tx_status`, когда обычного цикла опроса уже недостаточно и настоящий вопрос относится к дереву receipts. - -## Механика аккаунтов и ключей - -Начинайте отсюда, когда вопрос касается точных прав, точного состояния ключей или одного сценария записи на уровне контракта. +Для зафиксированной исторической транзакции (1-yocto self-transfer от `mike.testnet`) ответ возвращается как `FINAL`, хотя мы спрашивали `INCLUDED`. Правило такое: **`wait_until` — это минимальный порог, а не целевой**. Узел возвращает тот этап, которого транзакция действительно достигла: для исторической всегда `FINAL`; для полётной выбирайте `INCLUDED`, когда достаточно включения и нужен самый ранний возврат, или `FINAL`, когда реальный вопрос звучит «завершилась ли?». -### Проверить и удалить старые function-call-ключи Near Social +Два перехода отсюда: -Есть старые function-call-ключи для `social.near`? Сначала найдите нужный ключ, потом удалите один конкретный. +- **Отправляете в реальном времени?** [`broadcast_tx_async`](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) возвращает хеш сразу после того, как узел принял payload — отслеживайте отдельно через `tx`. [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) одновременно отправляет и блокируется на выбранном `wait_until` в одном запросе. +- **Нужно дерево receipts, а не только outcome?** `tx` уже включает `receipts_outcome`; расширяйте поверхность до [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) только тогда, когда дополнительно нужны сырые записи receipts. - Ход - Сначала сузьте набор точными чтениями ключей, а уже потом подписывайте ровно одно удаление. +## Инспекция блока на tip - 01RPC view_access_key_list находит только function-call-ключи, привязанные к social.near. - 02RPC view_access_key перепроверяет конкретный ключ перед удалением, а POST /v0/account нужен только для необязательного контекста на уровне аккаунта. - 03RPC send_tx отправляет DeleteKey, а RPC view_access_key_list подтверждает результат. +### Описать первый action первой транзакции на текущем tip -**Ход** - -- Через сам RPC получаете полный список access key аккаунта. -- Сужаете этот список до function-call-ключей, привязанных к `social.near`. -- Точно проверяете один выбранный ключ перед удалением. -- Собираете и подписываете транзакцию `DeleteKey` с помощью full-access-key, затем отправляете её через RPC и подтверждаете, что ключ исчез. - -Сразу важны два ограничения: - -- Ключ, которым вы удаляете другой ключ, должен быть full-access. Function-call-key не может подписать действие `DeleteKey`. -- Этот сценарий про точное состояние ключей и очистку. Необязательный шаг с Transactions API ниже даёт контекст на уровне аккаунта, но не является надёжным источником «когда использовался именно этот ключ». +Пройдите `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export SOCIAL_RECEIVER_ID=social.near -export DELETE_PUBLIC_KEY='ed25519:PASTE_THE_KEY_YOU_PLAN_TO_REMOVE' -export FULL_ACCESS_PUBLIC_KEY='ed25519:PASTE_THE_FULL_ACCESS_PUBLIC_KEY_YOU_WILL_SIGN_WITH' -export FULL_ACCESS_PRIVATE_KEY='ed25519:PASTE_THE_MATCHING_FULL_ACCESS_PRIVATE_KEY' -``` +RPC_URL=https://rpc.mainnet.fastnear.com +EMPTY_TX_ROOT=11111111111111111111111111111111 -1. Получите все access key аккаунта, затем сузьте результат до function-call-ключей для `social.near`. +BLOCK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ + | jq -r '.result.sync_info.latest_block_hash')" -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } +CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg block_hash "$BLOCK_HASH" '{ + jsonrpc:"2.0",id:"fastnear",method:"block",params:{block_id:$block_hash} }')" \ - | tee /tmp/fastnear-access-keys.json >/dev/null - -jq -r --arg receiver "$SOCIAL_RECEIVER_ID" ' - .result.keys[] - | select((.access_key.permission | type) == "object") - | select(.access_key.permission.FunctionCall.receiver_id == $receiver) - | { - public_key, - nonce: .access_key.nonce, - receiver_id: .access_key.permission.FunctionCall.receiver_id, - method_names: .access_key.permission.FunctionCall.method_names, - allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") - } -' /tmp/fastnear-access-keys.json -``` + | jq -r --arg empty "$EMPTY_TX_ROOT" ' + first(.result.chunks[] | select(.tx_root != $empty) | .chunk_hash) // empty')" -Выберите один `public_key` из этого отфильтрованного списка и присвойте его переменной `DELETE_PUBLIC_KEY`. - -2. Ещё раз проверьте конкретный ключ перед удалением. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$DELETE_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } +if [ -z "$CHUNK_HASH" ]; then + echo "tip block had no transactions in any chunk — rerun on the next head" +else + curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg chunk_hash "$CHUNK_HASH" '{ + jsonrpc:"2.0",id:"fastnear",method:"chunk",params:{chunk_id:$chunk_hash} }')" \ - | jq '{nonce: .result.nonce, permission: .result.permission}' + | jq '{ + chunk_shard: .result.header.shard_id, + chunk_height: .result.header.height_included, + first_tx: { + hash: .result.transactions[0].hash, + signer_id: .result.transactions[0].signer_id, + receiver_id: .result.transactions[0].receiver_id + }, + first_action: ( + .result.transactions[0].actions[0] as $a + | if ($a | type) == "string" then {kind: $a} + elif $a.FunctionCall then {kind: "FunctionCall", method_name: $a.FunctionCall.method_name} + else {kind: ($a | keys[0])} end + ) + }' +fi ``` -3. Необязательно: получите недавнюю function-call-активность аккаунта, если хотите понять, стоит ли сначала расследовать контекст, а уже потом чистить ключи. +Живой запуск возвращает первый chunk текущего tip, первую транзакцию и первый action — часто это `FunctionCall` на контракте моста или tg-бота (mainnet активен). Tip-блок может быть валидным и при этом не содержать транзакций ни в одном chunk — поэтому ветка с пустым результатом остаётся; это честный ответ для тихого момента сети. -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - is_function_call: true, - limit: 10 - }')" \ - | jq '{ - account_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_success - } - ] - }' -``` - -Этот запрос помогает ответить на вопрос «делал ли аккаунт недавно function-call-операции вообще?», но не доказывает, что использовался именно этот access key. +## Механика аккаунтов и ключей -4. Подпишите транзакцию `DeleteKey` для `DELETE_PUBLIC_KEY` с помощью full-access-key. +### Аудит старых function-call-ключей Near Social -Выполняйте это в каталоге, где установлен `near-api-js@5`. Команда использует переменные окружения выше, получает актуальный nonce для `FULL_ACCESS_PUBLIC_KEY`, запрашивает свежий хеш финализированного блока, подписывает действие `DeleteKey` и сохраняет `signed_tx_base64` в `SIGNED_TX_BASE64`. +У создателей накапливаются Social function-call-ключи от каждого кошелька и каждого BOS-шлюза, которым они пользовались. `view_access_key_list` возвращает их все; один фильтр сужает до `social.near`, а **младшие шесть цифр nonce** заодно служат счётчиком использования — новые ключи стартуют с `block_height * 10^6` и инкрементируются на единицу за каждую транзакцию. ```bash -SIGNED_TX_BASE64="$( - node --input-type=module <<'EOF' - -const { - ACCOUNT_ID, - NETWORK_ID = 'mainnet', - RPC_URL = 'https://rpc.mainnet.fastnear.com', - DELETE_PUBLIC_KEY, - FULL_ACCESS_PUBLIC_KEY, - FULL_ACCESS_PRIVATE_KEY, -} = process.env; - -for (const name of [ - 'ACCOUNT_ID', - 'DELETE_PUBLIC_KEY', - 'FULL_ACCESS_PUBLIC_KEY', - 'FULL_ACCESS_PRIVATE_KEY', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(FULL_ACCESS_PRIVATE_KEY); -const derivedPublicKey = keyPair.getPublicKey().toString(); - -if (derivedPublicKey !== FULL_ACCESS_PUBLIC_KEY) { - throw new Error( - `FULL_ACCESS_PUBLIC_KEY does not match FULL_ACCESS_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const signer = await InMemorySigner.fromKeyPair(NETWORK_ID, ACCOUNT_ID, keyPair); - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: ACCOUNT_ID, - public_key: FULL_ACCESS_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const transaction = transactions.createTransaction( - ACCOUNT_ID, - utils.PublicKey.fromString(FULL_ACCESS_PUBLIC_KEY), - ACCOUNT_ID, - BigInt(accessKey.nonce) + 1n, - [transactions.deleteKey(utils.PublicKey.fromString(DELETE_PUBLIC_KEY))], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -)" -``` - -5. Отправьте подписанную транзакцию через сырой RPC и дождитесь `FINAL`. +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near +RECEIVER_ID=social.near -```bash curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Повторно получите список access key и убедитесь, что нужного ключа больше нет. - -```bash -if curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key_list", - account_id: $account_id, - finality: "final" - } + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_access_key_list",account_id:$account_id,finality:"final"} }')" \ - | jq -e --arg public_key "$DELETE_PUBLIC_KEY" ' - .result.keys[] - | select(.public_key == $public_key) - ' >/dev/null; then - echo "Key is still present: $DELETE_PUBLIC_KEY" -else - echo "Key deleted: $DELETE_PUBLIC_KEY" -fi + | jq --arg receiver "$RECEIVER_ID" ' + { + total_keys: (.result.keys | length), + social_fcks: [ + .result.keys[] + | select((.access_key.permission | type) == "object") + | select(.access_key.permission.FunctionCall.receiver_id == $receiver) + | { + public_key, + created_near_block: (.access_key.nonce / 1000000 | floor), + tx_count: (.access_key.nonce % 1000000), + method_names: (.access_key.permission.FunctionCall.method_names | if . == [] then "ANY" else . end), + allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") + } + ] | sort_by(.tx_count) + }' ``` -**Когда переходить дальше** - -Повторный вызов `view_access_key_list` замыкает сценарий тем же RPC-методом, с которого вы начинали поиск. Если ключ исчез именно там, дополнительный индексированный API уже не нужен, чтобы подтвердить удаление. - -### Какая транзакция добавила этот function-call-ключ для `social.near` и какой ключ его авторизовал? - -Ключ уже виден на аккаунте? Вернитесь к транзакции `AddKey`, которая его создала. - - Ход - Начинаем с уже существующего ключа и идём назад только настолько, насколько это действительно нужно. - - 01RPC view_access_key даёт текущий сохранённый nonce, а это лучшая историческая подсказка в этой истории. - 02POST /v0/account превращает этот nonce в узкое окно кандидатов вместо полного поиска по истории аккаунта. - 03POST /v0/transactions показывает, был ли ключ добавлен напрямую или через делегированную авторизацию, а POST /v0/receipt нужен только для точного блока исполнения AddKey. - -**Ход** - -- Сначала читаете точное состояние ключа через RPC и берёте его текущий nonce как улику. -- Превращаете этот nonce в узкое окно высот блоков для вероятного `AddKey` receipt. -- Ищете историю аккаунта только внутри этого окна, а не сканируете весь аккаунт. -- Подтягиваете кандидата по транзакциям и различаете три разных ключа: - - ключ, который был добавлен - - public key верхнеуровневого signer - - public key, который реально авторизовал изменение, если оно было завернуто в `Delegate` - -Сразу важны три детали про nonce: +Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на очистку. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность конкретного dapp. -- Новый access key получает стартовый nonce, производный от высоты блока примерно как `block_height * 1_000_000`, поэтому деление текущего nonce на `1_000_000` даёт полезное поисковое окно. -- В payload действия `AddKey` часто будет `access_key.nonce: 0`. Это не тот сохранённый nonce, который вы потом видите через `view_access_key`. -- Если после создания ключ уже успели очень активно использовать, просто расширьте окно поиска. +Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом — function-call-ключ не может авторизовать `DeleteKey` — и отправьте через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Повторный запуск того же списка подтверждает удаление. Само подписание — стандартная near-api-js-история и не самая интересная часть аудита. -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_BASE_URL=https://tx.main.fastnear.com -export ACCOUNT_ID=YOUR_ACCOUNT_ID -export TARGET_PUBLIC_KEY='ed25519:PASTE_THE_ACCESS_KEY_YOU_WANT_TO_TRACE' - -# Пример живого ключа, наблюдавшегося 18 апреля 2026 года: -# export ACCOUNT_ID=mike.near -# export TARGET_PUBLIC_KEY='ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs' -``` +### Какая транзакция добавила этот `social.near` function-call-ключ и кто её авторизовал? -1. Сначала прочитайте точное состояние ключа, затем превратите его текущий nonce в поисковое окно. +Тот же nonce, что считает использование, заодно якорит `AddKey` во времени блоков: новые ключи стартуют примерно с `block_height * 10^6`, так что деление текущего nonce на миллион даёт плотное окно поиска. Один раз гидратируйте кандидатов — и ответ уже несёт достаточно, чтобы отличить прямой `AddKey` от делегированной (meta-tx) авторизации, то есть показать, *какой ключ подписал решение*, а не только какой аккаунт оплатил gas. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg public_key "$TARGET_PUBLIC_KEY" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_access_key", - account_id: $account_id, - public_key: $public_key, - finality: "final" - } - }')" \ - | tee /tmp/key-origin-view.json >/dev/null - -CURRENT_NONCE="$(jq -r '.result.nonce' /tmp/key-origin-view.json)" -ESTIMATED_RECEIPT_BLOCK="$(( CURRENT_NONCE / 1000000 + 1 ))" -SEARCH_FROM="$(( ESTIMATED_RECEIPT_BLOCK - 20 ))" -SEARCH_TO="$(( ESTIMATED_RECEIPT_BLOCK + 5 ))" - -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg target_public_key "$TARGET_PUBLIC_KEY" \ - --argjson current_nonce "$CURRENT_NONCE" \ - --argjson estimated_receipt_block "$ESTIMATED_RECEIPT_BLOCK" \ - --argjson search_from "$SEARCH_FROM" \ - --argjson search_to "$SEARCH_TO" \ - --arg permission "$(jq -c '.result.permission' /tmp/key-origin-view.json)" '{ - account_id: $account_id, - target_public_key: $target_public_key, - current_nonce: $current_nonce, - estimated_receipt_block: $estimated_receipt_block, - search_from_tx_block_height: $search_from, - search_to_tx_block_height: $search_to, - permission: ($permission | fromjson) - }' -``` +RPC_URL=https://rpc.mainnet.fastnear.com +TX_BASE_URL=https://tx.main.fastnear.com +ACCOUNT_ID=mike.near +TARGET_PUBLIC_KEY=ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs -Если использовать пример ключа выше, оценочный блок receipt должен получиться `112057392`. +CURRENT_NONCE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg public_key "$TARGET_PUBLIC_KEY" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_access_key",account_id:$account_id,public_key:$public_key,finality:"final"} + }')" \ + | jq -r '.result.nonce')" -2. Ищите историю аккаунта только внутри этого диапазона блоков. +ADD_KEY_BLOCK=$((CURRENT_NONCE / 1000000)) -```bash -curl -s "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --argjson from_tx_block_height "$SEARCH_FROM" \ - --argjson to_tx_block_height "$SEARCH_TO" '{ - account_id: $account_id, - is_real_signer: true, - from_tx_block_height: $from_tx_block_height, - to_tx_block_height: $to_tx_block_height, - desc: false, - limit: 50 +TX_HASHES="$(curl -s "$TX_BASE_URL/v0/account" -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" \ + --argjson from $((ADD_KEY_BLOCK - 20)) --argjson to $((ADD_KEY_BLOCK + 5)) '{ + account_id: $account_id, is_real_signer: true, + from_tx_block_height: $from, to_tx_block_height: $to, desc: false, limit: 50 }')" \ - | tee /tmp/key-origin-candidates.json >/dev/null - -jq '{ - txs_count, - candidate_txs: [ - .account_txs[] - | { - transaction_hash, - tx_block_height, - is_signer, - is_real_signer, - is_predecessor, - is_receiver - } - ] -}' /tmp/key-origin-candidates.json -``` - -Для примерного ключа `mike.near` выше это окно возвращает одну кандидатную транзакцию: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` во внешнем tx-блоке `112057390`. - -3. Подтяните этих кандидатов целиком и оставьте только ту транзакцию, которая действительно добавила ваш целевой ключ. - -```bash -TX_HASHES_JSON="$( - jq -c '[.account_txs[].transaction_hash]' /tmp/key-origin-candidates.json -)" - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES_JSON" '{tx_hashes: $tx_hashes}')" \ - | tee /tmp/key-origin-transactions.json >/dev/null - -jq --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) - | { - authorization_mode: "direct", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $tx.transaction.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }), - ($tx.transaction.actions[]? - | .Delegate? - | .delegate_action as $delegate - | $delegate.actions[]? - | .AddKey? - | select(.public_key == $target_public_key) + | jq -c '[.account_txs[].transaction_hash]')" + +curl -s "$TX_BASE_URL/v0/transactions" -H 'content-type: application/json' \ + --data "$(jq -nc --argjson tx_hashes "$TX_HASHES" '{tx_hashes: $tx_hashes}')" \ + | jq --arg target "$TARGET_PUBLIC_KEY" ' + [ .transactions[] + | . as $tx + | ( + ($tx.transaction.actions[]? | .AddKey? | select(.public_key == $target) + | {mode: "direct", authorizing_public_key: $tx.transaction.public_key, permission: .access_key.permission}), + ($tx.transaction.actions[]? | .Delegate? | .delegate_action as $d + | $d.actions[]? | .AddKey? | select(.public_key == $target) + | {mode: "delegated", authorizing_public_key: $d.public_key, permission: .access_key.permission}) + ) | { - authorization_mode: "delegated", - top_level_signer_id: $tx.transaction.signer_id, - top_level_signer_public_key: $tx.transaction.public_key, - authorizing_public_key: $delegate.public_key, - added_public_key: .public_key, - add_key_payload_nonce: .access_key.nonce, - permission: .access_key.permission - }) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - tx_block_hash: $tx.execution_outcome.block_hash, - receiver_id: $tx.transaction.receiver_id - } + . -' /tmp/key-origin-transactions.json | tee /tmp/key-origin-match.json + transaction_hash: $tx.transaction.hash, + tx_block_height: $tx.execution_outcome.block_height, + signer_id: $tx.transaction.signer_id, + receiver_id: $tx.transaction.receiver_id, + add_key_receipt: ([$tx.receipts[] + | select(any((.receipt.receipt.Action.actions // [])[]?; .AddKey.public_key? == $target)) + | {receipt_id: .receipt.receipt_id, receipt_block: .execution_outcome.block_height}][0]) + } + . + ]' ``` -Если `authorization_mode` равен `direct`, то top-level signer public key и authorizing public key — это один и тот же ключ. Если `authorization_mode` равен `delegated`, то ключ, который реально авторизовал `AddKey`, находится внутри `Delegate.delegate_action.public_key`. +Для ключа `ed25519:7GZg…` аккаунта `mike.near` (первый `social.near` FCK из аудита выше) это разрешается в транзакцию `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` на внешнем блоке tx `112057390`. Внешний signer — `app.herewallet.near`, это relayer HERE Wallet, и `mode: "delegated"` рассказывает остальную историю: relayer оплатил gas, но *авторизующий* ключ внутри Delegate — `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu`, full-access-ключ `mike.near`, который подписал сам `AddKey`. Это та разница meta-tx, которую верхнеуровневый `signer_id` в одиночку скрыл бы. -Для примерного ключа `mike.near` выше совпадение оказывается делегированным: +`add_key_receipt` замыкает картину: `AddKey` выполнился в блоке `112057392`, через два блока после внешней tx, потому что Delegate прыгает из shard relayer в shard целевого аккаунта. Расширьте окно `-20/+5`, если ключом с момента создания пользовались активно. -- `transaction_hash`: `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` -- `top_level_signer_public_key`: `ed25519:Ez817Dgs2uYP5a6GoijzFarcS3SWPT5eEB82VJXsd4oM` -- `authorizing_public_key`: `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu` -- `added_public_key`: `ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs` +### Зарегистрировать FT-хранилище при необходимости и затем перевести токены -4. Необязательно: если нужен ещё и точный блок `AddKey` receipt, сделайте ещё один шаг по `receipt_id`. +Токены NEP-141 требуют, чтобы каждый получатель предварительно зарегистрировал storage на контракте, прежде чем сможет держать баланс. Два view-вызова авторитетно отвечают на вопрос регистрации *до* отправки — пропуск этой проверки и есть причина, по которой `ft_transfer` в итоге тихо возвращается отправителю. ```bash -ADD_KEY_RECEIPT_ID="$( - jq -r --arg target_public_key "$TARGET_PUBLIC_KEY" ' - .transactions[] - | .receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]; .AddKey.public_key? == $target_public_key)) - | .receipt.receipt_id - ' /tmp/key-origin-transactions.json | head -n 1 -)" - -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$ADD_KEY_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receipt_block_height: .receipt.block_height, - tx_block_height: .receipt.tx_block_height, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - transaction_hash: .receipt.transaction_hash - }' -``` - -Для примерного ключа выше точный `AddKey` receipt — это `C5jsTftYwPiibyxdoDKd4LXFFru8n4weDKLV4cfb1bcX` в receipt-блоке `112057392`, тогда как внешняя транзакция попала раньше, в блок `112057390`. - -**Когда переходить дальше** - -Начинайте с точного текущего состояния ключа, потому что именно оно даёт вам nonce-подсказку. Узкое окно в `/v0/account` превращает эту подсказку в маленький набор кандидатов. `/v0/transactions` показывает, был ли ключ добавлен напрямую или через делегированную авторизацию. `/v0/receipt` — это необязательный последний шаг, если нужен именно точный блок исполнения `AddKey`, а не только внешняя транзакция. +RPC_URL=https://rpc.testnet.fastnear.com +TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet +RECEIVER_ACCOUNT_ID=mike.testnet -### Проверить регистрацию FT storage и затем перевести токены - -Нужно отправить FT безопасно? Сначала проверьте storage registration получателя. - - Ход - Сначала прочитайте storage-состояние, а затем тратьте только те write-вызовы, которые действительно нужны переводу. - - 01RPC call_function storage_balance_of показывает, зарегистрирован ли получатель уже сейчас. - 02RPC call_function storage_balance_bounds нужен только тогда, когда перед записью надо узнать точный минимальный депозит. - 03RPC send_tx отправляет storage_deposit и ft_transfer, а RPC call_function ft_balance_of доказывает итог. - -**Сеть** - -- testnet - -**Официальные ссылки** - -- [FT storage и перевод токенов](https://docs.near.org/integrations/fungible-tokens) -- [Предразвёрнутый FT-контракт](https://docs.near.org/tutorials/fts/predeployed-contract) - -В этом сценарии используется безопасный публичный контракт `ft.predeployed.examples.testnet`. Перед началом убедитесь, что у отправителя уже есть немного `gtNEAR` на этом контракте. Если баланса ещё нет, сначала получите небольшой объём через гайд по предразвёрнутому контракту и затем вернитесь к этому сценарию. - -**Ход** - -- Через точные RPC view-вызовы проверяете, есть ли у получателя FT storage на контракте. -- При необходимости получаете минимальный размер storage deposit. -- Подписываете и отправляете `storage_deposit`, а затем `ft_transfer`. -- Подтверждаете баланс получателя тем же view-методом самого контракта. - -```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -export SENDER_ACCOUNT_ID=YOUR_ACCOUNT_ID.testnet -export RECEIVER_ACCOUNT_ID=YOUR_RECEIVER_ID.testnet -export SENDER_PUBLIC_KEY='ed25519:YOUR_FULL_ACCESS_PUBLIC_KEY' -export SENDER_PRIVATE_KEY='ed25519:YOUR_MATCHING_PRIVATE_KEY' -export AMOUNT_YOCTO_GTNEAR='10000000000000000000000' -``` +ACCOUNT_ARGS_B64="$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -1. Проверьте, зарегистрирован ли получатель на FT-контракте. - -```bash -STORAGE_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$STORAGE_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "storage_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/ft-storage-balance.json >/dev/null - -jq '{ - registered: ((.result.result | implode | fromjson) != null), - storage_balance: (.result.result | implode | fromjson) -}' /tmp/ft-storage-balance.json -``` - -2. Если получатель ещё не зарегистрирован, получите минимальный storage deposit. - -```bash -MIN_STORAGE_YOCTO="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$TOKEN_CONTRACT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "storage_balance_bounds", - args_base64: "e30=", - finality: "final" - } - }')" \ - | tee /tmp/ft-storage-bounds.json \ - | jq -r '.result.result | implode | fromjson | .min' -)" - -printf 'Minimum storage deposit: %s yoctoNEAR\n' "$MIN_STORAGE_YOCTO" -``` - -3. Определите одну переиспользуемую функцию подписи для function-call к контракту. - -Выполняйте этот шаг в каталоге, где установлен `near-api-js@5`. Функция ниже читает экспортированные shell-переменные выше и превращает каждый function-call в подписанный payload для отправки через сырой RPC. - -```bash -sign_function_call() { - METHOD_NAME="$1" \ - ARGS_JSON="$2" \ - DEPOSIT_YOCTO="$3" \ - GAS_TGAS="$4" \ - node --input-type=module <<'EOF' - -const { - NETWORK_ID = 'testnet', - RPC_URL = 'https://rpc.testnet.fastnear.com', - TOKEN_CONTRACT_ID, - SENDER_ACCOUNT_ID, - SENDER_PUBLIC_KEY, - SENDER_PRIVATE_KEY, - METHOD_NAME, - ARGS_JSON, - DEPOSIT_YOCTO = '0', - GAS_TGAS = '100', -} = process.env; - -for (const name of [ - 'TOKEN_CONTRACT_ID', - 'SENDER_ACCOUNT_ID', - 'SENDER_PUBLIC_KEY', - 'SENDER_PRIVATE_KEY', - 'METHOD_NAME', - 'ARGS_JSON', -]) { - if (!process.env[name]) { - throw new Error(`Missing ${name}`); - } -} - -async function rpc(method, params) { - const response = await fetch(RPC_URL, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 'fastnear', - method, - params, - }), - }); - const json = await response.json(); - if (json.error) { - throw new Error(JSON.stringify(json.error)); - } - return json.result; -} - -const keyPair = KeyPair.fromString(SENDER_PRIVATE_KEY); -const signer = await InMemorySigner.fromKeyPair( - NETWORK_ID, - SENDER_ACCOUNT_ID, - keyPair -); - -const derivedPublicKey = keyPair.getPublicKey().toString(); -if (derivedPublicKey !== SENDER_PUBLIC_KEY) { - throw new Error( - `SENDER_PUBLIC_KEY does not match SENDER_PRIVATE_KEY (${derivedPublicKey})` - ); -} - -const accessKey = await rpc('query', { - request_type: 'view_access_key', - account_id: SENDER_ACCOUNT_ID, - public_key: SENDER_PUBLIC_KEY, - finality: 'final', -}); - -const block = await rpc('block', { finality: 'final' }); - -const action = transactions.functionCall( - METHOD_NAME, - Buffer.from(ARGS_JSON), - BigInt(GAS_TGAS) * 10n ** 12n, - BigInt(DEPOSIT_YOCTO) -); - -const transaction = transactions.createTransaction( - SENDER_ACCOUNT_ID, - utils.PublicKey.fromString(SENDER_PUBLIC_KEY), - TOKEN_CONTRACT_ID, - BigInt(accessKey.nonce) + 1n, - [action], - utils.serialize.base_decode(block.header.hash) -); - -const [, signedTx] = await transactions.signTransaction( - transaction, - signer, - SENDER_ACCOUNT_ID, - NETWORK_ID -); - -process.stdout.write(Buffer.from(signedTx.encode()).toString('base64')); -EOF -} -``` - -4. При необходимости сначала зарегистрируйте storage для получателя. - -```bash -if jq -e '.result.result | implode | fromjson == null' /tmp/ft-storage-balance.json >/dev/null; then - SIGNED_TX_BASE64="$( - sign_function_call \ - storage_deposit \ - "$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id, - registration_only: true - }')" \ - "$MIN_STORAGE_YOCTO" \ - 100 - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash - }' -fi -``` - -5. После готовности storage переведите FT. - -```bash -SIGNED_TX_BASE64="$( - sign_function_call \ - ft_transfer \ - "$(jq -nc \ - --arg receiver_id "$RECEIVER_ACCOUNT_ID" \ - --arg amount "$AMOUNT_YOCTO_GTNEAR" '{ - receiver_id: $receiver_id, - amount: $amount, - memo: "FastNear RPC example" - }')" \ - 1 \ - 100 -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg signed_tx_base64 "$SIGNED_TX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "send_tx", - params: { - signed_tx_base64: $signed_tx_base64, - wait_until: "FINAL" - } +REGISTERED="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_of",args_base64:$args,finality:"final"} }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - transaction_hash: .result.transaction.hash, - status: .result.status - }' -``` - -6. Подтвердите FT-баланс получателя тем же view-методом контракта. - -```bash -RECEIVER_BALANCE_ARGS_BASE64="$( - jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$TOKEN_CONTRACT_ID" \ - --arg args_base64 "$RECEIVER_BALANCE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "ft_balance_of", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - receiver_balance: (.result.result | implode | fromjson) - }' -``` - -**Когда переходить дальше** - -Это хороший RPC-сценарий, потому что каждый шаг держится рядом с самим контрактом: сначала вы проверяете состояние storage, затем отправляете минимально необходимые change-call, а потом напрямую подтверждаете итоговое состояние на контракте. - -## Чтения контракта и сырое состояние - -Начинайте отсюда, когда вопрос звучит как «достаточно ли мне вызова метода?» против «можно ли прочитать storage напрямую?» - -### Как прочитать сырое состояние контракта напрямую? - -Нужен raw storage, а не только view-метод? Читайте состояние напрямую, затем сравнивайте с view. - -Здесь используется живой публичный testnet-контракт `counter.near-examples.testnet`. Значение может меняться; важна последовательность чтений: сначала raw storage, потом проверка через view-метод. + | jq '(.result.result | implode | fromjson) != null')" -- `view_state` читает сырой ключ `STATE` прямо из storage контракта -- `call_function get_num` спрашивает у контракта то же текущее число через его публичный view API - - Ход - Сначала прочитайте storage напрямую, а уже потом дайте контракту подтвердить тот же ответ через view-метод. - - 01RPC view_state читает сырой ключ STATE, не запуская код контракта. - 02Декодируйте значение из base64 в байты, а затем интерпретируйте эти байты по известной Borsh-схеме контракта. - 03RPC call_function get_num — это удобная перепроверка того, что прямое чтение storage и view-метод по-прежнему дают один и тот же ответ. - -Здесь важнее ментальная модель, чем сам счётчик: - -- `view_state` — это прямое чтение storage из trie -- `call_function` исполняет read-only-метод контракта -- оба способа могут ответить на один и тот же вопрос, но делают разную работу - -```mermaid -flowchart LR - S["RPC view_state
prefix STATE"] --> R["Сырые байты STATE"] - R --> D["Декодировать base64 + Borsh"] - D --> N["Знаковое значение счётчика"] - C["RPC call_function get_num"] --> J["JSON-результат метода"] - N --> X["Сравнить"] - J --> X - X --> A["Одно и то же текущее значение"] -``` - -**Ход** - -- Читаете сырой ключ `STATE` из storage контракта. -- Декодируете возвращённые байты в текущее знаковое значение счётчика. -- Вызываете `get_num` через view-метод и подтверждаете, что ответ метода совпадает с raw-state-декодированием. - -```bash -export NETWORK_ID=testnet -export RPC_URL=https://rpc.testnet.fastnear.com -export CONTRACT_ID=counter.near-examples.testnet -export STATE_PREFIX_BASE64=U1RBVEU= -``` - -1. Сначала прочитайте сырое состояние контракта. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$CONTRACT_ID" \ - --arg prefix_base64 "$STATE_PREFIX_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_state", - account_id: $account_id, - prefix_base64: $prefix_base64, - finality: "final" - } - }')" \ - | tee /tmp/counter-view-state.json >/dev/null - -jq '{ - block_height: .result.block_height, - key_base64: .result.values[0].key, - value_base64: .result.values[0].value -}' /tmp/counter-view-state.json - -jq -r '.result.values[0].key | @base64d' /tmp/counter-view-state.json -``` - -Последняя команда должна вывести `STATE`. Это и есть семейство ключей, которое вы уже заранее знаете, поэтому `view_state` может пойти прямо к raw storage entry, не заставляя контракт исполнять никакой метод. - -2. Декодируйте байты значения в знаковое число счётчика. - -```bash -RAW_VALUE_BASE64="$(jq -r '.result.values[0].value' /tmp/counter-view-state.json)" - -python3 - "$RAW_VALUE_BASE64" <<'PY' | jq . - -raw = base64.b64decode(sys.argv[1]) - -print(json.dumps({ - "value_base64": sys.argv[1], - "bytes": list(raw), - "hex": raw.hex(), - "signed_i8": int.from_bytes(raw, "little", signed=True), - "unsigned_u8": int.from_bytes(raw, "little", signed=False), -})) -PY -``` - -Для этого конкретного контракта достаточно одного байта, потому что Rust-счётчик хранит `val: i8` внутри состояния контракта. Поэтому raw-значение вроде `CQ==` декодируется в один байт `0x09`, а он уже читается как знаковое целое `9`. - -Ещё один важный момент про знак: если бы счётчик был отрицательным, тот же однобайтовый payload всё равно корректно декодировался бы как знаковый `i8` в дополнительном коде. Например, `/w==` — это один байт `0xff`, а значит `-1` как `signed_i8`, а не `255`. - -Переиспользуемый рецепт здесь короткий: - -- `view_state` возвращает сырые байты в base64 -- вы декодируете эти байты по известной схеме хранения контракта -- для больших контрактов схема может быть сложнее, но идея та же: сначала байты, потом схема - -3. Теперь спросите контракт более привычным способом и сравните. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$CONTRACT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_num", - args_base64: "e30=", - finality: "final" - } +MIN_DEPOSIT="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_bounds",args_base64:"e30=",finality:"final"} }')" \ - | tee /tmp/counter-call-function.json >/dev/null - -jq '{ - block_height: .result.block_height, - view_method_value: (.result.result | implode | fromjson) -}' /tmp/counter-call-function.json -``` + | jq -r '.result.result | implode | fromjson | .min')" -4. Сравните оба ответа напрямую. - -```bash -RAW_STATE_NUMBER="$( - python3 - "$RAW_VALUE_BASE64" <<'PY' - -raw = base64.b64decode(sys.argv[1]) -print(int.from_bytes(raw, "little", signed=True)) -PY -)" - -VIEW_METHOD_NUMBER="$( - jq -r '.result.result | implode | fromjson' /tmp/counter-call-function.json -)" - -jq -n \ - --argjson raw_state "$RAW_STATE_NUMBER" \ - --argjson view_method "$VIEW_METHOD_NUMBER" '{ - raw_state: $raw_state, - view_method: $view_method, - agrees_now: ($raw_state == $view_method) - }' +jq -n --argjson registered "$REGISTERED" --arg min "$MIN_DEPOSIT" '{ + registered: $registered, + min_storage_deposit_yocto: $min +}' ``` -Если `agrees_now` равен `true`, значит вы доказали основную мысль этого примера: +Для зафиксированного testnet-контракта `storage_balance_of({account_id: "mike.testnet"})` возвращает `null` (не зарегистрирован), а `storage_balance_bounds` возвращает `{min: "1250000000000000000000", max: "1250000000000000000000"}` — плоскую комиссию регистрации 0.00125 NEAR. Это собственный ответ контракта, и большего на read-стороне до записи не нужно. -- `view_state` ответил на вопрос, прочитав storage напрямую -- `call_function get_num` ответил на тот же вопрос, исполнив публичный read-метод контракта - -**Когда переходить дальше** - -Используйте `view_state`, когда настоящий вопрос относится к точному storage, отсутствующему view-методу или проверке известного семейства ключей. Используйте `call_function`, когда вам нужен публичный read API самого контракта. Если следующий вопрос становится историческим, а не «что там лежит прямо сейчас?», тогда и стоит расширяться в [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). - -## Трассировка чанков и шардов - -Начинайте отсюда, когда вопрос уже не просто «транзакция прошла или нет?», а «какой именно чанк на шарде исполнил каждый шаг работы?» - -### Проследить, как сгенерированная `Transfer`-receipt переходит из одного чанка на шарде в другой - -Нужен cross-shard handoff, а не только исходная транзакция? Смотрите чанки. - -Зафиксированный mainnet-пример: - -- транзакции `8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP` от `7419369993.tg` к `game.hot.tg` с вызовом `l2_claim` -- исходному чанку `BfydTxiPbGY34pejscBytYSXpBsk9gWA2ixKoAe7VsVw` на шарде `11` в блоке `194623170` -- чанку первой receipt `FJWpAYzVXbZwqJUbGXELTnnBBkdvc6W8vWkwuUA3Zwz9` на шарде `11` в блоке `194623171` -- сгенерированной `Transfer`-receipt `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` -- конечному чанку `EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU` на шарде `6` в блоке `194623172` - - Ход - Сначала восстановите receipt-цепочку, потом напрямую посмотрите на сгенерированную receipt, а затем привяжите каждый шаг к тому чанку на шарде, который действительно нёс эту работу. - - 01RPC EXPERIMENTAL_tx_status быстро показывает граф receipts и в какие следующие блоки перешла работа. - 02RPC EXPERIMENTAL_receipt позволяет посмотреть на тело сгенерированной receipt напрямую, а не выводить его только из логов. - 03RPC chunk по блоку и шарду или по хешу чанка доказывает, какая именно единица исполнения на шарде нёсла каждый шаг. - -Оба experimental-метода здесь очень уместны: `EXPERIMENTAL_tx_status` быстро находит граф receipts, а `EXPERIMENTAL_receipt` показывает тело сгенерированной receipt ещё до того, как вы привяжете её обратно к чанкам. - -```mermaid -flowchart LR - A["Tx 8xrc...
блок 194623170
чанк Bfyd...
шард 11"] --> B["Receipt AFC2...
блок 194623171
чанк FJWp...
шард 11
логи ft_mint"] - B --> C["Сгенерированная receipt TtRn...
Transfer 1800930478788300000000 yoctoNEAR"] - C --> D["Чанк EPau...
блок 194623172
шард 6
receipt исполняется"] -``` +Write-сторона — это две подписанных function call (near-api-js `transactions.functionCall` или любая NEAR-библиотека подписи работает одинаково): -**Ход** +- `storage_deposit({account_id: "", registration_only: true})` с депозитом `` yocto и 100 Tgas — пропустите, если `registered: true`. +- `ft_transfer({receiver_id: "", amount: "", memo: "..."})` с депозитом 1 yocto (требует NEP-141) и 100 Tgas. -- Сначала восстанавливаете receipt-цепочку из транзакции. -- Напрямую смотрите на тело сгенерированной `Transfer`-receipt. -- Используете координаты блока и шарда там, где они уже известны. -- Используете хеш чанка там, где другой инструмент уже выдал точный конечный чанк. +Отправьте каждую подписанную транзакцию через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) с `wait_until: "FINAL"`. После этого подтвердите через собственный view-метод контракта — индексированная история не нужна, чтобы доказать, что перевод закрепился: ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export TX_HASH=8xrcQU6Sr1jhnigenBbpfGzk9jN24rLmMqSWT7TF7xJP -export SIGNER_ACCOUNT_ID=7419369993.tg -export ORIGIN_BLOCK_HEIGHT=194623170 -export ORIGIN_SHARD_ID=11 -export RECEIPT_BLOCK_HEIGHT=194623171 -export RECEIPT_SHARD_ID=11 -export GENERATED_RECEIPT_ID=TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4 -export DESTINATION_CHUNK_HASH=EPauY1GBaeAgGf1TikxFcPUhmYsVhLf1cwy14vAYsUuU -``` - -1. Начните с `EXPERIMENTAL_tx_status`, чтобы сначала увидеть граф receipts, а уже потом думать о чанках. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: [$tx_hash, $signer_account_id] - }')" \ - | tee /tmp/chunk-trace-status.json >/dev/null - -jq '{ - final_execution_status: .result.final_execution_status, - transaction_handoff: .result.transaction_outcome.outcome.status, - receipts: ( - .result.receipts_outcome - | map({ - receipt_id: .id, - executor_id: .outcome.executor_id, - block_hash, - status: .outcome.status - }) - ) -}' /tmp/chunk-trace-status.json -``` - -На что смотреть: - -- подписанная транзакция передаёт работу в receipt `AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y` -- позже в том же графе receipts исполняется `TtRn4DzLKzFmGEn5YqoZ35ts411Hz6Ci6WQMjphPMn4` для `7419369993.tg` -- уже одного tx status достаточно, чтобы увидеть: настоящая работа продолжилась после исходной подписанной транзакции - -2. Посмотрите на сгенерированную receipt напрямую, чтобы доказать, что это действительно `Transfer`-receipt. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$GENERATED_RECEIPT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_receipt", - params: { - receipt_id: $receipt_id - } +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"ft_balance_of",args_base64:$args,finality:"final"} }')" \ - | tee /tmp/chunk-trace-receipt.json >/dev/null - -jq '{ - predecessor_id: .result.predecessor_id, - receiver_id: .result.receiver_id, - signer_id: .result.receipt.Action.signer_id, - signer_public_key: .result.receipt.Action.signer_public_key, - actions: .result.receipt.Action.actions -}' /tmp/chunk-trace-receipt.json + | jq '{receiver_balance: (.result.result | implode | fromjson)}' ``` -Именно здесь история по шардам становится конкретной: эта цепочка исполнения контракта сгенерировала `Transfer` action receipt от `system` к `7419369993.tg` с депозитом `1800930478788300000000`. +## Чтение контрактов и сырой state -3. Используйте `chunk` по блоку и шарду, чтобы найти исходную подписанную транзакцию на шарде `11`. +### Как прочитать сырое storage контракта напрямую? -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --argjson block_id "$ORIGIN_BLOCK_HEIGHT" \ - --argjson shard_id "$ORIGIN_SHARD_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - block_id: $block_id, - shard_id: $shard_id - } - }')" \ - | jq --arg tx_hash "$TX_HASH" '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created - }, - matching_transaction: ( - .result.transactions[] - | select(.hash == $tx_hash) - | { - hash, - signer_id, - receiver_id - } - ) - }' -``` - -Это самый чистый use case для `chunk` по блоку и шарду: координаты уже известны, а вам нужна точная единица исполнения на шарде, которая несла исходную подписанную транзакцию. - -4. Оставайтесь на том же маршруте и посмотрите, как первая receipt исполняется в следующем блоке на том же шарде. +Два RPC-метода отвечают на один и тот же вопрос о counter с разных слоёв: `view_state` достаёт сырые байты trie без запуска кода, а `call_function` запускает собственный view-метод контракта. Когда они совпадают, вы доказали, что view-метод контракта соответствует его сохранённому состоянию. ```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --argjson block_id "$RECEIPT_BLOCK_HEIGHT" \ - --argjson shard_id "$RECEIPT_SHARD_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - block_id: $block_id, - shard_id: $shard_id - } - }')" \ - | jq '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created, - tx_root: .result.header.tx_root, - gas_used: .result.header.gas_used - }, - tx_count: (.result.transactions | length), - receipt_count: (.result.receipts | length), - matching_receipt: ( - .result.receipts[] - | select(.receipt_id == "AFC2xUPuuA6BKMMvAV47LLPtzsg3Moh7frvLSuyMeZ2Y") - | { - receipt_id, - predecessor_id, - receiver_id - } - ) - }' -``` +RPC_URL=https://rpc.testnet.fastnear.com +CONTRACT_ID=counter.near-examples.testnet -Вот здесь chunks наконец становятся естественными: - -- у чанка `tx_root = 11111111111111111111111111111111` -- `tx_count` равен `0` -- но шард всё равно жжёт gas и исполняет receipt `AFC2...` - -То есть этот шард реально сделал работу в этом блоке, хотя новая подписанная транзакция прямо в самом чанке не появилась. +RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_state",account_id:$contract,prefix_base64:"U1RBVEU=",finality:"final"} + }')" \ + | jq -r '.result.values[0].value')" -5. Переключайтесь на `chunk` по хешу, когда другой инструмент уже выдал точный конечный чанк. +RAW_I8="$(python3 -c "import base64,sys;print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg chunk_id "$DESTINATION_CHUNK_HASH" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "chunk", - params: { - chunk_id: $chunk_id - } +METHOD_VALUE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:$contract,method_name:"get_num",args_base64:"e30=",finality:"final"} }')" \ - | jq --arg receipt_id "$GENERATED_RECEIPT_ID" '{ - header: { - chunk_hash: .result.header.chunk_hash, - shard_id: .result.header.shard_id, - height_created: .result.header.height_created, - tx_root: .result.header.tx_root, - gas_used: .result.header.gas_used - }, - tx_count: (.result.transactions | length), - receipt_count: (.result.receipts | length), - matching_receipt: ( - .result.receipts[] - | select(.receipt_id == $receipt_id) - | { - receipt_id, - predecessor_id, - receiver_id - } - ) - }' -``` - -Это и подтверждает cross-shard hop: + | jq -r '.result.result | implode | fromjson')" -- сгенерированная `Transfer`-receipt исполняется в чанке `EPau...` -- этот чанк живёт на шарде `6`, а не на шарде `11` -- подписанная транзакция стартовала на одном шарде, а следующая receipt завершилась уже на другом +jq -n --arg raw_b64 "$RAW_B64" --argjson raw_i8 "$RAW_I8" --argjson method "$METHOD_VALUE" '{ + raw_state_b64: $raw_b64, + raw_state_decoded: $raw_i8, + view_method_value: $method, + agree: ($raw_i8 == $method) +}' +``` -**Когда переходить дальше** +Для живого counter `view_state` по ключу `STATE` (base64 `U1RBVEU=`) возвращает `"CQ=="` — один байт `0x09`, декодируется как signed i8 в `9`; `get_num` тоже возвращает `9`. Они совпадают, потому что контракт хранит `val: i8` по этому ключу. `signed=True` важен: отрицательный counter выглядел бы как `"/w=="` (байт `0xff` → i8 `-1`, а не u8 `255`). -Используйте [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда вы знаете координаты блока и шарда и хотите буквально спросить: «что этот шард исполнил в этом блоке?» Используйте [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. Используйте [EXPERIMENTAL_tx_status](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) и [EXPERIMENTAL_receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), когда настоящий вопрос относится к трассировке на уровне receipts. Если ещё нужны state changes и produced receipts, расширяйтесь в [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard). +`view_state` — правильный инструмент, когда у контракта нет view-метода для нужных данных, когда нужно сверить view-метод с реальным storage или когда нужна семья ключей, которую контракт не раскрывает публично. Для всего остального `call_function` требует меньше церемоний. Если следующий вопрос становится историческим, а не текущим, расширяйте поверхность до [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). -## Точные чтения NEAR Social и BOS +## NEAR Social и точные чтения BOS -Эти сценарии остаются на точных чтениях SocialDB и on-chain-проверках готовности, пока вопрос не становится историческим. +Оставайтесь на точных чтениях SocialDB и on-chain-проверках готовности — пока вопрос не станет историческим. ### Может ли этот аккаунт прямо сейчас публиковать в NEAR Social? -Нужно понять «готово / не готово» до окна подписи? Проверьте storage и права до отправки. - - Ход - Спросите у social.near ровно о двух вещах, которые важны до подписи. - - 01RPC view_account проверяет, что signer-аккаунт вообще существует и может отправить транзакцию. - 02RPC call_function get_account_storage показывает, осталось ли у целевого аккаунта место на social.near. - 03RPC call_function is_write_permission_granted нужен только тогда, когда писать пытается другой signer. - -Именно на такие вопросы и должен ответить клиент NEAR Social перед записью: - -- есть ли у целевого аккаунта storage на `social.near`? -- если есть, осталось ли там ещё место? -- если писать под этим аккаунтом пытается другой signer, выдано ли ему право на запись заранее? - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Ход** - -- Проверяете, что аккаунт signer вообще существует и способен оплатить gas. -- Спрашиваете у `social.near`, сколько storage осталось у аккаунта, под которым вы хотите писать. -- Если signer отличается от целевого аккаунта, отдельно спрашиваете у `social.near`, разрешена ли уже такая делегированная запись. -- Превращаете точные RPC-ответы в один понятный итог: «можно писать сейчас» или «сначала устраните блокер». +`social.near` знает две вещи, о которых UI кошелька может только догадываться: сколько storage осталось у каждого аккаунта и разрешена ли делегированному signer запись под этим аккаунтом. Два view-вызова сворачивают вопрос готовности к одному boolean. ```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mike.near -export SIGNER_ACCOUNT_ID=mike.near -``` +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near # account you're writing under +SIGNER_ACCOUNT_ID=mike.near # account signing the transaction -1. Сначала проверьте сам аккаунт signer. +STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } +STORAGE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$STORAGE_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"get_account_storage",args_base64:$args,finality:"final"} }')" \ - | tee /tmp/social-publish-signer.json >/dev/null - -jq --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - signer_account_id: $signer_account_id, - amount: .result.amount, - locked: .result.locked, - storage_usage: .result.storage_usage -}' /tmp/social-publish-signer.json -``` - -Если этот запрос падает, рабочего signer-аккаунта у вас нет. Если проходит, значит signer существует и хотя бы может оплатить gas. - -2. Спросите у `social.near`, сколько storage уже доступно для аккаунта, под которым вы хотите писать. - -```bash -SOCIAL_STORAGE_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$SOCIAL_STORAGE_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get_account_storage", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-account-storage.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - storage: (.result.result | implode | fromjson), - storage_ready: ((.result.result | implode | fromjson | .available_bytes) > 0) -}' /tmp/social-account-storage.json -``` - -Если `available_bytes` больше нуля, значит storage не является блокером. Если метод вернул `null` или `available_bytes` равен нулю, аккаунту нужен `storage_deposit`, иначе новая запись не ляжет. - -3. Если signer отличается от целевого аккаунта, отдельно проверьте и делегированное право на запись. - -```bash -if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - jq -n --arg account_id "$ACCOUNT_ID" '{ - account_id: $account_id, - signer_matches_target: true, - permission_granted: true, - reason: "owner write" - }' -else - WRITE_PERMISSION_ARGS_BASE64="$( - jq -nc \ - --arg predecessor_id "$SIGNER_ACCOUNT_ID" \ - --arg key "$ACCOUNT_ID" '{ - predecessor_id: $predecessor_id, - key: $key - }' | base64 | tr -d '\n' - )" - - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq '{ - signer_matches_target: false, - permission_granted: (.result.result | implode | fromjson) - }' -fi -``` - -4. Сведите проверку storage и разрешения в один читаемый итог. - -```bash -AVAILABLE_BYTES="$( - jq -r ' - .result.result - | if length == 0 then "0" - else (implode | fromjson | .available_bytes // 0 | tostring) - end - ' /tmp/social-account-storage.json -)" + | jq '.result.result | implode | fromjson')" if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then - PERMISSION_GRANTED=true + PERMISSION=true else - PERMISSION_GRANTED="$( - curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WRITE_PERMISSION_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "is_write_permission_granted", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | jq -r '.result.result | implode | fromjson' - )" + PERM_ARGS_B64="$(jq -nc --arg pred "$SIGNER_ACCOUNT_ID" --arg key "$ACCOUNT_ID" '{predecessor_id:$pred,key:$key}' | base64 | tr -d '\n')" + PERMISSION="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$PERM_ARGS_B64" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"is_write_permission_granted",args_base64:$args,finality:"final"} + }')" \ + | jq '.result.result | implode | fromjson')" fi -jq -n \ - --arg account_id "$ACCOUNT_ID" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" \ - --argjson available_bytes "$AVAILABLE_BYTES" \ - --argjson permission_granted "$PERMISSION_GRANTED" '{ +jq -n --argjson storage "$STORAGE" --argjson permission "$PERMISSION" \ + --arg account_id "$ACCOUNT_ID" --arg signer "$SIGNER_ACCOUNT_ID" '{ account_id: $account_id, - signer_account_id: $signer_account_id, - storage_ready: ($available_bytes > 0), - permission_ready: $permission_granted, - ready_to_publish_now: (($available_bytes > 0) and $permission_granted) + signer_account_id: $signer, + storage: $storage, + permission_granted: $permission, + ready_to_publish: (($storage.available_bytes // 0) > 0 and $permission) }' ``` -Если в этом итоговом объекте `ready_to_publish_now: true`, RPC уже дал ответ на вопрос. Если `false`, вы точно знаете, в чём блокер: в storage, в делегированном разрешении или сразу в обоих местах. - -**Когда переходить дальше** - -Весь вопрос остаётся на точных on-chain-чтениях. Именно `social.near` отвечает, осталось ли место у целевого аккаунта и разрешён ли уже делегированный signer. Для проверки готовности к записи в NEAR Social это надёжнее, чем гадать по одному только состоянию кошелька. - -### Что прямо сейчас содержит `mob.near/widget/Profile`? - -Нужен живой исходник виджета и последняя запись этого ключа? Оставайтесь на точных RPC-чтениях. - - Ход - Оставайтесь на точных чтениях SocialDB и расширяйтесь в историю только тогда, когда вопрос уже стал форензикой. - - 01RPC call_function keys показывает каталог виджетов и блоки последней записи под mob.near/widget/*. - 02RPC call_function get читает точный исходник widget/Profile. - 03Если следующий вопрос становится «какая транзакция это записала?», переходите к доказательству записи виджета в /tx/examples. - -**Официальные ссылки** - -- [API SocialDB и поверхность контракта](https://github.com/NearSocial/social-db#api) - -**Ход** - -- Спрашиваете у `social.near` каталог виджетов под `mob.near`. -- Сохраняете высоты блоков, чтобы понимать, когда каждый ключ виджета менялся в последний раз. -- Подтверждаете, что `Profile` действительно есть в каталоге, и читаете его точный исходник через тот же контракт. -- Если следующий вопрос уже звучит как «какая транзакция записала этот виджет?», переходите к сценариям-доказательствам в [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). - -```bash -export NETWORK_ID=mainnet -export RPC_URL=https://rpc.mainnet.fastnear.com -export SOCIAL_CONTRACT_ID=social.near -export ACCOUNT_ID=mob.near -export WIDGET_NAME=Profile -``` +Для `mike.near`, подписывающего под собой, это возвращает `storage: {used_bytes: 139803, available_bytes: 83891}`, `permission_granted: true` (владельческая запись) и `ready_to_publish: true`. Если `storage` приходит как `null` или `available_bytes: 0`, аккаунту нужен `storage_deposit` на `social.near`, прежде чем новая запись сможет закрепиться. Если signer отличается от цели, ветка permission спрашивает `is_write_permission_granted({predecessor_id, key})` — тот же on-chain-ответ, который dapp видит, прежде чем писать от имени пользователя. Полную поверхность контракта см. в [SocialDB API](https://github.com/NearSocial/social-db#api). -1. Получите каталог виджетов и сохраните высоты блоков последней записи. - -```bash -WIDGET_KEYS_ARGS_BASE64="$( - jq -nc --arg account_id "$ACCOUNT_ID" '{ - keys: [($account_id + "/widget/*")], - options: {return_type: "BlockHeight"} - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_KEYS_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "keys", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-widget-keys.json >/dev/null - -jq --arg account_id "$ACCOUNT_ID" ' - .result.result - | implode - | fromjson - | .[$account_id].widget - | to_entries - | sort_by(.value * -1) - | map({ - widget_name: .key, - last_write_block: .value - }) - | .[0:20] -' /tmp/social-widget-keys.json -``` - -2. Подтвердите, что `Profile` действительно есть в каталоге, и распечатайте точный исходник, который хранится в SocialDB. - -```bash -WIDGET_GET_ARGS_BASE64="$( - jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" '{ - keys: [($account_id + "/widget/" + $widget_name)] - }' | base64 | tr -d '\n' -)" - -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$SOCIAL_CONTRACT_ID" \ - --arg args_base64 "$WIDGET_GET_ARGS_BASE64" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "call_function", - account_id: $account_id, - method_name: "get", - args_base64: $args_base64, - finality: "final" - } - }')" \ - | tee /tmp/social-widget-source.json >/dev/null - -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - | split("\n")[0:25] - | join("\n") - ' /tmp/social-widget-source.json -``` +### Что `mob.near/widget/Profile` содержит прямо сейчас? -3. Заберите высоту последней записи для этого же виджета, чтобы оставить себе один полезный исторический якорь. +SocialDB хранит BOS-виджеты как ключи `/widget/` на `social.near`. Один `keys` с типом возврата `BlockHeight` возвращает каталог плюс якоря последней записи по каждому виджету; один `get` возвращает точный исходник. ```bash -jq -r \ - --arg account_id "$ACCOUNT_ID" \ - --arg widget_name "$WIDGET_NAME" ' - .result.result - | implode - | fromjson - | .[$account_id].widget[$widget_name] - ' /tmp/social-widget-keys.json \ - | xargs -I{} printf 'Last write block for %s/%s: %s\n' "$ACCOUNT_ID" "$WIDGET_NAME" "{}" +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mob.near +WIDGET_NAME=Profile + +KEYS_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + keys: [($account_id + "/widget/*")], + options: {return_type: "BlockHeight"} +}' | base64 | tr -d '\n')" + +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$KEYS_ARGS" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"keys",args_base64:$args,finality:"final"} + }')" \ + | jq --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" ' + .result.result | implode | fromjson | .[$account_id].widget as $map + | { + total_widgets: ($map | length), + most_recently_written: ($map | to_entries | sort_by(-.value) | .[0:5] | map({widget: .key, last_write_block: .value})), + target_last_write_block: $map[$widget] + }' + +GET_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" '{ + keys: [($account_id + "/widget/" + $widget)] +}' | base64 | tr -d '\n')" + +curl -s "$RPC_URL" -H 'content-type: application/json' \ + --data "$(jq -nc --arg args "$GET_ARGS" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"call_function",account_id:"social.near",method_name:"get",args_base64:$args,finality:"final"} + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" ' + .result.result | implode | fromjson | .[$account_id].widget[$widget] | split("\n")[0:20] | join("\n")' ``` -На момент написания живая высота последней записи для `mob.near/widget/Profile` была `86494825`. Сохраните этот блок, если позже понадобится доказать, какая транзакция записала именно эту версию. - -**Когда переходить дальше** - -Иногда правильный RPC-ответ очень простой: вот виджет, вот его живой исходник, и вот высота блока, которую стоит сохранить, если позже понадобится provenance. - -## Частые задачи - -### Проверить точное состояние аккаунта или ключа доступа - -**Начните здесь** - -- [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) для точных полей аккаунта. -- [View Access Key](https://docs.fastnear.com/ru/rpc/account/view-access-key) или [View Access Key List](https://docs.fastnear.com/ru/rpc/account/view-access-key-list) для проверки ключей. - -**Следующая страница при необходимости** - -- [FastNear API full account view](https://docs.fastnear.com/ru/api/v1/account-full), если после проверки точного RPC-состояния нужна ещё и понятная сводка по активам. -- [Transactions API account history](https://docs.fastnear.com/ru/tx/account), если следующий вопрос звучит как «что этот аккаунт делал недавно?» - -**Остановитесь, когда** - -- Поля RPC уже отвечают на вопрос о состоянии или правах доступа. - -**Переходите дальше, когда** - -- Пользователю нужны балансы, NFT, стейкинг или другая понятная сводка по аккаунту. -- Пользователя интересует не текущее состояние, а недавняя история активности. - -### Трассировать исполнение на уровне шарда через чанки - -**Начните здесь** - -- Начните с примера выше, если настоящий вопрос звучит как «какой чанк или шард вообще исполнил эту receipt?» -- [Chunk by Block and Shard](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-block-shard), когда координаты блока и шарда уже известны. -- [Chunk by Hash](https://docs.fastnear.com/ru/rpc/protocol/chunk-by-hash), когда другой инструмент уже выдал точный хеш чанка. - -**Следующая страница при необходимости** - -- [Experimental Receipt](https://docs.fastnear.com/ru/rpc/transaction/experimental-receipt), если нужно само тело сгенерированной receipt. -- [Block Shard](https://docs.fastnear.com/ru/neardata/block-shard), если chunk payload уже недостаточен и ещё нужны state changes или produced receipts. -- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если вопрос превращается в более широкое async- или callback-расследование. - -**Остановитесь, когда** - -- Уже можно назвать, какой именно чанк и какой шард несли ту работу, которая была важна. - -**Переходите дальше, когда** - -- Пользователю нужны уже не детали исполнения на уровне шарда, а читаемая история транзакции. Тогда переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Проверить один точный блок или снимок состояния протокола - -**Начните здесь** - -- [Block by ID](https://docs.fastnear.com/ru/rpc/block/block-by-id) или [Block by Height](https://docs.fastnear.com/ru/rpc/block/block-by-height), когда вы уже знаете, какой именно блок вас интересует. -- [Latest Block](https://docs.fastnear.com/ru/rpc/protocol/latest-block), когда вопрос звучит как «какая сейчас голова цепочки?» -- [Status](https://docs.fastnear.com/ru/rpc/protocol/status), [Health](https://docs.fastnear.com/ru/rpc/protocol/health) или [Network Info](https://docs.fastnear.com/ru/rpc/protocol/network-info), когда настоящий вопрос относится к состоянию узла или сети, а не к истории транзакций. - -**Следующая страница при необходимости** - -- [Block Effects](https://docs.fastnear.com/ru/rpc/block/block-effects), если ответ по блоку уже говорит, какой это блок, но всё ещё не объясняет, что в нём изменилось. -- [Transactions API block history](https://docs.fastnear.com/ru/tx/block) или [Transactions API block range](https://docs.fastnear.com/ru/tx/blocks), если вопрос превращается в «что вообще происходило вокруг этого блока?», а не только «что говорит payload этого блока?» - -**Остановитесь, когда** - -- Один точный ответ по блоку или протоколу уже напрямую отвечает на вопрос. - -**Переходите дальше, когда** - -- Нужно следить за появлением новых блоков, а не разбирать один точный снимок. Переходите к [NEAR Data API](https://docs.fastnear.com/ru/neardata). -- Нужна читаемая история по многим транзакциям, а не только payload одного блока. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Что этот контракт возвращает прямо сейчас? - -**Начните здесь** - -- Начните с примера со счётчиком выше, если настоящий выбор звучит как «мне нужен `call_function` или `view_state`?» или «можно ли прочитать storage напрямую вместо вызова метода?» -- [Call Function](https://docs.fastnear.com/ru/rpc/contract/call-function), когда вы уже знаете нужный view-метод и хотите просто получить его точный результат. -- [View State](https://docs.fastnear.com/ru/rpc/contract/view-state), когда настоящий вопрос относится к сырому хранилищу контракта или key prefix, а не к результату метода. -- [View Code](https://docs.fastnear.com/ru/rpc/contract/view-code), когда настоящий вопрос звучит как «есть ли здесь код вообще?» или «какой code hash здесь развёрнут?» - -**Следующая страница при необходимости** - -- [FastNear API](https://docs.fastnear.com/ru/api), если сырой ответ контракта технически правильный, но пользователю на самом деле нужна читаемая сводка по активам или аккаунту. -- [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv), если следующий вопрос уже звучит как «как этот storage key выглядел со временем?», а не «что там лежит сейчас?» - -**Остановитесь, когда** - -- View-вызов, чтение хранилища или code hash уже дают точный ответ на вопрос по контракту. - -**Переходите дальше, когда** - -- Пользователю нужна индексированная история или более простое резюме вместо сырого ответа контракта. -- Вопрос смещается от «что он возвращает сейчас?» к «что менялось со временем?» - -### Отправить транзакцию и подтвердить результат - -**Начните здесь** - -- Сначала поднимитесь к готовому примеру выше, если настоящий вопрос в том, какой эндпоинт отправки выбрать и как потом отслеживать транзакцию до завершения. -- [Send Transaction](https://docs.fastnear.com/ru/rpc/transaction/send-tx), когда нужна RPC-отправка с явной семантикой ожидания. -- [Broadcast Transaction Async](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-async) или [Broadcast Transaction Commit](https://docs.fastnear.com/ru/rpc/transaction/broadcast-tx-commit), когда важны именно эти режимы отправки. -- [Transaction Status](https://docs.fastnear.com/ru/rpc/transaction/tx-status), чтобы подтвердить финальный результат. - -**Следующая страница при необходимости** - -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), если после отправки нужна более читаемая история по транзакции. -- [Receipt Lookup](https://docs.fastnear.com/ru/tx/receipt), если нужно исследовать последующее исполнение или цепочку обратных вызовов. -- [Transactions Examples](https://docs.fastnear.com/ru/tx/examples), если следующий вопрос звучит так: «одно действие в пакете транзакции упало, а ранние действия откатились или нет?» - -**Остановитесь, когда** - -- У вас уже есть результат отправки и нужный финальный статус. - -**Переходите дальше, когда** - -- Следующий вопрос относится к квитанциям, затронутым аккаунтам или истории исполнения в более человеческом порядке. -- Нужен уже не единичный статус, а более широкий сценарий расследования. +Для `mob.near` каталог показывает 264 виджета; `Profile` последний раз записывался в блоке `86494825` — годами ранее, стабильно с тех пор — и исходник начинается с `const accountId = props.accountId ?? context.accountId;`. Тип возврата `BlockHeight` ничего не стоит дополнительно и превращает листинг ключей в дешёвую проверку актуальности. Сохраните блок последней записи, если позже захотите доказать, *какая транзакция* записала именно эту версию — передайте его в [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs). ## Частые ошибки -- Начинать с RPC, когда пользователю на самом деле нужна сводка по активам или индексированная история. -- Забывать переключаться с обычного RPC на архивный RPC для старого состояния. -- Воспринимать браузерную аутентификацию в интерфейсе документации как продовый паттерн для бэкенда. -- Продолжать пользоваться низкоуровневыми статусами транзакций, когда вопрос уже превратился в расследование или исторический разбор. +- Начинать в RPC, когда пользователю нужна сводка по активам или индексированная история. +- Забывать переключаться с обычного RPC на archival RPC для более старого state. +- Считать browser auth в UI документации продовым backend-паттерном. +- Оставаться в низкоуровневых вызовах статуса транзакции, когда вопрос уже стал forensic или историческим. -## Полезные связанные страницы +## Связанные страницы - [RPC Reference](https://docs.fastnear.com/ru/rpc) - [Auth & Access](https://docs.fastnear.com/ru/auth) diff --git a/static/ru/snapshots/examples.md b/static/ru/snapshots/examples.md index 9a660c3..15df6af 100644 --- a/static/ru/snapshots/examples.md +++ b/static/ru/snapshots/examples.md @@ -1,30 +1,12 @@ **Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) -## Быстрый старт +## Пути восстановления mainnet -Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. +Выберите один класс — optimized `fast-rpc`, standard RPC или archival — и выполняйте только команды этого пути. Смешивание классов приводит к несогласованным данным узла. -Эти helper-скрипты поддерживаются FastNear и оптимизированы под скорость восстановления. Если в вашей среде нужен review изменений, сначала скачайте скрипт, проверьте его и только потом запускайте, вместо прямого piping в `bash`. +FastNear поддерживает эти скрипты ради скорости восстановления. Если в вашей среде требуется review изменений, скачайте скрипт и проверьте его перед запуском (вместо прямой передачи через pipe в `bash`). -```bash -DATA_PATH=~/.near/data - -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ - | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash -``` - -## Пример - -### Выбрать и выполнить правильный сценарий восстановления mainnet - - Ход - Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него. - - 01Сначала решите, нужен ли вам optimized fast-rpc, обычный RPC или архивный режим. - 02Если нужен архив, сначала зафиксируйте одну точную высоту snapshot-блока и дальше переиспользуйте только её. - 03Выполняйте только команды выбранного пути и не смешивайте optimized, standard и archival шаги в одном сценарии. - -### Минимальная команда для optimized mainnet `fast-rpc` +### Optimized mainnet `fast-rpc` ```bash DATA_PATH=~/.near/data @@ -33,7 +15,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash ``` -### Минимальная команда для стандартного mainnet RPC +### Standard mainnet RPC ```bash DATA_PATH=~/.near/data @@ -42,14 +24,9 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet bash ``` -### Shell-сценарий архивного восстановления mainnet -Для архивного восстановления сначала получите одну высоту snapshot и переиспользуйте её для hot- и cold-data. +### Archival mainnet -**Ход** - -- Один раз получаете последнюю высоту архивного снапшота mainnet. -- Сохраняете её в `LATEST`. -- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. +Для archival нужны две загрузки из *одного и того же* среза снапшота. Зафиксируйте одно значение `LATEST` и переиспользуйте его и для hot-, и для cold-data — смешивание высот даёт внутренне несогласованный набор данных и удивляет nearcore на этапе настройки. ```bash HOT_DATA_PATH=~/.near/data @@ -65,20 +42,16 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_TYPE=cold-data DATA_PATH="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash ``` -**Когда переходить дальше** - -Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. - ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. -- Выбирать архивное восстановление, когда достаточно обычного или optimized RPC. -- Забывать про разделение hot/cold-данных для архивного режима. +- Выбирать archival-восстановление, когда достаточно standard или optimized RPC. +- Забывать про разделение hot/cold-хранилища для archival-данных. - Переходить к командам до выбора сети и цели узла. -## Полезные связанные страницы +## Связанные страницы -- [Обзор снапшотов](https://docs.fastnear.com/ru/snapshots) +- [Обзор snapshot](https://docs.fastnear.com/ru/snapshots) - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) - [RPC Reference](https://docs.fastnear.com/ru/rpc) diff --git a/static/ru/snapshots/examples/index.md b/static/ru/snapshots/examples/index.md index 9a660c3..15df6af 100644 --- a/static/ru/snapshots/examples/index.md +++ b/static/ru/snapshots/examples/index.md @@ -1,30 +1,12 @@ **Источник:** [https://docs.fastnear.com/ru/snapshots/examples](https://docs.fastnear.com/ru/snapshots/examples) -## Быстрый старт +## Пути восстановления mainnet -Если задача звучит просто как «быстро вернуть mainnet RPC-узел», начните с одной рабочей команды. +Выберите один класс — optimized `fast-rpc`, standard RPC или archival — и выполняйте только команды этого пути. Смешивание классов приводит к несогласованным данным узла. -Эти helper-скрипты поддерживаются FastNear и оптимизированы под скорость восстановления. Если в вашей среде нужен review изменений, сначала скачайте скрипт, проверьте его и только потом запускайте, вместо прямого piping в `bash`. +FastNear поддерживает эти скрипты ради скорости восстановления. Если в вашей среде требуется review изменений, скачайте скрипт и проверьте его перед запуском (вместо прямой передачи через pipe в `bash`). -```bash -DATA_PATH=~/.near/data - -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh \ - | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash -``` - -## Пример - -### Выбрать и выполнить правильный сценарий восстановления mainnet - - Ход - Сначала выберите класс восстановления, а затем выполните минимальную последовательность команд именно для него. - - 01Сначала решите, нужен ли вам optimized fast-rpc, обычный RPC или архивный режим. - 02Если нужен архив, сначала зафиксируйте одну точную высоту snapshot-блока и дальше переиспользуйте только её. - 03Выполняйте только команды выбранного пути и не смешивайте optimized, standard и archival шаги в одном сценарии. - -### Минимальная команда для optimized mainnet `fast-rpc` +### Optimized mainnet `fast-rpc` ```bash DATA_PATH=~/.near/data @@ -33,7 +15,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash ``` -### Минимальная команда для стандартного mainnet RPC +### Standard mainnet RPC ```bash DATA_PATH=~/.near/data @@ -42,14 +24,9 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_PATH="$DATA_PATH" CHAIN_ID=mainnet bash ``` -### Shell-сценарий архивного восстановления mainnet -Для архивного восстановления сначала получите одну высоту snapshot и переиспользуйте её для hot- и cold-data. +### Archival mainnet -**Ход** - -- Один раз получаете последнюю высоту архивного снапшота mainnet. -- Сохраняете её в `LATEST`. -- Переиспользуете ровно эту же высоту блока и для hot-data, и для cold-data. +Для archival нужны две загрузки из *одного и того же* среза снапшота. Зафиксируйте одно значение `LATEST` и переиспользуйте его и для hot-, и для cold-data — смешивание высот даёт внутренне несогласованный набор данных и удивляет nearcore на этапе настройки. ```bash HOT_DATA_PATH=~/.near/data @@ -65,20 +42,16 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ | DATA_TYPE=cold-data DATA_PATH="$COLD_DATA_PATH" CHAIN_ID=mainnet BLOCK="$LATEST" bash ``` -**Когда переходить дальше** - -Архивные hot- и cold-данные должны происходить из одного и того же среза снапшота. Повторное использование одного сохранённого значения `LATEST` в обеих командах сохраняет внутреннюю согласованность архива и делает последующую настройку nearcore заметно менее неожиданной. - ## Частые ошибки - Использовать документацию по снапшотам, когда задача на самом деле про чтение данных цепочки. -- Выбирать архивное восстановление, когда достаточно обычного или optimized RPC. -- Забывать про разделение hot/cold-данных для архивного режима. +- Выбирать archival-восстановление, когда достаточно standard или optimized RPC. +- Забывать про разделение hot/cold-хранилища для archival-данных. - Переходить к командам до выбора сети и цели узла. -## Полезные связанные страницы +## Связанные страницы -- [Обзор снапшотов](https://docs.fastnear.com/ru/snapshots) +- [Обзор snapshot](https://docs.fastnear.com/ru/snapshots) - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) - [RPC Reference](https://docs.fastnear.com/ru/rpc) diff --git a/static/ru/structured-data/site-graph.json b/static/ru/structured-data/site-graph.json index 8218951..13c79c9 100644 --- a/static/ru/structured-data/site-graph.json +++ b/static/ru/structured-data/site-graph.json @@ -6467,19 +6467,6 @@ "routeType": "docs", "url": "https://docs.fastnear.com/ru/tx/examples/berry-club" }, - { - "entityIds": { - "familyIds": [], - "mainEntityId": null, - "pageId": "https://docs.fastnear.com/ru/tx/examples/outlayer#page" - }, - "indexable": true, - "markdownMirrorUrl": "https://docs.fastnear.com/ru/tx/examples/outlayer.md", - "pageSchemaType": "TechArticle", - "route": "/ru/tx/examples/outlayer", - "routeType": "docs", - "url": "https://docs.fastnear.com/ru/tx/examples/outlayer" - }, { "entityIds": { "familyIds": [ diff --git a/static/ru/transfers/examples.md b/static/ru/transfers/examples.md index 0ecebee..3195bca 100644 --- a/static/ru/transfers/examples.md +++ b/static/ru/transfers/examples.md @@ -4,142 +4,44 @@ ### Отфильтровать и листать ленту переводов одного аккаунта - Ход - Сначала соберите саму ленту аккаунта, а `receipt` поднимайте только тогда, когда одна строка действительно требует истории исполнения. - - 01POST /v0/transfers даёт первую страницу отфильтрованной ленты одного аккаунта. - 02jq поднимает сами строки плюс resume_token, чтобы вы могли продолжать листать ту же ленту. - 03POST /v0/receipt — это уже необязательный следующий шаг, если одной строке нужна её история исполнения. - -**Сеть** - -- только mainnet - -**Ход** - -- Забираете первую страницу одной отфильтрованной ленты переводов для выбранного аккаунта. -- Используете сами параметры ленты как главный учебный материал: `account_id`, `direction`, `asset_id`, `min_amount`, `desc` и `limit`. -- Сначала смотрите на строки ответа и `resume_token`, а не прыгаете сразу в историю исполнения. -- Только если какая-то строка действительно требует дополнительной истории, переиспользуете её `receipt_id` в Transactions API. +`/v0/transfers` возвращает отфильтрованную ленту плюс `resume_token`, который вы переиспользуете *без изменения фильтров*, чтобы продолжать листать. В каждой строке уже есть `human_amount`, `usd_amount`, `transaction_id` и `receipt_id` — большинство audit-вопросов закрываются без второго запроса. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -ASSET_ID=native:near -MIN_AMOUNT=1000000000000000000000000 +ACCOUNT_ID=root.near -curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ +FEED="$(curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - min_amount: $min_amount, - desc: true, - limit: 10 - }')" \ - | tee /tmp/transfers-feed.json >/dev/null - -jq '{ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + direction: "receiver", + asset_id: "native:near", + min_amount: "1000000000000000000000000", + desc: true, + limit: 10 + }')")" + +echo "$FEED" | jq '{ resume_token, - transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - asset_id, - amount, - human_amount, - usd_amount, - other_account_id, - block_height - } - ] -}' /tmp/transfers-feed.json + transfers: [.transfers[] | {block_height, amount, human_amount, usd_amount, other_account_id, transaction_id, receipt_id}] +}' ``` -Необязательный следующий шаг: если одной строке всё-таки нужна её точка исполнения, поднимите её `receipt_id` и один раз перейдите в Transactions API. +Для зафиксированного аккаунта это возвращает недавние входящие native-NEAR переводы не меньше 1 NEAR — в примерных строках видны native-переводы с `escrow.ai.near` и уже посчитанным USD. Чтобы получить следующую страницу, отправьте то же тело с верхнеуровневым `resume_token: ""`; изменение любого другого фильтра делает токен недействительным. + +Когда одной строке нужна точка исполнения, возьмите её `receipt_id` и сразу обратитесь к `/v0/receipt`: ```bash -RECEIPT_ID="$(jq -r '.transfers[0].receipt_id' /tmp/transfers-feed.json)" +RECEIPT_ID="$(echo "$FEED" | jq -r '.transfers[0].receipt_id')" curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' + | jq '.receipt | {receipt_id, transaction_hash, receiver_id, predecessor_id, tx_block_height, is_success}' ``` -**Когда переходить дальше** - -Запрос переводов напрямую отвечает на первый вопрос: как сейчас выглядит отфильтрованная лента этого аккаунта и как её продолжать без потери места? Только после того как сама лента подскажет, какая строка действительно важна, имеет смысл переходить по `receipt_id` и забирать историю исполнения из `/tx`. - -## Частые задачи - -### Отфильтровать ленту переводов одного аккаунта - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом и самым узким стабильным набором фильтров для ленты: направление, актив, сумма и порядок. - -**Следующая страница при необходимости** - -- Уточните те же фильтры по активу или сумме, если в первой странице всё ещё слишком много лишних строк. - -**Остановитесь, когда** - -- Уже можно объяснить, как выглядит эта отфильтрованная лента и как листать её дальше. - -**Переходите дальше, когда** - -- Одна конкретная строка уже требует истории исполнения или следа по receipt. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Листать ленту переводов дальше и не потерять своё место - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. - -**Следующая страница при необходимости** - -- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. -- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. - -**Остановитесь, когда** - -- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. - -**Переходите дальше, когда** - -- Пользователь просит метаданные транзакции сверх самих переводов. -- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). - -### Перейти от истории переводов к полному расследованию транзакции - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. - -**Следующая страница при необходимости** - -- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. - -**Остановитесь, когда** - -- Уже определено правильное событие перевода и понятно, какой API открывать следующим. - -**Переходите дальше, когда** - -- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. +Это тот же переход, что описан в [Превратить один неказистый receipt ID из логов в человекочитаемую историю](https://docs.fastnear.com/ru/tx/examples#%D0%BF%D1%80%D0%B5%D0%B2%D1%80%D0%B0%D1%82%D0%B8%D1%82%D1%8C-%D0%BE%D0%B4%D0%B8%D0%BD-%D0%BD%D0%B5%D0%BA%D0%B0%D0%B7%D0%B8%D1%81%D1%82%D1%8B%D0%B9-receipt-id-%D0%B8%D0%B7-%D0%BB%D0%BE%D0%B3%D0%BE%D0%B2-%D0%B2-%D1%87%D0%B5%D0%BB%D0%BE%D0%B2%D0%B5%D0%BA%D0%BE%D1%87%D0%B8%D1%82%D0%B0%D0%B5%D0%BC%D1%83%D1%8E-%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D1%8E) — один запрос возвращает и квитанцию, и её родительскую транзакцию целиком. ## Частые ошибки @@ -147,7 +49,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - Считать историю переводов полной историей исполнения. - Переиспользовать `resume_token` с другими фильтрами. -## Полезные связанные страницы +## Связанные страницы - [Transfers API](https://docs.fastnear.com/ru/transfers) - [Transactions API](https://docs.fastnear.com/ru/tx) diff --git a/static/ru/transfers/examples/index.md b/static/ru/transfers/examples/index.md index 0ecebee..3195bca 100644 --- a/static/ru/transfers/examples/index.md +++ b/static/ru/transfers/examples/index.md @@ -4,142 +4,44 @@ ### Отфильтровать и листать ленту переводов одного аккаунта - Ход - Сначала соберите саму ленту аккаунта, а `receipt` поднимайте только тогда, когда одна строка действительно требует истории исполнения. - - 01POST /v0/transfers даёт первую страницу отфильтрованной ленты одного аккаунта. - 02jq поднимает сами строки плюс resume_token, чтобы вы могли продолжать листать ту же ленту. - 03POST /v0/receipt — это уже необязательный следующий шаг, если одной строке нужна её история исполнения. - -**Сеть** - -- только mainnet - -**Ход** - -- Забираете первую страницу одной отфильтрованной ленты переводов для выбранного аккаунта. -- Используете сами параметры ленты как главный учебный материал: `account_id`, `direction`, `asset_id`, `min_amount`, `desc` и `limit`. -- Сначала смотрите на строки ответа и `resume_token`, а не прыгаете сразу в историю исполнения. -- Только если какая-то строка действительно требует дополнительной истории, переиспользуете её `receipt_id` в Transactions API. +`/v0/transfers` возвращает отфильтрованную ленту плюс `resume_token`, который вы переиспользуете *без изменения фильтров*, чтобы продолжать листать. В каждой строке уже есть `human_amount`, `usd_amount`, `transaction_id` и `receipt_id` — большинство audit-вопросов закрываются без второго запроса. ```bash TRANSFERS_BASE_URL=https://transfers.main.fastnear.com TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=YOUR_ACCOUNT_ID -ASSET_ID=native:near -MIN_AMOUNT=1000000000000000000000000 +ACCOUNT_ID=root.near -curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ +FEED="$(curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg asset_id "$ASSET_ID" \ - --arg min_amount "$MIN_AMOUNT" '{ - account_id: $account_id, - direction: "receiver", - asset_id: $asset_id, - min_amount: $min_amount, - desc: true, - limit: 10 - }')" \ - | tee /tmp/transfers-feed.json >/dev/null - -jq '{ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + direction: "receiver", + asset_id: "native:near", + min_amount: "1000000000000000000000000", + desc: true, + limit: 10 + }')")" + +echo "$FEED" | jq '{ resume_token, - transfers: [ - .transfers[] - | { - transaction_id, - receipt_id, - asset_id, - amount, - human_amount, - usd_amount, - other_account_id, - block_height - } - ] -}' /tmp/transfers-feed.json + transfers: [.transfers[] | {block_height, amount, human_amount, usd_amount, other_account_id, transaction_id, receipt_id}] +}' ``` -Необязательный следующий шаг: если одной строке всё-таки нужна её точка исполнения, поднимите её `receipt_id` и один раз перейдите в Transactions API. +Для зафиксированного аккаунта это возвращает недавние входящие native-NEAR переводы не меньше 1 NEAR — в примерных строках видны native-переводы с `escrow.ai.near` и уже посчитанным USD. Чтобы получить следующую страницу, отправьте то же тело с верхнеуровневым `resume_token: ""`; изменение любого другого фильтра делает токен недействительным. + +Когда одной строке нужна точка исполнения, возьмите её `receipt_id` и сразу обратитесь к `/v0/receipt`: ```bash -RECEIPT_ID="$(jq -r '.transfers[0].receipt_id' /tmp/transfers-feed.json)" +RECEIPT_ID="$(echo "$FEED" | jq -r '.transfers[0].receipt_id')" curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - transaction_hash: .receipt.transaction_hash, - receiver_id: .receipt.receiver_id, - tx_block_height: .receipt.tx_block_height - }' + | jq '.receipt | {receipt_id, transaction_hash, receiver_id, predecessor_id, tx_block_height, is_success}' ``` -**Когда переходить дальше** - -Запрос переводов напрямую отвечает на первый вопрос: как сейчас выглядит отфильтрованная лента этого аккаунта и как её продолжать без потери места? Только после того как сама лента подскажет, какая строка действительно важна, имеет смысл переходить по `receipt_id` и забирать историю исполнения из `/tx`. - -## Частые задачи - -### Отфильтровать ленту переводов одного аккаунта - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) с аккаунтом и самым узким стабильным набором фильтров для ленты: направление, актив, сумма и порядок. - -**Следующая страница при необходимости** - -- Уточните те же фильтры по активу или сумме, если в первой странице всё ещё слишком много лишних строк. - -**Остановитесь, когда** - -- Уже можно объяснить, как выглядит эта отфильтрованная лента и как листать её дальше. - -**Переходите дальше, когда** - -- Одна конкретная строка уже требует истории исполнения или следа по receipt. Переходите к [Transactions API](https://docs.fastnear.com/ru/tx). - -### Листать ленту переводов дальше и не потерять своё место - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query) для первой страницы недавних событий, используя как можно более узкие и стабильные фильтры. - -**Следующая страница при необходимости** - -- Переиспользуйте ровно тот `resume_token`, который вернул сервис, чтобы получить следующую страницу с теми же фильтрами. -- Не меняйте фильтры во время пагинации, иначе это уже будет не та же самая лента. - -**Остановитесь, когда** - -- У вас уже достаточно страниц, чтобы ответить на запрос ленты, поддержки или комплаенса. - -**Переходите дальше, когда** - -- Пользователь просит метаданные транзакции сверх самих переводов. -- Нужны балансы или активы, а не только движение. Переходите к [FastNear API](https://docs.fastnear.com/ru/api). - -### Перейти от истории переводов к полному расследованию транзакции - -**Начните здесь** - -- [Запрос переводов](https://docs.fastnear.com/ru/transfers/query), чтобы выделить конкретные интересующие переводы. - -**Следующая страница при необходимости** - -- [История аккаунта в Transactions API](https://docs.fastnear.com/ru/tx/account), если нужна окружающая история исполнения для того же аккаунта. -- [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions), когда уже понятно, какую транзакцию смотреть дальше. - -**Остановитесь, когда** - -- Уже определено правильное событие перевода и понятно, какой API открывать следующим. - -**Переходите дальше, когда** - -- Пользователю прямо нужны receipt-детали или точное подтверждение через RPC. Сначала переходите к [Transactions API](https://docs.fastnear.com/ru/tx), затем к [RPC Reference](https://docs.fastnear.com/ru/rpc), если потребуется. +Это тот же переход, что описан в [Превратить один неказистый receipt ID из логов в человекочитаемую историю](https://docs.fastnear.com/ru/tx/examples#%D0%BF%D1%80%D0%B5%D0%B2%D1%80%D0%B0%D1%82%D0%B8%D1%82%D1%8C-%D0%BE%D0%B4%D0%B8%D0%BD-%D0%BD%D0%B5%D0%BA%D0%B0%D0%B7%D0%B8%D1%81%D1%82%D1%8B%D0%B9-receipt-id-%D0%B8%D0%B7-%D0%BB%D0%BE%D0%B3%D0%BE%D0%B2-%D0%B2-%D1%87%D0%B5%D0%BB%D0%BE%D0%B2%D0%B5%D0%BA%D0%BE%D1%87%D0%B8%D1%82%D0%B0%D0%B5%D0%BC%D1%83%D1%8E-%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D1%8E) — один запрос возвращает и квитанцию, и её родительскую транзакцию целиком. ## Частые ошибки @@ -147,7 +49,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - Считать историю переводов полной историей исполнения. - Переиспользовать `resume_token` с другими фильтрами. -## Полезные связанные страницы +## Связанные страницы - [Transfers API](https://docs.fastnear.com/ru/transfers) - [Transactions API](https://docs.fastnear.com/ru/tx) diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index 9d8cc69..323d76e 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -1,8 +1,10 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) -## Быстрый старт +## Начните здесь -Начните с одного tx hash и сначала получите самый короткий читаемый ответ. +### У меня один хеш транзакции. Что произошло? + +Вставьте хеш в `POST /v0/transactions` — один ответ обычно содержит всю историю. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -12,930 +14,211 @@ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height, + actions: (.transactions[0].transaction.actions | map(if type == "string" then . else keys[0] end)), + first_receipt_id: .transactions[0].execution_outcome.outcome.status.SuccessReceiptId, receipt_count: (.transactions[0].receipts | length) }' ``` -## С чего начать - -### У меня есть один хеш транзакции. Что вообще произошло? - - Ход - Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно. - - 01POST /v0/transactions даёт signer, receiver, типы действий, высоту блока и первую receipt-точку передачи. - 02RPC EXPERIMENTAL_tx_status нужен только для точной протокольной семантики успеха. - 03POST /v0/receipt имеет смысл только тогда, когда именно первая receipt становится новой опорной точкой. - -Зафиксированный пример: - -- хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- высота включающего блока: `194263342` -- ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` - -Короткий ответ: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. - -```mermaid -flowchart LR - H["Один tx hash
AdgNifPY..."] --> T["Получаем транзакцию"] - T --> A["Читаем signer, receiver, actions, block"] - A --> S["Короткая человеческая история"] - T -. "если потом понадобится" .-> R["Первая receipt
5GhZcpfK..."] -``` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Читаемая история транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с хеша транзакции и печатаем signer, receiver, включающий блок, список действий и handoff в первую receipt | Даёт самый быстрый читаемый ответ на вопрос «что вообще сделала эта транзакция?» | -| Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | -| Переход к receipt | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | - -#### Shell-сценарий: от хеша транзакции к человеческой истории - -**Ход** - -- Получаете транзакцию по хешу и печатаете её основные поля. -- Подтверждаете финальный статус только если нужны точные RPC-семантики. -- Сохраняете первую receipt только как необязательный следующий шаг. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp -SIGNER_ACCOUNT_ID=mike.near -``` - -1. Получите транзакцию и распечатайте базовую историю. - -```bash -FIRST_RECEIPT_ID="$( - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/basic-tx-story.json \ - | jq -r '.transactions[0].transaction_outcome.outcome.status.SuccessReceiptId' -)" - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/basic-tx-story.json - -# Ожидаемый список действий: ["Transfer"] -# Ожидаемая первая receipt: 5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq -``` - -2. Если нужны точные RPC-семантики статуса, подтвердите их через `EXPERIMENTAL_tx_status`. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' -``` - -3. Если следующий вопрос уже звучит как «что это была за первая receipt?», один раз перейдите по ней и остановитесь. - -```bash -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$FIRST_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash - }' -``` - -Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. - -**Когда переходить дальше** - -`POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. - -### Какая receipt выдала этот лог или event? - - Ход - Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога. - - 01POST /v0/transactions даёт полный индексированный список receipt для одного tx hash, включая receipt-логи. - 02jq сужает этот список до receipt, в логах которых встречается нужный вам фрагмент. - 03Как только совпадение осталось одно, сохраняйте его receipt_id, executor и имя метода как точный ответ. - -Для этого зафиксированного mainnet-примера используйте: - -- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` -- фрагмент лога: `Refund` -- ожидаемый matching `receipt_id`: `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` -- ожидаемый executor: `wrap.near` -- ожидаемый метод: `ft_resolve_transfer` - -В этой транзакции есть две logged receipt внутри одной истории: - -- ранний лог `Transfer ...` на receipt с `ft_transfer_call` -- более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` - -```mermaid -flowchart LR - T["Один tx hash
2KhhB1uD..."] --> L["Читаем все receipt-логи"] - L --> X["Ищем фрагмент:
Refund"] - X --> R["Точная receipt
9sLHQpaG..."] - R --> A["Ответ:
wrap.near / ft_resolve_transfer"] -``` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Атрибуция лога | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | -| Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | +Для зафиксированного хеша `mike.near` отправил один `Transfer` на `global-counter.mike.near` в блоке `194263342`, с передачей в receipt `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq`. Если `receipt_count > 1` или следующий вопрос касается поведения на уровне receipt, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие) или [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt). -#### Shell-сценарий атрибуции лога +### Какой receipt испустил этот лог или событие? -**Ход** - -- Один раз получаете транзакцию и сохраняете список её receipt. -- Фильтруете receipt по одному фрагменту лога. -- Останавливаетесь, как только у вас есть один точный `receipt_id`, один executor и одно имя метода. +Выведите список всех receipt транзакции с логами и флагом, содержат ли их логи ваш фрагмент. Совпадение доказывается, а не угадывается: у зафиксированной транзакции один receipt логирует `Transfer`, другой — `Refund`, и только сторона `Refund` переключается в `true`. ```bash TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL LOG_FRAGMENT=Refund -``` -1. Получите транзакцию и сохраните список receipt. - -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/log-attribution-transaction.json >/dev/null -``` - -2. Отфильтруйте список receipt до логов, которые содержат нужный вам фрагмент. - -```bash -jq --arg fragment "$LOG_FRAGMENT" '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id - }, - matching_receipts: [ - .transactions[0].receipts[] - | select(any(.execution_outcome.outcome.logs[]?; contains($fragment))) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - block_height: .execution_outcome.block_height, - logs: .execution_outcome.outcome.logs - } - ] -}' /tmp/log-attribution-transaction.json - -# На что смотреть: -# - фрагмент `Refund` совпадает ровно с одной receipt -# - это receipt 9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w -# - receipt исполнилась на wrap.near -# - имя метода — ft_resolve_transfer + | jq --arg fragment "$LOG_FRAGMENT" ' + [ + .transactions[0].receipts[] + | select((.execution_outcome.outcome.logs | length) > 0) + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0] + | if type == "string" then . else (.FunctionCall.method_name // keys[0]) end), + matches_fragment: any(.execution_outcome.outcome.logs[]?; contains($fragment)), + logs: .execution_outcome.outcome.logs + } + ]' ``` -3. Если хотите увидеть все logged receipt рядом, распечатайте только те receipt, где вообще были логи. - -```bash -jq '{ - logged_receipts: [ - .transactions[0].receipts[] - | select((.execution_outcome.outcome.logs | length) > 0) - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - logs: .execution_outcome.outcome.logs - } - ] -}' /tmp/log-attribution-transaction.json -``` - -Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. - -**Когда переходить дальше** - -Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. - -### Превратить один страшный receipt ID из логов в понятную человеческую историю - -Есть только `receipt_id` из логов или трассы? Сначала разрешите сам receipt, затем восстановите родительскую транзакцию. - -Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. - - Ход - Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой. - - 01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt. - 02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий. - 03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола». - -Зафиксированный receipt из логов: +Фрагмент `Refund` атрибутируется receipt `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` на `wrap.near`, метод `ft_resolve_transfer`. Логи receipt живут на receipts, а не на транзакции, поэтому одного прохода достаточно — более глубокая async-трассировка не нужна. -- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` -- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- высота блока транзакции: `194263342` -- высота блока исполнения receipt: `194263343` +### Превратить один неказистый receipt ID из логов в человекочитаемую историю -Короткий ответ: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. - -```mermaid -flowchart LR - L["Один страшный receipt ID
5GhZcpfK..."] --> R["Ищем receipt"] - R --> T["Восстанавливаем tx hash
AdgNifPY..."] - T --> S["Читаем действия транзакции"] - S --> H["Человеческая история:
mike.near отправил 5 NEAR в global-counter.mike.near"] -``` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и печатаем аккаунты, блок исполнения, флаг успеха и связанный хеш транзакции | Даёт самый короткий путь от сырого receipt ID к пониманию, что вообще за объект перед вами | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | -| Каноническое продолжение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | - -#### Shell-сценарий: от страшного receipt ID к человеческой истории +`POST /v0/receipt` возвращает запись receipt **и** его полную родительскую транзакцию в одном ответе, поэтому единственного запроса хватает на всю историю — дополнительный `/v0/transactions` не нужен. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' -``` - -1. Разрешите receipt и поймите, что за объект вы смотрите. - -```bash -TX_HASH="$( - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | tee /tmp/receipt-lookup.json \ - | jq -r '.receipt.transaction_hash' -)" - -jq '{ - receipt: { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - receipt_type: .receipt.receipt_type, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash, - tx_block_height: .receipt.tx_block_height - } -}' /tmp/receipt-lookup.json -``` +RECEIPT_ID=5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq -2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/receipt-parent-transaction.json >/dev/null - -jq '{ - transaction: { - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transfer_deposit_yocto: ( - .transactions[0].transaction.actions[0].Transfer.deposit // null - ) - }, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/receipt-parent-transaction.json -``` - -3. Сведите это к одному человеческому предложению. - -```bash -jq -r ' - def zeros($n): - reduce range(0; $n) as $i (""; . + "0"); - def yocto_to_near($yocto): - ($yocto | tostring) as $digits - | if ($digits | length) <= 24 then - ("0." + zeros(24 - ($digits | length)) + $digits) - else - ($digits[0:(($digits | length) - 24)] + "." + $digits[-24:]) - end - | sub("0+$"; "") - | sub("\\.$"; ""); - .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил \(yocto_to_near($tx.transaction.actions[0].Transfer.deposit)) NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." -' /tmp/receipt-parent-transaction.json -``` - -Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. - -В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. - -**Когда переходить дальше** - -`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. - -## Ошибки и async - -Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. - -### Доказать, что одно неудачное действие сорвало весь пакет - -Нужно проверить, закрепились ли ранние действия в неудачном батче? Используйте этот testnet-пример с `CreateAccount -> Transfer -> AddKey -> FunctionCall`. - -В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. - - Ход - Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов. - - 01POST /v0/transactions показывает упорядоченный пакет ровно в том виде, в каком его подписал signer. - 02RPC EXPERIMENTAL_tx_status показывает падающий FunctionCall и точную причину отказа на уровне протокола. - 03RPC view_account по предполагаемому новому аккаунту доказывает, закрепились ли вообще ранние create, fund и add-key действия. - -**Официальные ссылки** - -- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) -- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) - -Этот зафиксированный сбой был получен в **testnet 18 апреля 2026 года**: - -- хеш транзакции: `CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M` -- аккаунт signer: `temp.mike.testnet` -- целевой новый аккаунт: `rollback-mo4vmkig.temp.mike.testnet` -- высота включающего блока: `246365118` -- хеш включающего блока: `6f5zTKDqQRwrxMywzvxeRvYcCERJmAnatJaqUEtQYUNM` -- порядок действий: `CreateAccount -> Transfer -> AddKey -> FunctionCall` -- упавший метод: `definitely_missing_method` -- RPC-ошибка: `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet` - -```mermaid -flowchart LR - T["Одна подписанная транзакция"] --> A["CreateAccount"] - A --> B["Transfer 0.01 NEAR"] - B --> C["AddKey"] - C --> D["FunctionCall definitely_missing_method()"] - D --> E["Сбой: CodeDoesNotExist"] - E --> R["Весь пакет не закрепился"] - R --> N["Новый аккаунт не появился"] - R --> K["Новый ключ не закрепился"] - R --> F["У получателя нет профинансированного состояния"] + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block: .receipt.block_height, + tx_block: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }, + parent_transaction: { + signer_id: .transaction.transaction.signer_id, + receiver_id: .transaction.transaction.receiver_id, + action_types: (.transaction.transaction.actions | map(if type == "string" then . else keys[0] end)) + } + }' ``` -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Задуманный пакет | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированный хеш транзакции и печатаем упорядоченный список действий, получателя и метаданные включающего блока | Показывает, что именно signer пытался сделать, ещё до разговора о том, что закрепилось | -| Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | -| Доказательство по состоянию после исполнения | RPC [`query(view_account)`](https://docs.fastnear.com/ru/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | - -Индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. +Для зафиксированного receipt это возвращает `Action`-receipt от `mike.near` к `global-counter.mike.near`, который успешно выполнился в блоке `194263343`, через один блок после попадания родительской транзакции `AdgNifPY…`, — один `Transfer` (5 NEAR, в сыром `.transaction.transaction.actions` видимо как `5000000000000000000000000` yocto). Если интересным якорем становится родительская транзакция, хеш у вас уже есть — переиспользуйте его в [У меня один хеш транзакции. Что произошло?](#у-меня-один-хеш-транзакции-что-произошло). -#### Shell-сценарий неудачной транзакции с пакетом действий +## Сбои и async -**Ход** +### Доказать, что один провалившийся action откатил весь batch -- Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. -- Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. -- Через один RPC-запрос к состоянию после исполнения доказываете, что новый аккаунт так и не появился после finality. +Один batch отправил `CreateAccount → Transfer → AddKey → FunctionCall`, и финальный вызов попал в отсутствующий метод. Индексированная запись транзакции уже несёт упорядоченный batch *и* точный сбой на уровне receipt, поэтому одного запроса хватает, чтобы ответить «что пытались и что сломалось»; проверка через `view_account` затем доказывает, что предыдущие actions откатились. ```bash TX_BASE_URL=https://tx.test.fastnear.com RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M -SIGNER_ACCOUNT_ID=temp.mike.testnet NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet -``` - -1. Получите транзакцию и распечатайте задуманный пакет действий. -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/failed-batch-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height, - included_block_hash: .transactions[0].execution_outcome.block_hash - }, - batch: { - action_count: (.transactions[0].transaction.actions | length), - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - final_function_call_method_name: ( - .transactions[0].transaction.actions[3].FunctionCall.method_name - ) - }, - first_receipt_handoff: .transactions[0].transaction_outcome.outcome.status -}' /tmp/failed-batch-transaction.json - -# Ожидаемый порядок действий: -# 1. CreateAccount -# 2. Transfer -# 3. AddKey -# 4. FunctionCall + | jq '{ + action_types: (.transactions[0].transaction.actions | map(if type == "string" then . else keys[0] end)), + final_method: .transactions[0].transaction.actions[3].FunctionCall.method_name, + tx_handoff: .transactions[0].execution_outcome.outcome.status, + receipt_failure: ( + first( + .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) + | .execution_outcome.outcome.status.Failure.ActionError + ) + ) + }' ``` -2. Запросите RPC transaction status и посмотрите точную верхнеуровневую ошибку. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/failed-batch-rpc-status.json >/dev/null - -jq '{ - final_execution_status: .result.final_execution_status, - failed_action_index: .result.status.Failure.ActionError.index, - failure: .result.status.Failure.ActionError.kind.FunctionCallError.CompilationError.CodeDoesNotExist -}' /tmp/failed-batch-rpc-status.json - -# Ожидаемый failed_action_index: 3 -# Ожидаемый failure account_id: rollback-mo4vmkig.temp.mike.testnet -``` +Статус на уровне транзакции — `SuccessReceiptId`: транзакция успешно передала свои batched actions в receipt. Сбой лежит слоем ниже на этом receipt: `index: 3` (именно `FunctionCall`), вид `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet`. `SuccessReceiptId` в tx-outcome означает «handoff прошёл», а не «всё завершилось» — реальная ловушка, если смотреть только на статус уровня транзакции. -3. Запросите предполагаемый новый аккаунт после finality и докажите, что его всё ещё нет. +Теперь докажите откат предыдущих actions: спросите аккаунт, который batch *пытался* создать: ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } + jsonrpc: "2.0", id: "fastnear", method: "query", + params: {request_type: "view_account", account_id: $account_id, finality: "final"} }')" \ - | tee /tmp/failed-batch-view-account.json >/dev/null - -jq '{ - error: .error.cause.name, - message: .error.data, - requested_account_id: .error.cause.info.requested_account_id, - proof_block_height: .error.cause.info.block_height -}' /tmp/failed-batch-view-account.json - -# Ожидаемая ошибка: "UNKNOWN_ACCOUNT" + | jq '{error: .error.cause.name, requested_account_id: .error.cause.info.requested_account_id}' ``` -Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. - -**Когда переходить дальше** - -Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. - -### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? - -Нужно доказать, что позже упал отдельный cross-contract receipt, хотя первый вызов выглядел успешным? Используйте этот зафиксированный testnet-пример. - -Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. - - Ход - Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась. - - 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. - 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. - 03Как только эти два представления сходятся на одном и том же разрезе истории, остановитесь. Этот пример держится за сохранённые исторические свидетельства, а не за живой read состояния роутера. - -**Официальные ссылки** +`UNKNOWN_ACCOUNT` — это и есть доказательство. Если бы `CreateAccount` закрепился, `view_account` вернул бы результат; раз нет — предыдущие `Transfer` и `AddKey` из того же batched-receipt тоже не закрепились. -- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) -- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) +### Почему этот вызов контракта выглядел успешным, но потом receipt упал? -Этот зафиксированный асинхронный сбой был получен в **testnet 18 апреля 2026 года**: - -- хеш транзакции: `AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz` -- аккаунт signer: `temp.mike.testnet` -- первый контракт-получатель: `seq-dr.mike.testnet` -- аккаунт detached-цели: `asyncfail-in2hwikn.temp.mike.testnet` -- блок включения транзакции: `246368568` -- успешная первая receipt: `6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS` в блоке `246368569` -- более поздняя упавшая receipt: `2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es` в блоке `246368570` -- первый метод: `kickoff_append` -- более поздний упавший метод: `append` -- верхнеуровневый RPC `status`: `SuccessValue` - -```mermaid -flowchart LR - T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] - R --> D["Detached cross-contract receipt
append(...)"] - D --> F["Более поздний сбой
CodeDoesNotExist"] - T -. "внешняя транзакция всё равно завершается" .-> X["RPC top-level status
SuccessValue"] -``` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | -| Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | - -Успех receipt не транзитивен. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. - -#### Shell-сценарий более позднего сбоя receipt - -**Ход** - -- Читаете транзакцию и её таймлайн receipt из индексированного представления. -- Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. -- Останавливаетесь, как только эти два сохранённых представления сходятся на одном и том же разрезе истории. +Одна транзакция может закончиться тем, что внешний handoff рапортует `SuccessReceiptId`, а дочерний receipt при этом тихо падает — это и есть async-модель NEAR, и `/v0/transactions` выдаёт весь timeline за один запрос. ```bash -TX_BASE_URL=https://tx.test.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com -TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz -SIGNER_ACCOUNT_ID=temp.mike.testnet -FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS -FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es -``` - -1. Получите транзакцию и распечатайте таймлайн receipt по порядку блоков. +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/later-receipt-failure-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - tx_handoff: .transactions[0].transaction_outcome.outcome.status - }, - receipts: [ - .transactions[0].receipts[] - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - block_height: .execution_outcome.block_height, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system_transfer"), - status: .execution_outcome.outcome.status - } - ] -}' /tmp/later-receipt-failure-transaction.json - -# На что смотреть: -# - первая receipt контракта на seq-dr.mike.testnet успешно прошла в блоке 246368569 -# - более поздняя receipt append(...) упала в блоке 246368570 -``` - -2. Запросите RPC transaction status и сравните верхнеуровневую историю с более поздней упавшей receipt. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/later-receipt-failure-rpc.json >/dev/null - -jq \ - --arg first_receipt_id "$FIRST_RECEIPT_ID" \ - --arg failed_receipt_id "$FAILED_RECEIPT_ID" '{ - top_level_status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status, - first_contract_receipt: ( - .result.receipts_outcome[] - | select(.id == $first_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - logs: .outcome.logs, - status: .outcome.status - } - ), - later_failed_receipt: ( - .result.receipts_outcome[] - | select(.id == $failed_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - status: .outcome.status - } - ) - }' /tmp/later-receipt-failure-rpc.json - -# На что смотреть: -# - top_level_status всё ещё равен SuccessValue -# - первая receipt контракта залогировала dishonest_router:kickoff:late-failure -# - более поздняя receipt append(...) упала с CodeDoesNotExist -``` - -Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. - -**Когда переходить дальше** - -Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. - -### Дошёл ли callback вообще? - -Нужно проверить, вернулся ли callback в исходный контракт? Начните с этого mainnet-примера. - - Ход - Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а. - - 01POST /v0/transactions показывает downstream-вызов и более поздний receipt, который возвращается в исходный контракт. - 02jq сужает этот список receipt до одного downstream-вызова и одного callback-receipt. - 03RPC EXPERIMENTAL_tx_status нужен только как дополнительное подтверждение, если вам важны канонический результат callback-а и его логи. - -Зафиксированный mainnet-пример с callback замечен **19 апреля 2026 года**: - -- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` -- аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` -- исходный контракт: `wrap.near` -- downstream-receiver: `v2.ref-finance.near` -- верхнеуровневый метод: `ft_transfer_call` -- downstream-метод: `ft_on_transfer` -- callback-метод: `ft_resolve_transfer` -- блок транзакции: `194692298` -- блок downstream-receipt: `194692300` -- блок callback-receipt: `194692301` - -```mermaid -flowchart LR - T["Одна mainnet-транзакция
ft_transfer_call на wrap.near"] --> D["Downstream-receipt
v2.ref-finance.near.ft_on_transfer"] - D --> F["Receiver упал
E51: contract paused"] - F --> C["Callback-receipt обратно в wrap.near
ft_resolve_transfer"] - C --> R["Лог refund на wrap.near"] + | jq '{ + tx_handoff: .transactions[0].execution_outcome.outcome.status, + outer_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + descendant_failures: [ + .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) + | { + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + block_height: .execution_outcome.block_height, + failure: .execution_outcome.outcome.status.Failure + } + ], + receipt_timeline: [ + .transactions[0].receipts[] + | { + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + status_class: (.execution_outcome.outcome.status | keys[0]) + } + ] + }' ``` -Здесь хорошо видна одна полезная деталь NEAR: downstream-сбой не означает, что callback исчез. В этом случае `v2.ref-finance.near` уронил свой `ft_on_transfer`, но `wrap.near` всё равно позже получил `ft_resolve_transfer` и залогировал refund. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | -| Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | +Для зафиксированной транзакции mainnet `tx_handoff` — `SuccessReceiptId`: транзакция чисто запустила свой первый receipt. Если смотреть только сюда, можно назвать это победой. `descendant_failures` рассказывают вторую историю: `ft_on_transfer` на `v2.ref-finance.near` упал с `E51: contract paused` — DEX был на паузе во время этого свопа и не мог принять wrapped NEAR. А `receipt_timeline` показывает, как история разрешилась: callback `ft_resolve_transfer` на `wrap.near` всё равно отработал и вывел лог `Refund`, вернув wrapped NEAR отправителю. -#### Shell-сценарий проверки callback-а +Успех receipt не транзитивен. Протокол может чисто отдать handoff и при этом увидеть, как отцеплённая работа провалится позже. Если ваше приложение «выглядело успешным», но деньги всё равно вернулись, пройдите этот же timeline — разделение видно на индексированном ответе без отдельного RPC status-запроса. Чтобы отдельно проверить, что ваш callback отработал, см. [Отработал ли мой callback?](#отработал-ли-мой-callback). -**Ход** +### Отработал ли мой callback? -- Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. -- Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. -- Останавливаетесь сразу, как только можете сказать, вернулся ли callback и что он сделал. +Кросс-контрактные вызовы NEAR возвращаются через callback-receipt на исходном контракте. Отработал ли этот callback — это одна строка с `any(...)` против индексированного списка receipts; а полная история refund выпадает из того же ответа. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL -SENDER_ACCOUNT_ID=7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72 ORIGIN_CONTRACT_ID=wrap.near -DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near -``` - -1. Получите транзакцию и сохраните receipt-цепочку. +CALLBACK_METHOD=ft_resolve_transfer -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/callback-check-transaction.json >/dev/null -``` - -2. Сначала ответьте на самый короткий полезный вопрос: вернулся ли callback вообще? - -```bash -jq --arg origin "$ORIGIN_CONTRACT_ID" ' - [ - .transactions[0].receipts[] - | select( + | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ + top_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + callback_ran: any( + .transactions[0].receipts[]; .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - ] | length > 0 -' /tmp/callback-check-transaction.json -``` - -3. Если ответ `true`, распечатайте downstream-receipt вместе с callback-receipt. - -```bash - -CALLBACK_RECEIPT_ID="$( - jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | .receipt.receipt_id - ) - ' /tmp/callback-check-transaction.json -)" - -jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - tx_block_height: .transactions[0].execution_outcome.block_height - }, - downstream_receipt: ( - first( - .transactions[0].receipts[] - | select(.receipt.receiver_id == $downstream) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - status: .execution_outcome.outcome.status, - block_height: .execution_outcome.block_height - } - ) - ), - callback_receipt: ( - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: .receipt.receipt.Action.actions[0].FunctionCall.method_name, - logs: .execution_outcome.outcome.logs, - status: .execution_outcome.outcome.status, - block_height: .execution_outcome.block_height - } - ) - ), - callback_ran: ( - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | true - ) // false - ) -}' /tmp/callback-check-transaction.json - -# На что смотреть: -# - downstream-receipt выполнил ft_on_transfer на v2.ref-finance.near -# - более поздний callback-receipt выполнил ft_resolve_transfer на wrap.near -# - callback_ran равно true, даже несмотря на downstream-сбой -``` - -4. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg sender_account_id "$SENDER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $sender_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/callback-check-rpc.json >/dev/null - -jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ - top_level_status: .result.status, - callback_receipt: ( - first( - .result.receipts_outcome[] - | select(.id == $callback_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - logs: .outcome.logs, - status: .outcome.status - } - ) - ) -}' /tmp/callback-check-rpc.json - -# На что смотреть: -# - downstream ft_on_transfer receipt упал на v2.ref-finance.near -# - wrap.near всё равно позже получил ft_resolve_transfer -# - лог callback-а показывает refund обратно отправителю + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback + ), + receipt_chain: [ + .transactions[0].receipts[] + | { + receiver_id: .receipt.receiver_id, + method: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + block: .execution_outcome.block_height, + status: (.execution_outcome.outcome.status | keys[0]), + logs: .execution_outcome.outcome.logs + } + ] + }' ``` -**Когда переходить дальше** - -Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. +Для зафиксированной транзакции `ft_transfer_call` на `wrap.near` передаёт управление в `ft_on_transfer` на `v2.ref-finance.near`, который **падает**. Callback `ft_resolve_transfer` всё равно выполняется на `wrap.near` и логирует `Refund 7278020378457059679767103 from v2.ref-finance.near to …` обратно отправителю — поэтому `callback_ran: true`, несмотря на сбой дочернего receipt. Сбой ниже по цепочке не мешает исходному контракту увидеть свой callback; так async-обработка ошибок NEAR остаётся восстанавливаемой. Строки с `method: "system"` — это рантайм-возвраты газа, а не логика контракта. Чтобы привязать один из этих логов к породившему его receipt, см. [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). ## Частые ошибки -- Пытаться отправлять транзакцию через history API вместо сырого RPC. +- Пытаться отправить транзакцию через history-API вместо raw RPC. - Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. -- Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». +- Спускаться в raw RPC до того, как индексированная история ответила на читаемый вопрос «что произошло?». -## Полезные связанные страницы +## Связанные страницы - [Transactions API](https://docs.fastnear.com/ru/tx) - [RPC Reference](https://docs.fastnear.com/ru/rpc) - [FastNear API](https://docs.fastnear.com/ru/api) - [NEAR Data API](https://docs.fastnear.com/ru/neardata) - [Berry Club: живая доска и один путь исторической реконструкции](https://docs.fastnear.com/ru/tx/examples/berry-club) -- [OutLayer: связать одну транзакцию запроса с одним ответом воркера](https://docs.fastnear.com/ru/tx/examples/outlayer) - [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index 9d8cc69..323d76e 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -1,8 +1,10 @@ **Источник:** [https://docs.fastnear.com/ru/tx/examples](https://docs.fastnear.com/ru/tx/examples) -## Быстрый старт +## Начните здесь -Начните с одного tx hash и сначала получите самый короткий читаемый ответ. +### У меня один хеш транзакции. Что произошло? + +Вставьте хеш в `POST /v0/transactions` — один ответ обычно содержит всю историю. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -12,930 +14,211 @@ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height, + actions: (.transactions[0].transaction.actions | map(if type == "string" then . else keys[0] end)), + first_receipt_id: .transactions[0].execution_outcome.outcome.status.SuccessReceiptId, receipt_count: (.transactions[0].receipts | length) }' ``` -## С чего начать - -### У меня есть один хеш транзакции. Что вообще произошло? - - Ход - Начните с читаемой записи о транзакции и переходите в RPC или receipts только если первого ответа оказалось недостаточно. - - 01POST /v0/transactions даёт signer, receiver, типы действий, высоту блока и первую receipt-точку передачи. - 02RPC EXPERIMENTAL_tx_status нужен только для точной протокольной семантики успеха. - 03POST /v0/receipt имеет смысл только тогда, когда именно первая receipt становится новой опорной точкой. - -Зафиксированный пример: - -- хеш транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- высота включающего блока: `194263342` -- ID первой receipt: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` - -Короткий ответ: `mike.near` отправил одну транзакцию с действием `Transfer` в адрес `global-counter.mike.near`, эта транзакция попала в блок `194263342`, и сеть передала её в одну успешную receipt. - -```mermaid -flowchart LR - H["Один tx hash
AdgNifPY..."] --> T["Получаем транзакцию"] - T --> A["Читаем signer, receiver, actions, block"] - A --> S["Короткая человеческая история"] - T -. "если потом понадобится" .-> R["Первая receipt
5GhZcpfK..."] -``` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Читаемая история транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с хеша транзакции и печатаем signer, receiver, включающий блок, список действий и handoff в первую receipt | Даёт самый быстрый читаемый ответ на вопрос «что вообще сделала эта транзакция?» | -| Каноническое продолжение по статусу | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же хеш транзакции и signer только если нужны точные протокольные семантики статуса | Полезно, когда следующий вопрос уже звучит как «а по RPC это точно успех?» | -| Переход к receipt | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем ID первой receipt, если вопрос превращается в историю на уровне receipt | Даёт естественный мост к следующему расследованию, когда лучшим якорем становится уже не транзакция, а receipt | - -#### Shell-сценарий: от хеша транзакции к человеческой истории - -**Ход** - -- Получаете транзакцию по хешу и печатаете её основные поля. -- Подтверждаете финальный статус только если нужны точные RPC-семантики. -- Сохраняете первую receipt только как необязательный следующий шаг. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com -TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp -SIGNER_ACCOUNT_ID=mike.near -``` - -1. Получите транзакцию и распечатайте базовую историю. - -```bash -FIRST_RECEIPT_ID="$( - curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/basic-tx-story.json \ - | jq -r '.transactions[0].transaction_outcome.outcome.status.SuccessReceiptId' -)" - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - actions: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - first_receipt_id: .transactions[0].transaction_outcome.outcome.status.SuccessReceiptId, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/basic-tx-story.json - -# Ожидаемый список действий: ["Transfer"] -# Ожидаемая первая receipt: 5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq -``` - -2. Если нужны точные RPC-семантики статуса, подтвердите их через `EXPERIMENTAL_tx_status`. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | jq '{ - final_execution_status: .result.final_execution_status, - status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status - }' -``` - -3. Если следующий вопрос уже звучит как «что это была за первая receipt?», один раз перейдите по ней и остановитесь. - -```bash -curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$FIRST_RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | jq '{ - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash - }' -``` - -Последний шаг специально сделан необязательным. Если вам нужна была только история транзакции, уже первого шага достаточно. Двигайтесь дальше только когда сама receipt становится новым якорем. - -**Когда переходить дальше** - -`POST /v0/transactions` — это самый чистый старт, когда у вас на руках только tx hash и нужен один читаемый ответ. RPC нужен как продолжение для точных семантик статуса. `POST /v0/receipt` — это handoff на случай, когда следующий вопрос уже относится не ко всей транзакции, а к одной receipt внутри неё. - -### Какая receipt выдала этот лог или event? - - Ход - Один раз получите список receipt, отфильтруйте его по фрагменту лога и остановитесь, как только одна receipt окажется владельцем этого лога. - - 01POST /v0/transactions даёт полный индексированный список receipt для одного tx hash, включая receipt-логи. - 02jq сужает этот список до receipt, в логах которых встречается нужный вам фрагмент. - 03Как только совпадение осталось одно, сохраняйте его receipt_id, executor и имя метода как точный ответ. - -Для этого зафиксированного mainnet-примера используйте: - -- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` -- фрагмент лога: `Refund` -- ожидаемый matching `receipt_id`: `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` -- ожидаемый executor: `wrap.near` -- ожидаемый метод: `ft_resolve_transfer` - -В этой транзакции есть две logged receipt внутри одной истории: - -- ранний лог `Transfer ...` на receipt с `ft_transfer_call` -- более поздний лог `Refund ...` на receipt с `ft_resolve_transfer` - -```mermaid -flowchart LR - T["Один tx hash
2KhhB1uD..."] --> L["Читаем все receipt-логи"] - L --> X["Ищем фрагмент:
Refund"] - X --> R["Точная receipt
9sLHQpaG..."] - R --> A["Ответ:
wrap.near / ft_resolve_transfer"] -``` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Атрибуция лога | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Один раз получаем транзакцию и фильтруем её receipt по фрагменту лога вроде `Refund` | Даёт самый короткий путь от одной наблюдаемой строки лога к точной receipt, которая её выдала | -| Необязательный следующий pivot | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Переиспользуем найденный `receipt_id` только если сама receipt становится следующим якорем | Позволяет сохранить receipt для следующего расследования, не раздувая сам пример | +Для зафиксированного хеша `mike.near` отправил один `Transfer` на `global-counter.mike.near` в блоке `194263342`, с передачей в receipt `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq`. Если `receipt_count > 1` или следующий вопрос касается поведения на уровне receipt, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие) или [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt). -#### Shell-сценарий атрибуции лога +### Какой receipt испустил этот лог или событие? -**Ход** - -- Один раз получаете транзакцию и сохраняете список её receipt. -- Фильтруете receipt по одному фрагменту лога. -- Останавливаетесь, как только у вас есть один точный `receipt_id`, один executor и одно имя метода. +Выведите список всех receipt транзакции с логами и флагом, содержат ли их логи ваш фрагмент. Совпадение доказывается, а не угадывается: у зафиксированной транзакции один receipt логирует `Transfer`, другой — `Refund`, и только сторона `Refund` переключается в `true`. ```bash TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL LOG_FRAGMENT=Refund -``` -1. Получите транзакцию и сохраните список receipt. - -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/log-attribution-transaction.json >/dev/null -``` - -2. Отфильтруйте список receipt до логов, которые содержат нужный вам фрагмент. - -```bash -jq --arg fragment "$LOG_FRAGMENT" '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id - }, - matching_receipts: [ - .transactions[0].receipts[] - | select(any(.execution_outcome.outcome.logs[]?; contains($fragment))) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - block_height: .execution_outcome.block_height, - logs: .execution_outcome.outcome.logs - } - ] -}' /tmp/log-attribution-transaction.json - -# На что смотреть: -# - фрагмент `Refund` совпадает ровно с одной receipt -# - это receipt 9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w -# - receipt исполнилась на wrap.near -# - имя метода — ft_resolve_transfer + | jq --arg fragment "$LOG_FRAGMENT" ' + [ + .transactions[0].receipts[] + | select((.execution_outcome.outcome.logs | length) > 0) + | { + receipt_id: .receipt.receipt_id, + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0] + | if type == "string" then . else (.FunctionCall.method_name // keys[0]) end), + matches_fragment: any(.execution_outcome.outcome.logs[]?; contains($fragment)), + logs: .execution_outcome.outcome.logs + } + ]' ``` -3. Если хотите увидеть все logged receipt рядом, распечатайте только те receipt, где вообще были логи. - -```bash -jq '{ - logged_receipts: [ - .transactions[0].receipts[] - | select((.execution_outcome.outcome.logs | length) > 0) - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - logs: .execution_outcome.outcome.logs - } - ] -}' /tmp/log-attribution-transaction.json -``` - -Это последнее сравнение полезно тем, что оно показывает: атрибуция лога здесь не строится на догадке. В этой транзакции есть больше одной logged receipt, и фрагмент `Refund` принадлежит одной конкретной более поздней receipt, а не транзакции в целом. - -**Когда переходить дальше** - -Receipt-логи живут на уровне receipt, а не на каком-то абстрактном объекте верхнего уровня. `POST /v0/transactions` уже достаточно, чтобы привязать одну строку лога к одной точной receipt без ухода в более глубокую async-трассировку. - -### Превратить один страшный receipt ID из логов в понятную человеческую историю - -Есть только `receipt_id` из логов или трассы? Сначала разрешите сам receipt, затем восстановите родительскую транзакцию. - -Если у вас уже есть хеш транзакции, а не receipt ID, начните с более простого расследования прямо выше и опускайтесь сюда только тогда, когда сама receipt становится лучшим якорем. - - Ход - Сначала разрешите сам receipt, затем восстановите родительскую транзакцию и остановитесь, как только история стала читаемой. - - 01POST /v0/receipt показывает, к какой транзакции и к какому блоку исполнения относится receipt. - 02POST /v0/transactions превращает этот сырой receipt в контекст signer, receiver и действий. - 03RPC tx status — это уже необязательный следующий шаг, когда «человеческая история» превращается в «нужна точная семантика протокола». - -Зафиксированный receipt из логов: +Фрагмент `Refund` атрибутируется receipt `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` на `wrap.near`, метод `ft_resolve_transfer`. Логи receipt живут на receipts, а не на транзакции, поэтому одного прохода достаточно — более глубокая async-трассировка не нужна. -- receipt ID: `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq` -- хеш исходной транзакции: `AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp` -- signer: `mike.near` -- receiver: `global-counter.mike.near` -- высота блока транзакции: `194263342` -- высота блока исполнения receipt: `194263343` +### Превратить один неказистый receipt ID из логов в человекочитаемую историю -Короткий ответ: `mike.near` подписал обычную транзакцию `Transfer` в адрес `global-counter.mike.near`, сеть превратила её в одну квитанцию с действием, а эта квитанция успешно исполнилась в следующем блоке. - -```mermaid -flowchart LR - L["Один страшный receipt ID
5GhZcpfK..."] --> R["Ищем receipt"] - R --> T["Восстанавливаем tx hash
AdgNifPY..."] - T --> S["Читаем действия транзакции"] - S --> H["Человеческая история:
mike.near отправил 5 NEAR в global-counter.mike.near"] -``` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Якорь по квитанции | Transactions API [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt) | Сначала ищем ID квитанции и печатаем аккаунты, блок исполнения, флаг успеха и связанный хеш транзакции | Даёт самый короткий путь от сырого receipt ID к пониманию, что вообще за объект перед вами | -| История транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Переиспользуем полученный хеш транзакции и печатаем signer, receiver, упорядоченные действия и включающий блок | Превращает сырую квитанцию в читаемую историю того, что signer на самом деле отправил | -| Каноническое продолжение | RPC [`tx`](https://docs.fastnear.com/ru/rpc/transaction/tx-status) или [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Подтверждаем протокольные семантики только если индексированного ответа всё ещё недостаточно | Полезно, когда вопрос меняется с «расскажи мне историю» на «покажи точную RPC-семантику статуса» | - -#### Shell-сценарий: от страшного receipt ID к человеческой истории +`POST /v0/receipt` возвращает запись receipt **и** его полную родительскую транзакцию в одном ответе, поэтому единственного запроса хватает на всю историю — дополнительный `/v0/transactions` не нужен. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID='5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq' -``` - -1. Разрешите receipt и поймите, что за объект вы смотрите. - -```bash -TX_HASH="$( - curl -s "$TX_BASE_URL/v0/receipt" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ - | tee /tmp/receipt-lookup.json \ - | jq -r '.receipt.transaction_hash' -)" - -jq '{ - receipt: { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - receipt_type: .receipt.receipt_type, - is_success: .receipt.is_success, - receipt_block_height: .receipt.block_height, - transaction_hash: .receipt.transaction_hash, - tx_block_height: .receipt.tx_block_height - } -}' /tmp/receipt-lookup.json -``` +RECEIPT_ID=5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq -2. Переиспользуйте хеш транзакции и превратите квитанцию в читаемую историю транзакции. - -```bash -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "$TX_BASE_URL/v0/receipt" \ -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/receipt-parent-transaction.json >/dev/null - -jq '{ - transaction: { - transaction_hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - transfer_deposit_yocto: ( - .transactions[0].transaction.actions[0].Transfer.deposit // null - ) - }, - receipt_count: (.transactions[0].receipts | length) -}' /tmp/receipt-parent-transaction.json -``` - -3. Сведите это к одному человеческому предложению. - -```bash -jq -r ' - def zeros($n): - reduce range(0; $n) as $i (""; . + "0"); - def yocto_to_near($yocto): - ($yocto | tostring) as $digits - | if ($digits | length) <= 24 then - ("0." + zeros(24 - ($digits | length)) + $digits) - else - ($digits[0:(($digits | length) - 24)] + "." + $digits[-24:]) - end - | sub("0+$"; "") - | sub("\\.$"; ""); - .transactions[0] as $tx - | "Receipt \($tx.execution_outcome.outcome.receipt_ids[0]) относится к tx \($tx.transaction.hash): \($tx.transaction.signer_id) отправил \(yocto_to_near($tx.transaction.actions[0].Transfer.deposit)) NEAR в \($tx.transaction.receiver_id). Транзакция попала в блок \($tx.execution_outcome.block_height), а receipt успешно исполнился в блоке \($tx.receipts[0].execution_outcome.block_height)." -' /tmp/receipt-parent-transaction.json -``` - -Для другого receipt держитесь того же шаблона, но поменяйте финальное предложение так, чтобы оно соответствовало типам действий, которые вы только что напечатали. - -В этом и состоит ключевой приём: не нужно объяснять каждое поле квитанции. Нужно восстановить ровно столько контекста, чтобы сказать, что сделал signer, где исполнился receipt и был ли этот receipt главным событием или только шагом в более крупном каскаде. - -**Когда переходить дальше** - -`POST /v0/receipt` показывает, к чему привязан сырой receipt. `POST /v0/transactions` показывает, что signer на самом деле пытался сделать. Как только эти две части собраны вместе, чаще всего уже можно объяснить receipt одним предложением и только потом решать, нужны ли вообще контекст блока, история аккаунта или канонический RPC-статус. - -## Ошибки и async - -Здесь страница перестаёт быть просто поиском по объектам и начинает объяснять семантику исполнения в NEAR: атомарность пакета действий, более поздние async-сбои и то, дошёл ли callback обратно до исходного контракта. - -### Доказать, что одно неудачное действие сорвало весь пакет - -Нужно проверить, закрепились ли ранние действия в неудачном батче? Используйте этот testnet-пример с `CreateAccount -> Transfer -> AddKey -> FunctionCall`. - -В NEAR действия внутри одного пакета транзакции исполняются по порядку внутри первой квитанции с действиями. Если одно действие в этой квитанции падает, ранние действия из того же пакета тоже не закрепляются. Это отличается от более поздних асинхронных квитанций или promise-цепочек, где первая квитанция может пройти успешно, а уже следующая упасть отдельно. - - Ход - Докажите, что пакет пытался сделать, какое действие упало и закрепилось ли что-нибудь из ранних шагов. - - 01POST /v0/transactions показывает упорядоченный пакет ровно в том виде, в каком его подписал signer. - 02RPC EXPERIMENTAL_tx_status показывает падающий FunctionCall и точную причину отказа на уровне протокола. - 03RPC view_account по предполагаемому новому аккаунту доказывает, закрепились ли вообще ранние create, fund и add-key действия. - -**Официальные ссылки** - -- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) -- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) - -Этот зафиксированный сбой был получен в **testnet 18 апреля 2026 года**: - -- хеш транзакции: `CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M` -- аккаунт signer: `temp.mike.testnet` -- целевой новый аккаунт: `rollback-mo4vmkig.temp.mike.testnet` -- высота включающего блока: `246365118` -- хеш включающего блока: `6f5zTKDqQRwrxMywzvxeRvYcCERJmAnatJaqUEtQYUNM` -- порядок действий: `CreateAccount -> Transfer -> AddKey -> FunctionCall` -- упавший метод: `definitely_missing_method` -- RPC-ошибка: `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet` - -```mermaid -flowchart LR - T["Одна подписанная транзакция"] --> A["CreateAccount"] - A --> B["Transfer 0.01 NEAR"] - B --> C["AddKey"] - C --> D["FunctionCall definitely_missing_method()"] - D --> E["Сбой: CodeDoesNotExist"] - E --> R["Весь пакет не закрепился"] - R --> N["Новый аккаунт не появился"] - R --> K["Новый ключ не закрепился"] - R --> F["У получателя нет профинансированного состояния"] + --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ + | jq '{ + receipt: { + receipt_id: .receipt.receipt_id, + type: .receipt.receipt_type, + is_success: .receipt.is_success, + receipt_block: .receipt.block_height, + tx_block: .receipt.tx_block_height, + predecessor_id: .receipt.predecessor_id, + receiver_id: .receipt.receiver_id, + transaction_hash: .receipt.transaction_hash + }, + parent_transaction: { + signer_id: .transaction.transaction.signer_id, + receiver_id: .transaction.transaction.receiver_id, + action_types: (.transaction.transaction.actions | map(if type == "string" then . else keys[0] end)) + } + }' ``` -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Задуманный пакет | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированный хеш транзакции и печатаем упорядоченный список действий, получателя и метаданные включающего блока | Показывает, что именно signer пытался сделать, ещё до разговора о том, что закрепилось | -| Точное место сбоя | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Запрашиваем ту же транзакцию с `wait_until: "FINAL"` и смотрим `status.Failure` | Показывает, какое действие упало и почему весь пакет не закрепился на уровне протокола | -| Доказательство по состоянию после исполнения | RPC [`query(view_account)`](https://docs.fastnear.com/ru/rpc/account/view-account) | Запрашиваем предполагаемый новый аккаунт после finality | Если созданный аккаунт до сих пор не существует, значит ранние `CreateAccount`, `Transfer` и `AddKey` из того же пакета действий тоже не закрепились | - -Индексированная запись транзакции всё ещё показывает `transaction_outcome.outcome.status = SuccessReceiptId`, потому что подписанная транзакция успешно превратилась в свою первую квитанцию с действиями. Но доказательство того, что весь пакет не закрепился, приходит из верхнеуровневого RPC `status.Failure` для этой первой квитанции и из проверки состояния после исполнения, что целевой новый аккаунт так и не появился. +Для зафиксированного receipt это возвращает `Action`-receipt от `mike.near` к `global-counter.mike.near`, который успешно выполнился в блоке `194263343`, через один блок после попадания родительской транзакции `AdgNifPY…`, — один `Transfer` (5 NEAR, в сыром `.transaction.transaction.actions` видимо как `5000000000000000000000000` yocto). Если интересным якорем становится родительская транзакция, хеш у вас уже есть — переиспользуйте его в [У меня один хеш транзакции. Что произошло?](#у-меня-один-хеш-транзакции-что-произошло). -#### Shell-сценарий неудачной транзакции с пакетом действий +## Сбои и async -**Ход** +### Доказать, что один провалившийся action откатил весь batch -- Читаете индексированную запись транзакции, чтобы восстановить задуманный пакет действий. -- Через RPC transaction status доказываете, что финальный `FunctionCall` действительно упал и сорвал весь пакет. -- Через один RPC-запрос к состоянию после исполнения доказываете, что новый аккаунт так и не появился после finality. +Один batch отправил `CreateAccount → Transfer → AddKey → FunctionCall`, и финальный вызов попал в отсутствующий метод. Индексированная запись транзакции уже несёт упорядоченный batch *и* точный сбой на уровне receipt, поэтому одного запроса хватает, чтобы ответить «что пытались и что сломалось»; проверка через `view_account` затем доказывает, что предыдущие actions откатились. ```bash TX_BASE_URL=https://tx.test.fastnear.com RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M -SIGNER_ACCOUNT_ID=temp.mike.testnet NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet -``` - -1. Получите транзакцию и распечатайте задуманный пакет действий. -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/failed-batch-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height, - included_block_hash: .transactions[0].execution_outcome.block_hash - }, - batch: { - action_count: (.transactions[0].transaction.actions | length), - action_types: ( - .transactions[0].transaction.actions - | map(if type == "string" then . else keys[0] end) - ), - final_function_call_method_name: ( - .transactions[0].transaction.actions[3].FunctionCall.method_name - ) - }, - first_receipt_handoff: .transactions[0].transaction_outcome.outcome.status -}' /tmp/failed-batch-transaction.json - -# Ожидаемый порядок действий: -# 1. CreateAccount -# 2. Transfer -# 3. AddKey -# 4. FunctionCall + | jq '{ + action_types: (.transactions[0].transaction.actions | map(if type == "string" then . else keys[0] end)), + final_method: .transactions[0].transaction.actions[3].FunctionCall.method_name, + tx_handoff: .transactions[0].execution_outcome.outcome.status, + receipt_failure: ( + first( + .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) + | .execution_outcome.outcome.status.Failure.ActionError + ) + ) + }' ``` -2. Запросите RPC transaction status и посмотрите точную верхнеуровневую ошибку. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/failed-batch-rpc-status.json >/dev/null - -jq '{ - final_execution_status: .result.final_execution_status, - failed_action_index: .result.status.Failure.ActionError.index, - failure: .result.status.Failure.ActionError.kind.FunctionCallError.CompilationError.CodeDoesNotExist -}' /tmp/failed-batch-rpc-status.json - -# Ожидаемый failed_action_index: 3 -# Ожидаемый failure account_id: rollback-mo4vmkig.temp.mike.testnet -``` +Статус на уровне транзакции — `SuccessReceiptId`: транзакция успешно передала свои batched actions в receipt. Сбой лежит слоем ниже на этом receipt: `index: 3` (именно `FunctionCall`), вид `CodeDoesNotExist` на `rollback-mo4vmkig.temp.mike.testnet`. `SuccessReceiptId` в tx-outcome означает «handoff прошёл», а не «всё завершилось» — реальная ловушка, если смотреть только на статус уровня транзакции. -3. Запросите предполагаемый новый аккаунт после finality и докажите, что его всё ещё нет. +Теперь докажите откат предыдущих actions: спросите аккаунт, который batch *пытался* создать: ```bash curl -s "$RPC_URL" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "query", - params: { - request_type: "view_account", - account_id: $account_id, - finality: "final" - } + jsonrpc: "2.0", id: "fastnear", method: "query", + params: {request_type: "view_account", account_id: $account_id, finality: "final"} }')" \ - | tee /tmp/failed-batch-view-account.json >/dev/null - -jq '{ - error: .error.cause.name, - message: .error.data, - requested_account_id: .error.cause.info.requested_account_id, - proof_block_height: .error.cause.info.block_height -}' /tmp/failed-batch-view-account.json - -# Ожидаемая ошибка: "UNKNOWN_ACCOUNT" + | jq '{error: .error.cause.name, requested_account_id: .error.cause.info.requested_account_id}' ``` -Этой одной проверки состояния после исполнения здесь достаточно. Если бы `CreateAccount` закрепился, `view_account` вернул бы аккаунт. Раз аккаунт до сих пор не существует, значит ранние `Transfer` и `AddKey` из той же квитанции с действиями тоже не закрепились. - -**Когда переходить дальше** - -Для любой другой неудачной транзакции с несколькими действиями держитесь того же шаблона: сначала прочитайте, что транзакция пыталась сделать, через [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions), затем подтвердите точную верхнеуровневую ошибку через RPC transaction status, а потом проверьте состояние после исполнения у аккаунта, ключа, контракта или другого объекта, который должен был измениться, если бы ранние действия закрепились. - -### Почему вызов контракта выглядел успешным, а потом упал более поздний receipt? - -Нужно доказать, что позже упал отдельный cross-contract receipt, хотя первый вызов выглядел успешным? Используйте этот зафиксированный testnet-пример. - -Это противоположность примеру с неудачным пакетом действий выше. Там одно действие упало внутри первой action-receipt, поэтому не закрепилось ничего из этого пакета. Здесь первая receipt контракта действительно прошла успешно, и её изменение состояния действительно закрепилось. Сбой случился позже, в отдельной receipt. - - Ход - Сначала получаем человеческий таймлайн, а уже потом доказываем, где именно async-история разошлась. - - 01POST /v0/transactions даёт самый удобный первый проход: какая receipt успела пройти первой и какая упала позже. - 02RPC EXPERIMENTAL_tx_status доказывает важную NEAR-деталь: верхнеуровневый успех и более поздний сбой потомка могут одновременно быть правдой. - 03Как только эти два представления сходятся на одном и том же разрезе истории, остановитесь. Этот пример держится за сохранённые исторические свидетельства, а не за живой read состояния роутера. - -**Официальные ссылки** +`UNKNOWN_ACCOUNT` — это и есть доказательство. Если бы `CreateAccount` закрепился, `view_account` вернул бы результат; раз нет — предыдущие `Transfer` и `AddKey` из того же batched-receipt тоже не закрепились. -- [Основы транзакций](https://docs.fastnear.com/ru/transaction-flow/foundations) -- [Исполнение в рантайме](https://docs.fastnear.com/ru/transaction-flow/runtime-execution) +### Почему этот вызов контракта выглядел успешным, но потом receipt упал? -Этот зафиксированный асинхронный сбой был получен в **testnet 18 апреля 2026 года**: - -- хеш транзакции: `AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz` -- аккаунт signer: `temp.mike.testnet` -- первый контракт-получатель: `seq-dr.mike.testnet` -- аккаунт detached-цели: `asyncfail-in2hwikn.temp.mike.testnet` -- блок включения транзакции: `246368568` -- успешная первая receipt: `6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS` в блоке `246368569` -- более поздняя упавшая receipt: `2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es` в блоке `246368570` -- первый метод: `kickoff_append` -- более поздний упавший метод: `append` -- верхнеуровневый RPC `status`: `SuccessValue` - -```mermaid -flowchart LR - T["Подписанная tx
kickoff_append(...)"] --> R["Первая receipt на seq-dr.mike.testnet
SuccessValue + kickoff log"] - R --> D["Detached cross-contract receipt
append(...)"] - D --> F["Более поздний сбой
CodeDoesNotExist"] - T -. "внешняя транзакция всё равно завершается" .-> X["RPC top-level status
SuccessValue"] -``` - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Каркас транзакции | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Загружаем зафиксированную транзакцию и печатаем включающий блок плюс таймлайн receipt | Даёт самый короткий читаемый обзор: какая receipt отработала первой и какая упала позже | -| Точные семантики статуса | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Смотрим верхнеуровневый `status`, outcome первой receipt контракта и outcome более поздней упавшей receipt | Доказывает, что верхнеуровневый успех и более поздний сбой потомка могут сосуществовать в одной async-истории | - -Успех receipt не транзитивен. `seq-dr.mike.testnet` вернул успех на своей собственной receipt, потому что `kickoff_append(...)` только залогировал событие и detached-нул следующий hop. Detached-receipt `append(...)` была уже отдельной частью async-работы, поэтому её более поздний сбой не меняет того факта, что собственная receipt роутера уже успешно завершилась. - -#### Shell-сценарий более позднего сбоя receipt - -**Ход** - -- Читаете транзакцию и её таймлайн receipt из индексированного представления. -- Через RPC transaction status показываете, что верхнеуровневая история всё равно закончилась `SuccessValue`, хотя более поздняя receipt упала. -- Останавливаетесь, как только эти два сохранённых представления сходятся на одном и том же разрезе истории. +Одна транзакция может закончиться тем, что внешний handoff рапортует `SuccessReceiptId`, а дочерний receipt при этом тихо падает — это и есть async-модель NEAR, и `/v0/transactions` выдаёт весь timeline за один запрос. ```bash -TX_BASE_URL=https://tx.test.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com -TX_HASH=AUciGAq54XZtEuVXA9bSq4k6h13LmspoKtLegcWGRmQz -SIGNER_ACCOUNT_ID=temp.mike.testnet -FIRST_RECEIPT_ID=6XgWxB9QVkgGKJaLcjDphGHYTK5d1suNe2cH1WHRWnoS -FAILED_RECEIPT_ID=2A5JG8N1BxyR57WbrjqntTSf1UwR4RXR79MD2Zg3K2es -``` - -1. Получите транзакцию и распечатайте таймлайн receipt по порядку блоков. +TX_BASE_URL=https://tx.main.fastnear.com +TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/later-receipt-failure-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - tx_block_height: .transactions[0].execution_outcome.block_height, - tx_handoff: .transactions[0].transaction_outcome.outcome.status - }, - receipts: [ - .transactions[0].receipts[] - | { - receipt_id: .receipt.receipt_id, - receiver_id: .receipt.receiver_id, - block_height: .execution_outcome.block_height, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system_transfer"), - status: .execution_outcome.outcome.status - } - ] -}' /tmp/later-receipt-failure-transaction.json - -# На что смотреть: -# - первая receipt контракта на seq-dr.mike.testnet успешно прошла в блоке 246368569 -# - более поздняя receipt append(...) упала в блоке 246368570 -``` - -2. Запросите RPC transaction status и сравните верхнеуровневую историю с более поздней упавшей receipt. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg signer_account_id "$SIGNER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $signer_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/later-receipt-failure-rpc.json >/dev/null - -jq \ - --arg first_receipt_id "$FIRST_RECEIPT_ID" \ - --arg failed_receipt_id "$FAILED_RECEIPT_ID" '{ - top_level_status: .result.status, - transaction_handoff: .result.transaction_outcome.outcome.status, - first_contract_receipt: ( - .result.receipts_outcome[] - | select(.id == $first_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - logs: .outcome.logs, - status: .outcome.status - } - ), - later_failed_receipt: ( - .result.receipts_outcome[] - | select(.id == $failed_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - status: .outcome.status - } - ) - }' /tmp/later-receipt-failure-rpc.json - -# На что смотреть: -# - top_level_status всё ещё равен SuccessValue -# - первая receipt контракта залогировала dishonest_router:kickoff:late-failure -# - более поздняя receipt append(...) упала с CodeDoesNotExist -``` - -Остановитесь здесь. По состоянию на **18 апреля 2026 года** `seq-dr.mike.testnet` больше не резолвится в testnet, поэтому живое доказательство через текущее состояние роутера уже было бы неточным. Индексированный таймлайн receipt вместе с `EXPERIMENTAL_tx_status` и есть те сохранённые исторические свидетельства, которые здесь действительно важны. - -**Когда переходить дальше** - -Когда NEAR-приложение «как будто прошло успешно», а потом всё равно сломалось, надо спрашивать не только «какой был статус транзакции?», но и «какая receipt завершилась успешно, а какая позже упала?» Этот пример как раз даёт такой разрез: индексированный таймлайн receipt для общей формы, RPC status для точных семантик и никакого притворного живого read состояния роутера после того, как исторический контракт исчез. - -### Дошёл ли callback вообще? - -Нужно проверить, вернулся ли callback в исходный контракт? Начните с этого mainnet-примера. - - Ход - Сначала используйте индексированный список receipt, а к RPC переходите только если нужна каноническая семантика callback-а. - - 01POST /v0/transactions показывает downstream-вызов и более поздний receipt, который возвращается в исходный контракт. - 02jq сужает этот список receipt до одного downstream-вызова и одного callback-receipt. - 03RPC EXPERIMENTAL_tx_status нужен только как дополнительное подтверждение, если вам важны канонический результат callback-а и его логи. - -Зафиксированный mainnet-пример с callback замечен **19 апреля 2026 года**: - -- хеш транзакции: `2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL` -- аккаунт-отправитель: `7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72` -- исходный контракт: `wrap.near` -- downstream-receiver: `v2.ref-finance.near` -- верхнеуровневый метод: `ft_transfer_call` -- downstream-метод: `ft_on_transfer` -- callback-метод: `ft_resolve_transfer` -- блок транзакции: `194692298` -- блок downstream-receipt: `194692300` -- блок callback-receipt: `194692301` - -```mermaid -flowchart LR - T["Одна mainnet-транзакция
ft_transfer_call на wrap.near"] --> D["Downstream-receipt
v2.ref-finance.near.ft_on_transfer"] - D --> F["Receiver упал
E51: contract paused"] - F --> C["Callback-receipt обратно в wrap.near
ft_resolve_transfer"] - C --> R["Лог refund на wrap.near"] + | jq '{ + tx_handoff: .transactions[0].execution_outcome.outcome.status, + outer_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + descendant_failures: [ + .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) + | { + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + block_height: .execution_outcome.block_height, + failure: .execution_outcome.outcome.status.Failure + } + ], + receipt_timeline: [ + .transactions[0].receipts[] + | { + receiver_id: .receipt.receiver_id, + method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + status_class: (.execution_outcome.outcome.status | keys[0]) + } + ] + }' ``` -Здесь хорошо видна одна полезная деталь NEAR: downstream-сбой не означает, что callback исчез. В этом случае `v2.ref-finance.near` уронил свой `ft_on_transfer`, но `wrap.near` всё равно позже получил `ft_resolve_transfer` и залогировал refund. - -| Поверхность | Эндпоинт | Как используем | Зачем используем | -| --- | --- | --- | --- | -| Индексированная цепочка receipt | Transactions API [`POST /v0/transactions`](https://docs.fastnear.com/ru/tx/transactions) | Стартуем с tx hash и печатаем только downstream-receipt на receiver и более поздний callback-receipt на исходном контракте | Даёт самый быстрый читаемый ответ на вопрос «вернулся ли callback?» | -| Каноническое подтверждение receipt | RPC [`EXPERIMENTAL_tx_status`](https://docs.fastnear.com/ru/rpc/transaction/experimental-tx-status) | Переиспользуем тот же tx hash и sender только если нужен канонический статус callback-receipt и его логи | Полезно, когда индексированного ответа хватает для формы, но нужен протокольно-канонический proof | +Для зафиксированной транзакции mainnet `tx_handoff` — `SuccessReceiptId`: транзакция чисто запустила свой первый receipt. Если смотреть только сюда, можно назвать это победой. `descendant_failures` рассказывают вторую историю: `ft_on_transfer` на `v2.ref-finance.near` упал с `E51: contract paused` — DEX был на паузе во время этого свопа и не мог принять wrapped NEAR. А `receipt_timeline` показывает, как история разрешилась: callback `ft_resolve_transfer` на `wrap.near` всё равно отработал и вывел лог `Refund`, вернув wrapped NEAR отправителю. -#### Shell-сценарий проверки callback-а +Успех receipt не транзитивен. Протокол может чисто отдать handoff и при этом увидеть, как отцеплённая работа провалится позже. Если ваше приложение «выглядело успешным», но деньги всё равно вернулись, пройдите этот же timeline — разделение видно на индексированном ответе без отдельного RPC status-запроса. Чтобы отдельно проверить, что ваш callback отработал, см. [Отработал ли мой callback?](#отработал-ли-мой-callback). -**Ход** +### Отработал ли мой callback? -- Один раз получаете транзакцию и сужаете список receipt до downstream-вызова и callback-receipt. -- Переиспользуете ID callback-receipt только если ещё нужно каноническое RPC-подтверждение. -- Останавливаетесь сразу, как только можете сказать, вернулся ли callback и что он сделал. +Кросс-контрактные вызовы NEAR возвращаются через callback-receipt на исходном контракте. Отработал ли этот callback — это одна строка с `any(...)` против индексированного списка receipts; а полная история refund выпадает из того же ответа. ```bash TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL -SENDER_ACCOUNT_ID=7c5206b1b75b8787420b09d8697e08180cdf896c5fcf15f6afbf5f33fcc3cf72 ORIGIN_CONTRACT_ID=wrap.near -DOWNSTREAM_CONTRACT_ID=v2.ref-finance.near -``` - -1. Получите транзакцию и сохраните receipt-цепочку. +CALLBACK_METHOD=ft_resolve_transfer -```bash curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/callback-check-transaction.json >/dev/null -``` - -2. Сначала ответьте на самый короткий полезный вопрос: вернулся ли callback вообще? - -```bash -jq --arg origin "$ORIGIN_CONTRACT_ID" ' - [ - .transactions[0].receipts[] - | select( + | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ + top_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + callback_ran: any( + .transactions[0].receipts[]; .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - ] | length > 0 -' /tmp/callback-check-transaction.json -``` - -3. Если ответ `true`, распечатайте downstream-receipt вместе с callback-receipt. - -```bash - -CALLBACK_RECEIPT_ID="$( - jq -r --arg origin "$ORIGIN_CONTRACT_ID" ' - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | .receipt.receipt_id - ) - ' /tmp/callback-check-transaction.json -)" - -jq --arg origin "$ORIGIN_CONTRACT_ID" --arg downstream "$DOWNSTREAM_CONTRACT_ID" '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - method_name: .transactions[0].transaction.actions[0].FunctionCall.method_name, - tx_block_height: .transactions[0].execution_outcome.block_height - }, - downstream_receipt: ( - first( - .transactions[0].receipts[] - | select(.receipt.receiver_id == $downstream) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: ( - .receipt.receipt.Action.actions[0] - | if type == "string" then . - else (.FunctionCall.method_name // keys[0]) - end - ), - status: .execution_outcome.outcome.status, - block_height: .execution_outcome.block_height - } - ) - ), - callback_receipt: ( - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - method_name: .receipt.receipt.Action.actions[0].FunctionCall.method_name, - logs: .execution_outcome.outcome.logs, - status: .execution_outcome.outcome.status, - block_height: .execution_outcome.block_height - } - ) - ), - callback_ran: ( - first( - .transactions[0].receipts[] - | select( - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == "ft_resolve_transfer" - ) - | true - ) // false - ) -}' /tmp/callback-check-transaction.json - -# На что смотреть: -# - downstream-receipt выполнил ft_on_transfer на v2.ref-finance.near -# - более поздний callback-receipt выполнил ft_resolve_transfer на wrap.near -# - callback_ran равно true, даже несмотря на downstream-сбой -``` - -4. Если нужен канонический результат callback-а и лог refund, подтвердите тот же receipt через RPC. - -```bash -curl -s "$RPC_URL" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg tx_hash "$TX_HASH" \ - --arg sender_account_id "$SENDER_ACCOUNT_ID" '{ - jsonrpc: "2.0", - id: "fastnear", - method: "EXPERIMENTAL_tx_status", - params: { - tx_hash: $tx_hash, - sender_account_id: $sender_account_id, - wait_until: "FINAL" - } - }')" \ - | tee /tmp/callback-check-rpc.json >/dev/null - -jq --arg callback_receipt_id "$CALLBACK_RECEIPT_ID" '{ - top_level_status: .result.status, - callback_receipt: ( - first( - .result.receipts_outcome[] - | select(.id == $callback_receipt_id) - | { - receipt_id: .id, - executor_id: .outcome.executor_id, - logs: .outcome.logs, - status: .outcome.status - } - ) - ) -}' /tmp/callback-check-rpc.json - -# На что смотреть: -# - downstream ft_on_transfer receipt упал на v2.ref-finance.near -# - wrap.near всё равно позже получил ft_resolve_transfer -# - лог callback-а показывает refund обратно отправителю + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback + ), + receipt_chain: [ + .transactions[0].receipts[] + | { + receiver_id: .receipt.receiver_id, + method: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), + block: .execution_outcome.block_height, + status: (.execution_outcome.outcome.status | keys[0]), + logs: .execution_outcome.outcome.logs + } + ] + }' ``` -**Когда переходить дальше** - -Для вопросов про callback главный proof звучит не как «все ли receipt прошли успешно?», а как «получил ли исходный контракт свой callback-receipt обратно и что там случилось?» `POST /v0/transactions` даёт самый быстрый читаемый ответ. RPC нужен только как дополнительный слой подтверждения, если важны канонический результат callback-а и его логи. +Для зафиксированной транзакции `ft_transfer_call` на `wrap.near` передаёт управление в `ft_on_transfer` на `v2.ref-finance.near`, который **падает**. Callback `ft_resolve_transfer` всё равно выполняется на `wrap.near` и логирует `Refund 7278020378457059679767103 from v2.ref-finance.near to …` обратно отправителю — поэтому `callback_ran: true`, несмотря на сбой дочернего receipt. Сбой ниже по цепочке не мешает исходному контракту увидеть свой callback; так async-обработка ошибок NEAR остаётся восстанавливаемой. Строки с `method: "system"` — это рантайм-возвраты газа, а не логика контракта. Чтобы привязать один из этих логов к породившему его receipt, см. [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). ## Частые ошибки -- Пытаться отправлять транзакцию через history API вместо сырого RPC. +- Пытаться отправить транзакцию через history-API вместо raw RPC. - Использовать Transactions API, когда пользователю нужны только текущие балансы или активы. -- Слишком рано уходить в сырой RPC до того, как индексированная история уже ответила на читаемый вопрос «что произошло?». +- Спускаться в raw RPC до того, как индексированная история ответила на читаемый вопрос «что произошло?». -## Полезные связанные страницы +## Связанные страницы - [Transactions API](https://docs.fastnear.com/ru/tx) - [RPC Reference](https://docs.fastnear.com/ru/rpc) - [FastNear API](https://docs.fastnear.com/ru/api) - [NEAR Data API](https://docs.fastnear.com/ru/neardata) - [Berry Club: живая доска и один путь исторической реконструкции](https://docs.fastnear.com/ru/tx/examples/berry-club) -- [OutLayer: связать одну транзакцию запроса с одним ответом воркера](https://docs.fastnear.com/ru/tx/examples/outlayer) - [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/tx/examples/outlayer.md b/static/ru/tx/examples/outlayer.md deleted file mode 100644 index 74a8f53..0000000 --- a/static/ru/tx/examples/outlayer.md +++ /dev/null @@ -1,100 +0,0 @@ -**Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) - -{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как прочитать один caller-side запрос OutLayer вместе с более поздним worker-side resolution, а затем разобрать finish-receipts только если это действительно нужно. */} - -# OutLayer: что сделала эта пара request/resolution? - -Используйте этот walkthrough, когда вопрос звучит так: «что сделал этот запрос OutLayer, какое более позднее resolution к нему относится и нужно ли мне вообще смотреть на finish-receipts?» - -Оставайтесь в пределах публичных chain-данных: прочитайте request tx, прочитайте более поздний resolution tx и только потом решайте, нужны ли вам finish-receipts. - -## Компактный shell-сценарий - -Эта пара работала 18 апреля 2026 года: - -- caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` -- worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` - -### 1. Основной ответ: сразу раскройте request tx и resolution tx вместе - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -REQUEST_TX_HASH=AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4 -WORKER_TX_HASH=AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs - -curl -sS "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg request_tx_hash "$REQUEST_TX_HASH" --arg worker_tx_hash "$WORKER_TX_HASH" '{ - tx_hashes: [$request_tx_hash, $worker_tx_hash] - }')" \ - | tee /tmp/outlayer-pair.json >/dev/null - -jq '{ - transactions: [ - .transactions[] - | { - hash: .transaction.hash, - signer_id: .transaction.signer_id, - receiver_id: .transaction.receiver_id, - methods: [ - .transaction.actions[] - | .FunctionCall.method_name? - | select(. != null) - ], - first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) - } - ] -}' /tmp/outlayer-pair.json -``` - -Это и есть основной ответ: один request tx, один более поздний resolution tx и читаемые signer-, receiver-, method- и log-доказательства для обеих транзакций. - -### Необязательное продолжение: Что сделал finish-путь? - -```bash -jq --arg worker_tx_hash "$WORKER_TX_HASH" ' - .transactions[] - | select(.transaction.hash == $worker_tx_hash) - | { - worker_tx_hash: .transaction.hash, - receipts: [ - .receipts[] - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - actions: [.receipt.receipt.Action.actions[] | keys[0]], - logs: .execution_outcome.outcome.logs - } - ] - } -' /tmp/outlayer-pair.json -``` - -Смотрите на самое маленькое читаемое доказательство finish-пути: - -- `FunctionCall`-receipts, которые продолжают finish-путь -- логи списания вроде `[[yNEAR charged: "..."]]` -- последующие `Transfer`-receipts, которые похожи на refund или settlement-движение - -Именно здесь receipts становятся нужной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции здесь относятся друг к другу?» - -### Необязательный шаг: сначала найдите два хеша - -```bash -curl -sS "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data '{"account_id":"outlayer.near","desc":true,"limit":10}' \ - | jq '{ - txs_count, - recent_hashes: [.account_txs[:10][] | .transaction_hash] - }' -``` - -Используйте это только если вы ещё не знаете пару хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` превращает их в читаемый ответ. - -## Полезные связанные страницы - -- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) -- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) -- [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) diff --git a/static/ru/tx/examples/outlayer/index.md b/static/ru/tx/examples/outlayer/index.md deleted file mode 100644 index 74a8f53..0000000 --- a/static/ru/tx/examples/outlayer/index.md +++ /dev/null @@ -1,100 +0,0 @@ -**Источник:** [https://docs.fastnear.com/ru/tx/examples/outlayer](https://docs.fastnear.com/ru/tx/examples/outlayer) - -{/* FASTNEAR_AI_DISCOVERY: Этот walkthrough остаётся в пределах наблюдаемых транзакций и receipts. Он показывает, как прочитать один caller-side запрос OutLayer вместе с более поздним worker-side resolution, а затем разобрать finish-receipts только если это действительно нужно. */} - -# OutLayer: что сделала эта пара request/resolution? - -Используйте этот walkthrough, когда вопрос звучит так: «что сделал этот запрос OutLayer, какое более позднее resolution к нему относится и нужно ли мне вообще смотреть на finish-receipts?» - -Оставайтесь в пределах публичных chain-данных: прочитайте request tx, прочитайте более поздний resolution tx и только потом решайте, нужны ли вам finish-receipts. - -## Компактный shell-сценарий - -Эта пара работала 18 апреля 2026 года: - -- caller-side запрос: `AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4` -- worker-side разрешение: `AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs` - -### 1. Основной ответ: сразу раскройте request tx и resolution tx вместе - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -REQUEST_TX_HASH=AJgn2DB7BaD3487wXii8rGM648eqvkFDqJ1zXCxfuRk4 -WORKER_TX_HASH=AVbxfPyN5P1ryFh7HPstWbjiSantPYWfMpiwKcJ7hXTs - -curl -sS "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg request_tx_hash "$REQUEST_TX_HASH" --arg worker_tx_hash "$WORKER_TX_HASH" '{ - tx_hashes: [$request_tx_hash, $worker_tx_hash] - }')" \ - | tee /tmp/outlayer-pair.json >/dev/null - -jq '{ - transactions: [ - .transactions[] - | { - hash: .transaction.hash, - signer_id: .transaction.signer_id, - receiver_id: .transaction.receiver_id, - methods: [ - .transaction.actions[] - | .FunctionCall.method_name? - | select(. != null) - ], - first_logs: (.receipts[0].execution_outcome.outcome.logs[:2]) - } - ] -}' /tmp/outlayer-pair.json -``` - -Это и есть основной ответ: один request tx, один более поздний resolution tx и читаемые signer-, receiver-, method- и log-доказательства для обеих транзакций. - -### Необязательное продолжение: Что сделал finish-путь? - -```bash -jq --arg worker_tx_hash "$WORKER_TX_HASH" ' - .transactions[] - | select(.transaction.hash == $worker_tx_hash) - | { - worker_tx_hash: .transaction.hash, - receipts: [ - .receipts[] - | { - receipt_id: .receipt.receipt_id, - predecessor_id: .receipt.predecessor_id, - receiver_id: .receipt.receiver_id, - actions: [.receipt.receipt.Action.actions[] | keys[0]], - logs: .execution_outcome.outcome.logs - } - ] - } -' /tmp/outlayer-pair.json -``` - -Смотрите на самое маленькое читаемое доказательство finish-пути: - -- `FunctionCall`-receipts, которые продолжают finish-путь -- логи списания вроде `[[yNEAR charged: "..."]]` -- последующие `Transfer`-receipts, которые похожи на refund или settlement-движение - -Именно здесь receipts становятся нужной абстракцией. Не начинайте с них, если вопрос пока ещё звучит как «какие две транзакции здесь относятся друг к другу?» - -### Необязательный шаг: сначала найдите два хеша - -```bash -curl -sS "$TX_BASE_URL/v0/account" \ - -H 'content-type: application/json' \ - --data '{"account_id":"outlayer.near","desc":true,"limit":10}' \ - | jq '{ - txs_count, - recent_hashes: [.account_txs[:10][] | .transaction_hash] - }' -``` - -Используйте это только если вы ещё не знаете пару хешей. В этом примере `/v0/account` даёт кандидатов, а `/v0/transactions` превращает их в читаемый ответ. - -## Полезные связанные страницы - -- [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) -- [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) -- [Transactions API: receipt по ID](https://docs.fastnear.com/ru/tx/receipt) diff --git a/static/ru/tx/socialdb-proofs.md b/static/ru/tx/socialdb-proofs.md index e67f8ee..c016569 100644 --- a/static/ru/tx/socialdb-proofs.md +++ b/static/ru/tx/socialdb-proofs.md @@ -2,15 +2,15 @@ # Расширенный поиск записи SocialDB -Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому поиску записи. +Используйте эту страницу только тогда, когда отправная точка — уже читаемое значение SocialDB из `api.near.social`, а следующий вопрос относится к историческому поиску записи. -Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" +Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос звучит как «какая запись сделала это читаемое значение SocialDB истинным?». ## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` -Используйте этот сценарий, когда читаемый факт уже звучит как "текущее `profile.name` равно `Mike Purvis`", а остаётся только вопрос, какая запись сделала это поле истинным. +Используйте этот сценарий, когда читаемый факт уже звучит как «текущее `profile.name` равно `Mike Purvis`», а остаётся вопрос, какая запись сделала это поле истинным. -Здесь достаточно сохранить один важный нюанс SocialDB: для исторического provenance правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. +Это единственный нюанс SocialDB, который стоит запомнить: для исторического доказательства правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. Для этого живого якоря: @@ -22,70 +22,57 @@ ### Shell-сценарий -1. Прочитайте поле из NEAR Social и сохраните block записи на уровне поля. +1. Прочитайте поле из NEAR Social и сохраните блок записи на уровне поля. ```bash SOCIAL_API_BASE_URL=https://api.near.social TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com ACCOUNT_ID=mike.near PROFILE_FIELD=profile/name -PROFILE_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-profile-name.json \ - | jq -r --arg account_id "$ACCOUNT_ID" \ - '.[ $account_id ].profile.name[":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ +PROFILE="$(curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')")" + +echo "$PROFILE" | jq --arg account_id "$ACCOUNT_ID" '{ current_name: .[$account_id].profile.name[""], field_block_height: .[$account_id].profile.name[":block"], parent_profile_block_height: .[$account_id].profile[":block"] -}' /tmp/mike-profile-name.json +}' + +PROFILE_BLOCK_HEIGHT="$(echo "$PROFILE" | jq -r --arg account_id "$ACCOUNT_ID" '.[$account_id].profile.name[":block"]')" ``` -2. Переиспользуйте этот block уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. +2. Переиспользуйте этот блок уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. ```bash -PROFILE_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-profile-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ +BLOCK_RECEIPTS="$(curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')")" + +echo "$BLOCK_RECEIPTS" | jq --arg account_id "$ACCOUNT_ID" '{ profile_receipt: ( first( .block_receipts[] | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | { - receipt_id, - transaction_hash, - block_height, - tx_block_height - } + | {receipt_id, transaction_hash, block_height, tx_block_height} ) ) -}' /tmp/mike-profile-block.json +}' + +PROFILE_TX_HASH="$(echo "$BLOCK_RECEIPTS" | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )')" ``` 3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. @@ -94,34 +81,27 @@ jq --arg account_id "$ACCOUNT_ID" '{ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-profile-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), - description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), - tags: ( - .args - | @base64d - | fromjson - | .data["mike.near"].profile.tags - | keys - ) - } - ) -}' /tmp/mike-profile-transaction.json + | jq --arg account_id "$ACCOUNT_ID" '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | (.args | @base64d | fromjson | .data[$account_id].profile) as $profile + | { + method_name, + profile_name: $profile.name, + description: $profile.description, + tags: ($profile.tags | keys) + } + ) + }' ``` -Это и есть весь паттерн lookup-а: читаемое значение, block уровня поля, мост через receipt и payload транзакции. +Это и есть весь паттерн lookup: читаемое значение, блок уровня поля, мост через receipt и payload транзакции. Тот же мост работает и для других читаемых значений SocialDB: diff --git a/static/ru/tx/socialdb-proofs/index.md b/static/ru/tx/socialdb-proofs/index.md index e67f8ee..c016569 100644 --- a/static/ru/tx/socialdb-proofs/index.md +++ b/static/ru/tx/socialdb-proofs/index.md @@ -2,15 +2,15 @@ # Расширенный поиск записи SocialDB -Используйте эту страницу только тогда, когда отправная точка уже является читаемым значением из `api.near.social`, а следующий вопрос относится к историческому поиску записи. +Используйте эту страницу только тогда, когда отправная точка — уже читаемое значение SocialDB из `api.near.social`, а следующий вопрос относится к историческому поиску записи. -Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос уже звучит как "какая запись сделала это читаемое значение SocialDB истинным?" +Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос звучит как «какая запись сделала это читаемое значение SocialDB истинным?». ## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` -Используйте этот сценарий, когда читаемый факт уже звучит как "текущее `profile.name` равно `Mike Purvis`", а остаётся только вопрос, какая запись сделала это поле истинным. +Используйте этот сценарий, когда читаемый факт уже звучит как «текущее `profile.name` равно `Mike Purvis`», а остаётся вопрос, какая запись сделала это поле истинным. -Здесь достаточно сохранить один важный нюанс SocialDB: для исторического provenance правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. +Это единственный нюанс SocialDB, который стоит запомнить: для исторического доказательства правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. Для этого живого якоря: @@ -22,70 +22,57 @@ ### Shell-сценарий -1. Прочитайте поле из NEAR Social и сохраните block записи на уровне поля. +1. Прочитайте поле из NEAR Social и сохраните блок записи на уровне поля. ```bash SOCIAL_API_BASE_URL=https://api.near.social TX_BASE_URL=https://tx.main.fastnear.com -RPC_URL=https://rpc.mainnet.fastnear.com ACCOUNT_ID=mike.near PROFILE_FIELD=profile/name -PROFILE_BLOCK_HEIGHT="$( - curl -s "$SOCIAL_API_BASE_URL/get" \ - -H 'content-type: application/json' \ - --data "$(jq -nc \ - --arg account_id "$ACCOUNT_ID" \ - --arg profile_field "$PROFILE_FIELD" '{ - keys: [($account_id + "/" + $profile_field)], - options: {with_block_height: true} - }')" \ - | tee /tmp/mike-profile-name.json \ - | jq -r --arg account_id "$ACCOUNT_ID" \ - '.[ $account_id ].profile.name[":block"]' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ +PROFILE="$(curl -s "$SOCIAL_API_BASE_URL/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')")" + +echo "$PROFILE" | jq --arg account_id "$ACCOUNT_ID" '{ current_name: .[$account_id].profile.name[""], field_block_height: .[$account_id].profile.name[":block"], parent_profile_block_height: .[$account_id].profile[":block"] -}' /tmp/mike-profile-name.json +}' + +PROFILE_BLOCK_HEIGHT="$(echo "$PROFILE" | jq -r --arg account_id "$ACCOUNT_ID" '.[$account_id].profile.name[":block"]')" ``` -2. Переиспользуйте этот block уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. +2. Переиспользуйте этот блок уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. ```bash -PROFILE_TX_HASH="$( - curl -s "$TX_BASE_URL/v0/block" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ - block_id: $block_id, - with_transactions: false, - with_receipts: true - }')" \ - | tee /tmp/mike-profile-block.json \ - | jq -r --arg account_id "$ACCOUNT_ID" ' - first( - .block_receipts[] - | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | .transaction_hash - )' -)" - -jq --arg account_id "$ACCOUNT_ID" '{ +BLOCK_RECEIPTS="$(curl -s "$TX_BASE_URL/v0/block" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')")" + +echo "$BLOCK_RECEIPTS" | jq --arg account_id "$ACCOUNT_ID" '{ profile_receipt: ( first( .block_receipts[] | select(.predecessor_id == $account_id and .receiver_id == "social.near") - | { - receipt_id, - transaction_hash, - block_height, - tx_block_height - } + | {receipt_id, transaction_hash, block_height, tx_block_height} ) ) -}' /tmp/mike-profile-block.json +}' + +PROFILE_TX_HASH="$(echo "$BLOCK_RECEIPTS" | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )')" ``` 3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. @@ -94,34 +81,27 @@ jq --arg account_id "$ACCOUNT_ID" '{ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | tee /tmp/mike-profile-transaction.json >/dev/null - -jq '{ - transaction: { - hash: .transactions[0].transaction.hash, - signer_id: .transactions[0].transaction.signer_id, - receiver_id: .transactions[0].transaction.receiver_id, - included_block_height: .transactions[0].execution_outcome.block_height - }, - write_proof: ( - .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall - | { - method_name, - profile_name: (.args | @base64d | fromjson | .data["mike.near"].profile.name), - description: (.args | @base64d | fromjson | .data["mike.near"].profile.description), - tags: ( - .args - | @base64d - | fromjson - | .data["mike.near"].profile.tags - | keys - ) - } - ) -}' /tmp/mike-profile-transaction.json + | jq --arg account_id "$ACCOUNT_ID" '{ + transaction: { + hash: .transactions[0].transaction.hash, + signer_id: .transactions[0].transaction.signer_id, + receiver_id: .transactions[0].transaction.receiver_id, + included_block_height: .transactions[0].execution_outcome.block_height + }, + write_proof: ( + .transactions[0].receipts[0].receipt.receipt.Action.actions[0].FunctionCall + | (.args | @base64d | fromjson | .data[$account_id].profile) as $profile + | { + method_name, + profile_name: $profile.name, + description: $profile.description, + tags: ($profile.tags | keys) + } + ) + }' ``` -Это и есть весь паттерн lookup-а: читаемое значение, block уровня поля, мост через receipt и payload транзакции. +Это и есть весь паттерн lookup: читаемое значение, блок уровня поля, мост через receipt и payload транзакции. Тот же мост работает и для других читаемых значений SocialDB: From 940c802936d3edd6e065dbc169f4c9379ed5af9f Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Mon, 20 Apr 2026 08:09:48 -0700 Subject: [PATCH 28/35] docs: add outlayer tee request-worker pairing scenario Append one scenario to tx/examples.md (Failure and async section) that hydrates an outlayer.near request + worker resolution pair in a single /v0/transactions batch and surfaces the request_id + data_id correlation along with the TEE resources_used fingerprint (instructions, time_ms, encrypted bytes). Pinned pair: retrorn.near's request_execution tx BZDQAxEd... (block 194832281) + worker.outlayer.near's submit_execution_output_and_resolve tx 3NYD4Mkn... (block 194832292), both calling the zavodil.near/near-email project for a delete_email action. Narrative deliberately avoids claiming yield/resume receipt kinds (they render as Action in the indexed view) or DAO attestation posting (no receipt chain to any DAO contract was observed; product docs' DAO governs only the CKD master key). Archival note is one line: the indexed /v0/transactions surface serves historical pairs regardless. Mirrored in i18n/ru/.../tx/examples.md using the three-tier model. yarn audit:ru-terminology + yarn audit:i18n:all clean; yarn build passes both locales. --- docs/tx/examples.md | 40 +++++++++++++++++++ .../current/tx/examples.md | 40 +++++++++++++++++++ static/ru/llms-full.txt | 40 +++++++++++++++++++ static/ru/tx/examples.md | 40 +++++++++++++++++++ static/ru/tx/examples/index.md | 40 +++++++++++++++++++ 5 files changed, 200 insertions(+) diff --git a/docs/tx/examples.md b/docs/tx/examples.md index d634c90..4956972 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -214,6 +214,46 @@ curl -s "$TX_BASE_URL/v0/transactions" \ For the pinned tx, `ft_transfer_call` on `wrap.near` hands off to `v2.ref-finance.near`'s `ft_on_transfer`, which **fails**. The callback `ft_resolve_transfer` still runs on `wrap.near` and logs `Refund 7278020378457059679767103 from v2.ref-finance.near to …` back to the sender — so `callback_ran: true` even though the downstream receipt failed. A downstream failure never prevents the origin contract from seeing its callback; that's how NEAR async error handling stays recoverable. The `method: "system"` rows are runtime gas refunds, not contract logic. To attribute one of the logs above to its emitting receipt, see [Which receipt emitted this log or event?](#which-receipt-emitted-this-log-or-event). +### Pair one OutLayer request with its TEE worker resolution + +[OutLayer](https://outlayer.fastnear.com) splits one logical call across two transactions: a user calls `request_execution` on `outlayer.near`, an Intel TDX worker runs the requested WASM off-chain, and `worker.outlayer.near` later calls `submit_execution_output_and_resolve` with the result. One `/v0/transactions` batch with both hashes returns the full pairing — method, correlation IDs, and TEE fingerprint — without a separate worker query. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz +WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg a "$REQUEST_TX" --arg b "$WORKER_TX" '{tx_hashes: [$a, $b]}')" \ + | jq '[ + .transactions[] + | { + hash: .transaction.hash, + signer: .transaction.signer_id, + method: .transaction.actions[0].FunctionCall.method_name, + block: .execution_outcome.block_height, + evidence: ( + if .transaction.actions[0].FunctionCall.method_name == "request_execution" + then (.receipts[0].execution_outcome.outcome.logs + | map(select(startswith("EVENT_JSON"))) | .[0] + | sub("EVENT_JSON:"; "") | fromjson | .data[0] as $e + | ($e.request_data | fromjson) as $r + | {request_id: $r.request_id, sender_id: $r.sender_id, project_id: $r.project_id, + data_id: $e.data_id, code_hash: $r.code_source.WasmUrl.hash, + input_preview: ($r.input_data | .[0:80] + "…")}) + else (.receipts[0].receipt.receipt.Action.actions[0].FunctionCall.args + | @base64d | fromjson + | {request_id, success: .output.Json.success, encrypted_bytes: (.output.Json.encrypted_data | length), + instructions: .resources_used.instructions, time_ms: .resources_used.time_ms}) + end + ) + } + ]' +``` + +Both rows carry `request_id: 1868`. The request half, signed by `retrorn.near` in block `194832281`, calls the `zavodil.near/near-email` project with a `delete_email` action and a 32-byte `data_id` — that's NEAR's yield/resume payload identifier, used internally to pause the on-chain promise while the worker runs. The worker half lands 11 blocks later and reports `success: true`, `instructions: 53075053`, `time_ms: 2401`, and a 21,568-byte encrypted result; those `resources_used` figures are the TEE fingerprint you can audit against the request's stated limits. `/v0/transactions` serves historical pairs indefinitely, so you don't need archival RPC to trace this even weeks later — reach for [archival RPC](https://archival-rpc.mainnet.fastnear.com) only when cross-checking contract state at the request's block height. + ## Common mistakes - Trying to submit a transaction from the history API instead of raw RPC. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index 360a496..9626c2f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -214,6 +214,46 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Для зафиксированной транзакции `ft_transfer_call` на `wrap.near` передаёт управление в `ft_on_transfer` на `v2.ref-finance.near`, который **падает**. Callback `ft_resolve_transfer` всё равно выполняется на `wrap.near` и логирует `Refund 7278020378457059679767103 from v2.ref-finance.near to …` обратно отправителю — поэтому `callback_ran: true`, несмотря на сбой дочернего receipt. Сбой ниже по цепочке не мешает исходному контракту увидеть свой callback; так async-обработка ошибок NEAR остаётся восстанавливаемой. Строки с `method: "system"` — это рантайм-возвраты газа, а не логика контракта. Чтобы привязать один из этих логов к породившему его receipt, см. [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). +### Сопоставить запрос OutLayer с его TEE-разрешением + +[OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь вызывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, и позже `worker.outlayer.near` вызывает `submit_execution_output_and_resolve` с результатом. Один batch на `/v0/transactions` с обоими хешами возвращает всё сопоставление — метод, корреляционные идентификаторы и TEE-отпечаток — без отдельного worker-запроса. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz +WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg a "$REQUEST_TX" --arg b "$WORKER_TX" '{tx_hashes: [$a, $b]}')" \ + | jq '[ + .transactions[] + | { + hash: .transaction.hash, + signer: .transaction.signer_id, + method: .transaction.actions[0].FunctionCall.method_name, + block: .execution_outcome.block_height, + evidence: ( + if .transaction.actions[0].FunctionCall.method_name == "request_execution" + then (.receipts[0].execution_outcome.outcome.logs + | map(select(startswith("EVENT_JSON"))) | .[0] + | sub("EVENT_JSON:"; "") | fromjson | .data[0] as $e + | ($e.request_data | fromjson) as $r + | {request_id: $r.request_id, sender_id: $r.sender_id, project_id: $r.project_id, + data_id: $e.data_id, code_hash: $r.code_source.WasmUrl.hash, + input_preview: ($r.input_data | .[0:80] + "…")}) + else (.receipts[0].receipt.receipt.Action.actions[0].FunctionCall.args + | @base64d | fromjson + | {request_id, success: .output.Json.success, encrypted_bytes: (.output.Json.encrypted_data | length), + instructions: .resources_used.instructions, time_ms: .resources_used.time_ms}) + end + ) + } + ]' +``` + +Обе строки несут `request_id: 1868`. Half-запрос, подписанный `retrorn.near` в блоке `194832281`, вызывает проект `zavodil.near/near-email` с действием `delete_email` и 32-байтным `data_id` — это идентификатор payload yield/resume NEAR, который контракт использует внутри, чтобы приостановить on-chain-обещание, пока worker выполняется. Half-worker попадает через 11 блоков и рапортует `success: true`, `instructions: 53075053`, `time_ms: 2401` и 21 568-байтный зашифрованный результат; эти цифры `resources_used` — тот самый TEE-отпечаток, который можно сверить с лимитами из запроса. `/v0/transactions` отдаёт исторические пары бессрочно, поэтому archival RPC для самой трассировки не нужен — тянитесь к [archival RPC](https://archival-rpc.mainnet.fastnear.com) только когда нужно сверять состояние контракта на высоте блока запроса. + ## Частые ошибки - Пытаться отправить транзакцию через history-API вместо raw RPC. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 6d96c86..d92b2a7 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -3097,6 +3097,46 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Для зафиксированной транзакции `ft_transfer_call` на `wrap.near` передаёт управление в `ft_on_transfer` на `v2.ref-finance.near`, который **падает**. Callback `ft_resolve_transfer` всё равно выполняется на `wrap.near` и логирует `Refund 7278020378457059679767103 from v2.ref-finance.near to …` обратно отправителю — поэтому `callback_ran: true`, несмотря на сбой дочернего receipt. Сбой ниже по цепочке не мешает исходному контракту увидеть свой callback; так async-обработка ошибок NEAR остаётся восстанавливаемой. Строки с `method: "system"` — это рантайм-возвраты газа, а не логика контракта. Чтобы привязать один из этих логов к породившему его receipt, см. [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). +### Сопоставить запрос OutLayer с его TEE-разрешением + +[OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь вызывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, и позже `worker.outlayer.near` вызывает `submit_execution_output_and_resolve` с результатом. Один batch на `/v0/transactions` с обоими хешами возвращает всё сопоставление — метод, корреляционные идентификаторы и TEE-отпечаток — без отдельного worker-запроса. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz +WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg a "$REQUEST_TX" --arg b "$WORKER_TX" '{tx_hashes: [$a, $b]}')" \ + | jq '[ + .transactions[] + | { + hash: .transaction.hash, + signer: .transaction.signer_id, + method: .transaction.actions[0].FunctionCall.method_name, + block: .execution_outcome.block_height, + evidence: ( + if .transaction.actions[0].FunctionCall.method_name == "request_execution" + then (.receipts[0].execution_outcome.outcome.logs + | map(select(startswith("EVENT_JSON"))) | .[0] + | sub("EVENT_JSON:"; "") | fromjson | .data[0] as $e + | ($e.request_data | fromjson) as $r + | {request_id: $r.request_id, sender_id: $r.sender_id, project_id: $r.project_id, + data_id: $e.data_id, code_hash: $r.code_source.WasmUrl.hash, + input_preview: ($r.input_data | .[0:80] + "…")}) + else (.receipts[0].receipt.receipt.Action.actions[0].FunctionCall.args + | @base64d | fromjson + | {request_id, success: .output.Json.success, encrypted_bytes: (.output.Json.encrypted_data | length), + instructions: .resources_used.instructions, time_ms: .resources_used.time_ms}) + end + ) + } + ]' +``` + +Обе строки несут `request_id: 1868`. Half-запрос, подписанный `retrorn.near` в блоке `194832281`, вызывает проект `zavodil.near/near-email` с действием `delete_email` и 32-байтным `data_id` — это идентификатор payload yield/resume NEAR, который контракт использует внутри, чтобы приостановить on-chain-обещание, пока worker выполняется. Half-worker попадает через 11 блоков и рапортует `success: true`, `instructions: 53075053`, `time_ms: 2401` и 21 568-байтный зашифрованный результат; эти цифры `resources_used` — тот самый TEE-отпечаток, который можно сверить с лимитами из запроса. `/v0/transactions` отдаёт исторические пары бессрочно, поэтому archival RPC для самой трассировки не нужен — тянитесь к [archival RPC](https://archival-rpc.mainnet.fastnear.com) только когда нужно сверять состояние контракта на высоте блока запроса. + ## Частые ошибки - Пытаться отправить транзакцию через history-API вместо raw RPC. diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index 323d76e..fc760a0 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -206,6 +206,46 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Для зафиксированной транзакции `ft_transfer_call` на `wrap.near` передаёт управление в `ft_on_transfer` на `v2.ref-finance.near`, который **падает**. Callback `ft_resolve_transfer` всё равно выполняется на `wrap.near` и логирует `Refund 7278020378457059679767103 from v2.ref-finance.near to …` обратно отправителю — поэтому `callback_ran: true`, несмотря на сбой дочернего receipt. Сбой ниже по цепочке не мешает исходному контракту увидеть свой callback; так async-обработка ошибок NEAR остаётся восстанавливаемой. Строки с `method: "system"` — это рантайм-возвраты газа, а не логика контракта. Чтобы привязать один из этих логов к породившему его receipt, см. [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). +### Сопоставить запрос OutLayer с его TEE-разрешением + +[OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь вызывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, и позже `worker.outlayer.near` вызывает `submit_execution_output_and_resolve` с результатом. Один batch на `/v0/transactions` с обоими хешами возвращает всё сопоставление — метод, корреляционные идентификаторы и TEE-отпечаток — без отдельного worker-запроса. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz +WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg a "$REQUEST_TX" --arg b "$WORKER_TX" '{tx_hashes: [$a, $b]}')" \ + | jq '[ + .transactions[] + | { + hash: .transaction.hash, + signer: .transaction.signer_id, + method: .transaction.actions[0].FunctionCall.method_name, + block: .execution_outcome.block_height, + evidence: ( + if .transaction.actions[0].FunctionCall.method_name == "request_execution" + then (.receipts[0].execution_outcome.outcome.logs + | map(select(startswith("EVENT_JSON"))) | .[0] + | sub("EVENT_JSON:"; "") | fromjson | .data[0] as $e + | ($e.request_data | fromjson) as $r + | {request_id: $r.request_id, sender_id: $r.sender_id, project_id: $r.project_id, + data_id: $e.data_id, code_hash: $r.code_source.WasmUrl.hash, + input_preview: ($r.input_data | .[0:80] + "…")}) + else (.receipts[0].receipt.receipt.Action.actions[0].FunctionCall.args + | @base64d | fromjson + | {request_id, success: .output.Json.success, encrypted_bytes: (.output.Json.encrypted_data | length), + instructions: .resources_used.instructions, time_ms: .resources_used.time_ms}) + end + ) + } + ]' +``` + +Обе строки несут `request_id: 1868`. Half-запрос, подписанный `retrorn.near` в блоке `194832281`, вызывает проект `zavodil.near/near-email` с действием `delete_email` и 32-байтным `data_id` — это идентификатор payload yield/resume NEAR, который контракт использует внутри, чтобы приостановить on-chain-обещание, пока worker выполняется. Half-worker попадает через 11 блоков и рапортует `success: true`, `instructions: 53075053`, `time_ms: 2401` и 21 568-байтный зашифрованный результат; эти цифры `resources_used` — тот самый TEE-отпечаток, который можно сверить с лимитами из запроса. `/v0/transactions` отдаёт исторические пары бессрочно, поэтому archival RPC для самой трассировки не нужен — тянитесь к [archival RPC](https://archival-rpc.mainnet.fastnear.com) только когда нужно сверять состояние контракта на высоте блока запроса. + ## Частые ошибки - Пытаться отправить транзакцию через history-API вместо raw RPC. diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index 323d76e..fc760a0 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -206,6 +206,46 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Для зафиксированной транзакции `ft_transfer_call` на `wrap.near` передаёт управление в `ft_on_transfer` на `v2.ref-finance.near`, который **падает**. Callback `ft_resolve_transfer` всё равно выполняется на `wrap.near` и логирует `Refund 7278020378457059679767103 from v2.ref-finance.near to …` обратно отправителю — поэтому `callback_ran: true`, несмотря на сбой дочернего receipt. Сбой ниже по цепочке не мешает исходному контракту увидеть свой callback; так async-обработка ошибок NEAR остаётся восстанавливаемой. Строки с `method: "system"` — это рантайм-возвраты газа, а не логика контракта. Чтобы привязать один из этих логов к породившему его receipt, см. [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). +### Сопоставить запрос OutLayer с его TEE-разрешением + +[OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь вызывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, и позже `worker.outlayer.near` вызывает `submit_execution_output_and_resolve` с результатом. Один batch на `/v0/transactions` с обоими хешами возвращает всё сопоставление — метод, корреляционные идентификаторы и TEE-отпечаток — без отдельного worker-запроса. + +```bash +TX_BASE_URL=https://tx.main.fastnear.com +REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz +WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 + +curl -s "$TX_BASE_URL/v0/transactions" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg a "$REQUEST_TX" --arg b "$WORKER_TX" '{tx_hashes: [$a, $b]}')" \ + | jq '[ + .transactions[] + | { + hash: .transaction.hash, + signer: .transaction.signer_id, + method: .transaction.actions[0].FunctionCall.method_name, + block: .execution_outcome.block_height, + evidence: ( + if .transaction.actions[0].FunctionCall.method_name == "request_execution" + then (.receipts[0].execution_outcome.outcome.logs + | map(select(startswith("EVENT_JSON"))) | .[0] + | sub("EVENT_JSON:"; "") | fromjson | .data[0] as $e + | ($e.request_data | fromjson) as $r + | {request_id: $r.request_id, sender_id: $r.sender_id, project_id: $r.project_id, + data_id: $e.data_id, code_hash: $r.code_source.WasmUrl.hash, + input_preview: ($r.input_data | .[0:80] + "…")}) + else (.receipts[0].receipt.receipt.Action.actions[0].FunctionCall.args + | @base64d | fromjson + | {request_id, success: .output.Json.success, encrypted_bytes: (.output.Json.encrypted_data | length), + instructions: .resources_used.instructions, time_ms: .resources_used.time_ms}) + end + ) + } + ]' +``` + +Обе строки несут `request_id: 1868`. Half-запрос, подписанный `retrorn.near` в блоке `194832281`, вызывает проект `zavodil.near/near-email` с действием `delete_email` и 32-байтным `data_id` — это идентификатор payload yield/resume NEAR, который контракт использует внутри, чтобы приостановить on-chain-обещание, пока worker выполняется. Half-worker попадает через 11 блоков и рапортует `success: true`, `instructions: 53075053`, `time_ms: 2401` и 21 568-байтный зашифрованный результат; эти цифры `resources_used` — тот самый TEE-отпечаток, который можно сверить с лимитами из запроса. `/v0/transactions` отдаёт исторические пары бессрочно, поэтому archival RPC для самой трассировки не нужен — тянитесь к [archival RPC](https://archival-rpc.mainnet.fastnear.com) только когда нужно сверять состояние контракта на высоте блока запроса. + ## Частые ошибки - Пытаться отправить транзакцию через history-API вместо raw RPC. From 8db48864810c7daea7439f6f7ad801f4b738b07b Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Mon, 20 Apr 2026 10:04:10 -0700 Subject: [PATCH 29/35] docs: simplify examples and add beginner intros - README "Examples Voice And Shape": add four principle bullets (teach NEAR not verify the RPC; one question per subsection; lead with mental model when the RPC surface surprises; one jq pipeline per example). - /rpc: reshape Tip Block, Account Keys, and Contract Reads around the codified principles; add a new "Account State" section with a view_account beginner intro. - /api: add three examples (NFT collections by publisher suffix, balance decomposition via /full state, activity recency across /full arrays), plus a one-call account summary beginner intro. - /neardata: drop the 33-line shared contract_touch_summary helper, rewrite all three examples with inline minimal jq, fix a latent echo/backslash bug in the optimistic-vs-final pipeline with printf, and add a current-tip + txs-per-shard beginner intro. - /transfers: add an unfiltered-recent-activity beginner intro before the existing filter-and-page example. - /tx: merge the overlapping "successful outer, failed descendant" and "callback ran" examples into one consolidated async-failure example; trim the OutLayer pairing to a request_id handshake (richer TEE fingerprint fields noted inline rather than decoded). EN/RU line parity verified on every page; yarn audit:ru-terminology clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 6 +- docs/api/examples.md | 112 ++++++++++++ docs/neardata/examples.md | 145 ++++++++------- docs/rpc/examples.md | 172 +++++------------- docs/transfers/examples.md | 27 ++- docs/tx/examples.md | 94 +++------- .../current/api/examples.md | 112 ++++++++++++ .../current/neardata/examples.md | 143 ++++++++------- .../current/rpc/examples.md | 172 +++++------------- .../current/transfers/examples.md | 27 ++- .../current/tx/examples.md | 94 +++------- 11 files changed, 578 insertions(+), 526 deletions(-) diff --git a/README.md b/README.md index f2817eb..80d486c 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,7 @@ Use the generated `pageModelId` from `mike-docs`. The canonical hosted `/rpcs/.. ### Examples Voice And Shape -Examples pages are reference workflows for technical readers. Keep them task-first and concise. +Examples pages are reference workflows for technical readers. A good example answers a real question a reader arrives with, and the RPC calls teach something about how NEAR actually works along the way. Keep them task-first and concise. - Start with the job and the first surface, not page philosophy. - Prefer exact endpoint or method names in the first sentence. @@ -281,6 +281,10 @@ Examples pages are reference workflows for technical readers. Keep them task-fir - Keep tables or Mermaid diagrams only when they reduce confusion. If the `Flow` block and commands already make the path obvious, cut them. - Do not add `Goal` or `Capture` sections unless they carry information the example does not already make obvious. - Every line before the first command should earn its place by doing one of three jobs: choose the surface, explain the signal to look for, or justify why the example is pinned. +- Use examples to teach NEAR, not to verify the RPC. Pick a question a real reader would arrive with, not one contrived to exercise the RPC's correctness. "Which function-call keys might I want to remove" is real; "prove that `view_state` and `call_function` agree on the counter" is an RPC self-test — it adds surface without signal. +- Give each subsection one question. If a single `###` is trying to answer several at once (cleanup *and* forensic attribution *and* a separate write flow), split it. Forensic trails — multi-hop joins, Delegate unwrapping, archival attribution — belong on their own pages, not as tails on lighter-weight sections. +- Lead with the mental model when the RPC surface surprises a reasonable intuition. If the natural first guess (`block → tx`) doesn't match the actual path (`block → chunk → transactions`), say why up front. Otherwise readers spend the example wondering whether the author took a wrong turn. +- Keep each example to one jq pipeline shaping one RPC response. When an example needs shell-variable joins across three RPCs to produce an answer, that's a signal it's really multiple examples. ## Canonical Route Contract diff --git a/docs/api/examples.md b/docs/api/examples.md index 3de6366..3cea43e 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -10,6 +10,26 @@ page_actions: ## Examples +### Summarize one account in one call + +`/v1/account/{id}/full` is the FastNear API's account aggregator — one call bundles the account's NEAR state, every FT contract it's touched, every NFT collection it's received, and every validator pool it's delegated to. When you already have the `account_id`, this is the fastest "what does this account look like?" read. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + near_balance_yocto: .state.balance, + ft_contracts: (.tokens | length), + nft_contracts: (.nfts | length), + staking_pool_contracts: (.pools | length) + }' +``` + +For `mike.near`: 40 FT contracts in the list, 40 NFT collections, 5 staking pools. The contract counts alone tell you this is an active mainnet account. Every example below drills into one of those surfaces — start here when all you have is the account ID. + ### Resolve a public key, then fetch the account snapshot Look up which account a key belongs to, then read that account's holdings in one call. @@ -30,6 +50,98 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ If `matched` is greater than 1, switch to [V1 Public Key Lookup All](/api/v1/public-key-all) and loop over every returned account. +### How much of this account's NEAR is actually spendable? + +NEAR account state has three buckets that wallet UIs tend to conflate: `balance` is the unstaked amount, `locked` is NEAR tied up in validator stake or a lockup contract, and `storage_bytes` implies a separate amount pinned to the trie at the current rate of 10^19 yoctoNEAR per byte. One pipeline over `/full` breaks them apart. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq ' + (.state.balance | tonumber) as $amount + | (.state.locked | tonumber) as $locked + | (.state.storage_bytes * 10000000000000000000) as $pinned + | 1e24 as $ynear + | { + account_id, + near: { + total_owned: (($amount + $locked) / $ynear), + unstaked: ($amount / $ynear), + stake_or_lockup: ($locked / $ynear), + pinned_to_storage: ($pinned / $ynear), + spendable: (($amount - $pinned) / $ynear) + } + }' +``` + +For `mike.near`: ~2613.49 NEAR total, all unstaked, ~5.58 NEAR pinned to 558 KB of on-chain state, and ~2607.91 NEAR spendable. New accounts feel this most acutely — a fresh named account of ~182 bytes has ~0.00182 NEAR stuck to storage, which is why CLI tools refuse to let you send an account's full balance. + +Point the same pipeline at a validator pool like `astro-stakers.poolv1.near` and the numbers invert: ~730 K unstaked, ~27.68 M in `locked`. That `locked` is the pool's own protocol-level validator stake, not the delegators' funds (those are tracked inside the pool contract's state). The same field means different things on different account types. + +jq uses IEEE-754 doubles, so the NEAR values above are display-precision only — keep the raw yocto strings if you need exact bookkeeping. + +### When did anything about this account last change? + +Every entry under `/full`'s `tokens`, `nfts`, and `pools` arrays carries its own `last_update_block_height` — the block at which the indexer last saw that row change for this account. Taking the max across all three arrays gives a cheap "latest activity" signal without touching the transaction API. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq ' + [ + (.tokens // [])[].last_update_block_height, + (.nfts // [])[].last_update_block_height, + (.pools // [])[].last_update_block_height + ] as $heights + | ($heights | map(select(. != null))) as $tracked + | { + account_id, + total_entries: ($heights | length), + tracked_entries: ($tracked | length), + most_recent_block: ($tracked | max), + oldest_tracked_block: ($tracked | min) + }' +``` + +For `mike.near` against the current tip, this returns 85 total entries across FT, NFT, and pool contracts, 34 with a tracked block, and a most-recent block of `194711866` — about 125 K blocks back, or roughly 35 hours at NEAR's ~1 block/sec tempo. For `root.near`: 254 entries, 158 tracked. + +This is the right question for "is this wallet abandoned?" or "has anything moved since block X?" — cheap, one call, no transaction history needed. For the transaction that caused the latest change, widen to the [Transactions API](/tx). Entries with `last_update_block_height: null` predate the indexer's per-row tracking (typically older airdrops) and are ignored here rather than counted as recent. + +### Show NFT collections this wallet holds from a specific publisher + +NEAR account names encode a hierarchy: `mint.sharddog.near` is a subaccount of `sharddog.near`, which is a subaccount of `near`. Publishers that ship multiple NFT collections usually deploy each one as its own subaccount, so a single suffix filter over the account's NFT list recovers everything under one publisher tree — no external collection registry required. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near +PUBLISHER=sharddog.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | jq --arg publisher "$PUBLISHER" ' + ("." + $publisher) as $suffix + | { + account_id: .account_id, + publisher: $publisher, + collections: [ + .tokens[] + | select(.contract_id | endswith($suffix)) + | { + contract_id, + last_update_block_height, + status: (if .last_update_block_height == null then "dormant" else "active" end) + } + ] | sort_by(.last_update_block_height // 0) + }' +``` + +For `mike.near` and `sharddog.near`, this returns four subaccount contracts: `comic`, `mintv2`, `mint`, and `claim`. The two with a non-null `last_update_block_height` (`mint` at `115715361` and `claim` at `119718026`) are where the wallet's position actually changed. The other two are dormant — common for drop-era contracts an account received into but never interacted with again. + +Swap `PUBLISHER` to any account to scope the filter to a different publisher tree. + ### Does this wallet show direct staking, liquid staking tokens, or both? Direct pool positions live on `/staking`; liquid staking tokens (stNEAR, LiNEAR, etc.) sit on `/ft` like any other FT. Read both, classify the wallet — `root.near` shows up as `mixed`. diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index 9f4118a..e67d367 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -10,117 +10,126 @@ page_actions: ## Examples -Each hydrated NEAR Data block document carries per-shard transactions, receipts, execution outcomes, and state changes. The three scenarios below share one `bash` helper that rolls those four signals into a single summary with handoff fields. Define it once, then pipe blocks through it: +NEAR Data returns each block fully hydrated as one JSON document — header plus per-shard chunks, receipts, execution outcomes, and state changes — so a single `curl` gives you everything you need to filter for a specific contract without a second call. + +### What block is NEAR on right now? + +`/v0/last_block/final` 302-redirects to the current finalized block. Before filtering for a specific contract, it's worth seeing what one block looks like at the protocol level: transactions arrive sharded, so the tx count for a block is a sum across shards — not a single top-level number. ```bash -contract_touch_summary() { - jq -r --arg contract "$1" ' - [ .shards[] | { - shard_id, - direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length), - sample_tx_hash: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash] | .[0]), - sample_receipt_id: ( - [ .chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id ] + - [ .receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract) | .execution_outcome.id ] + - [ .state_changes[]? | select(.change.account_id? == $contract) | (.cause.receipt_hash? // empty) ] - | .[0] - ) - } - | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) - ] as $rows - | { - height: .block.header.height, - hash: .block.header.hash, - contract: $contract, - touched: (($rows | length) > 0), - shards: ($rows | map(.shard_id)), - evidence: { - direct_txs: (($rows | map(.direct_txs) | add) // 0), - incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), - execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), - state_changes: (($rows | map(.state_changes) | add) // 0) - }, - sample_tx_hash: ([ $rows[] | .sample_tx_hash | select(.) ] | .[0]), - sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) - }' -} +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ + | jq '{ + height: .block.header.height, + timestamp_nanosec: .block.header.timestamp_nanosec, + txs_per_shard: [.shards[] | {shard_id, tx_count: (.chunk.transactions | length)}], + total_txs: ([.shards[].chunk.transactions[]?] | length) + }' ``` +A live block shows 9 shards and a handful of transactions scattered across them — most shards are empty in any given block, and activity tends to cluster on whichever shards host the busy contracts. `timestamp_nanosec` is a Unix time in nanoseconds (divide by 1e9 for seconds). With this one call you already have everything needed to dig deeper — the filtering examples below are just jq over this same response. + ### Did my contract get touched in the latest finalized block? -`/v0/last_block/final` 302-redirects to the current finalized block; follow it and pipe straight through the helper. +`/v0/last_block/final` 302-redirects to the current finalized block. Contracts can show up in a chunk's `transactions` (when they are the `receiver_id`) or in its `receipts` (when a cross-shard call lands), so one jq pass over the shards covers both. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ - | contract_touch_summary "$TARGET_CONTRACT" + | jq --arg contract "$TARGET_CONTRACT" '{ + height: .block.header.height, + contract: $contract, + touched_shards: [ + .shards[] | { + shard_id, + txs: [.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash], + receipts: [.chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id] + } | select((.txs | length) + (.receipts | length) > 0) + ] + }' ``` -Read `touched: false` as a complete, unambiguous answer for a quiet block. On `true`, the handoff fields (`sample_tx_hash`, `sample_receipt_id`) drop you straight into [/tx/examples](/tx/examples) for the readable story. One call replaces scanning chunks by hand — and note that `touched: true` with `state_changes: 0` is a real shape: a receipt can land in a chunk without producing state mutation in the same block. +`touched_shards: []` is a complete answer for a quiet block. A non-empty list names the shards where the contract showed up and gives you the concrete `tx` hashes or `receipt_id`s — pipe a hash into the [Transactions API](/tx) when you want the human-readable story. Receipts without matching `txs` are normal: a cross-contract call shows up as an incoming receipt in this block even if the originating transaction landed earlier. -### Did I see activity optimistically, and did it survive finality? +### Did activity show up optimistically, and did finality catch up? -Optimistic blocks live at `/v0/block_opt/{height}`; once finality catches up (usually within one block, ~1s on mainnet), the same height is also served at `/v0/block/{height}`. Run the helper on both and compare. +Optimistic blocks ship at `/v0/block_opt/{height}` about a second ahead of `/v0/block/{height}`. A monitoring loop can act on the optimistic signal and expect the same answer to arrive at the finalized endpoint one block later — unless network stress widens the gap, in which case the finalized fetch returns `null` and you wait. ```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + +count_touches() { + jq --arg contract "$1" ' + [.shards[] + | ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length) + + ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length)] + | add // 0' +} + OPT_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" OPT_HEIGHT="${OPT_LOCATION##*/}" -echo "Optimistic view at $OPT_HEIGHT:" -curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | contract_touch_summary "$TARGET_CONTRACT" - -echo "Finalized view at $OPT_HEIGHT:" +echo "optimistic @ $OPT_HEIGHT: $(curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | count_touches "$TARGET_CONTRACT") touches" FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" -if [ "$(echo "$FINAL" | jq 'type')" = '"null"' ]; then - echo "finality has not caught up to $OPT_HEIGHT yet" +if [ "$(printf '%s' "$FINAL" | jq 'type')" = '"null"' ]; then + echo "finalized @ $OPT_HEIGHT: not caught up yet" else - echo "$FINAL" | contract_touch_summary "$TARGET_CONTRACT" + echo "finalized @ $OPT_HEIGHT: $(printf '%s' "$FINAL" | count_touches "$TARGET_CONTRACT") touches" fi ``` -On a healthy network the two summaries match immediately; the value is in the pattern, not the dramatic difference. A monitoring loop that reacts to the optimistic signal knows the same answer is one block away from durable. Use the `finality has not caught up` branch when you really do need to distinguish "seen optimistically" from "confirmed" — during chain stress, that gap widens. +On a healthy mainnet the two counts match within a second. The value is in the *pattern* — the optimistic stream gives you an answer you can act on immediately, with the finalized stream arriving a block later as durable confirmation. -### Which shard actually changed my contract in this block? +### Which shard actually changed my contract's state? -Blocks are thin — most finalized blocks show no state mutation for any given contract. Walk back from the finalized head until the helper reports `state_changes > 0`, then open the winning shard with `/v0/block/{height}/shard/{shard_id}` for the actual mutation payload. +Most finalized blocks show no state mutation for any given contract — activity is sparse and shard-local. Walk back from the finalized head until the contract's state actually changes, then open that shard for the mutation payload. The block-level call tells you *which* shard; the shard-level call tells you *how*. ```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" -TARGET_HEIGHT="" -WINNING_SHARD="" +FOUND_HEIGHT="" +FOUND_SHARD="" -for OFFSET in 0 1 2 3 4 5 6 7 8 9; do +for OFFSET in $(seq 0 15); do H=$((HEAD - OFFSET)) - SUMMARY="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" | contract_touch_summary "$TARGET_CONTRACT")" - if [ "$(echo "$SUMMARY" | jq '.evidence.state_changes')" -gt 0 ]; then - TARGET_HEIGHT=$H - WINNING_SHARD="$(echo "$SUMMARY" | jq -r '.shards[0]')" - echo "$SUMMARY" + SHARD="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" \ + | jq -r --arg contract "$TARGET_CONTRACT" ' + .shards[] + | select([.state_changes[]? | select(.change.account_id? == $contract)] | length > 0) + | .shard_id + ' | head -1)" + if [ -n "$SHARD" ]; then + FOUND_HEIGHT=$H; FOUND_SHARD=$SHARD break fi done -curl -s "$NEARDATA_BASE_URL/v0/block/$TARGET_HEIGHT/shard/$WINNING_SHARD" \ - | jq --arg contract "$TARGET_CONTRACT" '{ - shard_id, - chunk_hash: .chunk.header.chunk_hash, - matching_state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause_type: (.cause | keys[0]), account_id: .change.account_id}][0:3], - matching_execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0]), predecessor_id: .receipt.predecessor_id}][0:3] - }' +if [ -z "$FOUND_HEIGHT" ]; then + echo "no state mutation for $TARGET_CONTRACT in the last 16 finalized blocks" +else + curl -s "$NEARDATA_BASE_URL/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + | jq --arg contract "$TARGET_CONTRACT" --argjson height "$FOUND_HEIGHT" --argjson shard_id "$FOUND_SHARD" '{ + height: $height, + shard_id: $shard_id, + state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause: (.cause | keys[0])}][0:3], + execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0])}][0:3] + }' +fi ``` -On mainnet, `intents.near` consistently executes on shard 7, so the walk-back typically lands within a few blocks. The shard payload then names the actual state-change types (`account_update`, `data_update`, and the like) and the receipt execution outcomes that produced them — shard-local proof without guessing. Widen the `OFFSET` range for less-active contracts. +On mainnet, `intents.near` lives on shard 7, so the walk typically lands within a handful of blocks. The shard payload names the actual state-change types (`account_update`, `data_update`, etc.) and the receipt outcomes that caused them — shard-local proof without guessing. Widen the offset range for contracts with lighter traffic. ## When to widen -- Use [Transactions API](/tx) once you have a `tx_hash` and want the human-readable transaction story. -- Use [RPC Reference](/rpc) when the next question is about exact protocol-native receipt or block semantics. +- Use the [Transactions API](/tx) once you have a `tx_hash` and want the human-readable transaction story. +- Use the [RPC Reference](/rpc) when the next question is about exact protocol-native receipt or block semantics. - Use [Block Headers](/neardata/block-headers) when you only need head progression or finality lag, not contract-touch inspection. diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index 1611496..cfe8225 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -12,6 +12,27 @@ page_actions: Start with the RPC method that answers the question. Use `tx` to track inclusion and finality from a tx hash, and widen only when you need receipt trees, raw state, or shard-level tracing. +## Account State + +### Show an account's balance and storage at finality + +`view_account` is the canonical RPC query for an account's current state. One call returns the unstaked balance, any stake-locked amount, storage consumed, and the block the reading was taken at. `finality: "final"` ensures you're reading stable state, not an optimistic view. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_account",account_id:$account_id,finality:"final"} + }')" \ + | jq '.result | {amount, locked, storage_usage, block_height, block_hash}' +``` + +For `mike.near`, this returns `amount` (yoctoNEAR held unstaked), `locked: "0"` (nothing in validator stake or a lockup contract), and `storage_usage: 558441` — 558 KB of on-chain state. The `block_height`/`block_hash` pair anchors the reading; to read multiple accounts at the *same* block, reuse the returned `block_hash` as `block_id` on follow-up queries. + ## Transaction Inclusion and Finality ### Track a transaction from hash to finality @@ -48,7 +69,7 @@ Two handoffs from here: ### Describe the first action of the first transaction at the current tip -Walk `status` → `block` → `chunk`, skipping empty chunks along the way. Most chunks in a tip block are empty — their `tx_root` is the sentinel `11111111111111111111111111111111` — so the selector has to filter. +A NEAR block is a header over N shard chunks, not a flat list of transactions. `block` returns chunk headers; the transactions live one level down, inside `chunk`. There's no `block → tx` shortcut — the block doesn't carry transaction hashes, so `tx` (which needs a hash) doesn't enter this flow at all. The canonical walk is `status` → `block` → `chunk`, skipping empty chunks along the way. Most chunks in a tip block are empty — their `tx_root` is the sentinel `11111111111111111111111111111111` — so the selector has to filter. ```bash RPC_URL=https://rpc.mainnet.fastnear.com @@ -94,9 +115,16 @@ A live run returns the current tip's first chunk, first transaction, and first a ## Account and Key Mechanics -### Audit old Near Social function-call keys +### Identify function-call keys you might want to remove + +Every wallet, gateway, and dapp session you sign into tends to leave behind a function-call key. Most of them you'll never use again. `view_access_key_list` returns every key on an account; the structure of the nonce tells you which ones are stale. -Creators accumulate Social function-call keys from every wallet and BOS gateway they've used. `view_access_key_list` returns all of them; one filter narrows to `social.near`, and the **low six digits of the nonce** double as a usage counter — new keys start at `block_height * 10^6` and increment by one per transaction. +New keys start at `block_height * 10^6` and the value increments by one per transaction the key signs, so: + +- `nonce / 10^6` → the block the key was added at +- `nonce % 10^6` → the number of times the key has been used + +Any key with `tx_count: 0` was created and never used — the clearest candidate for cleanup. Keys scoped to a contract you no longer interact with are the next tier. The filter below narrows to `social.near`, but `RECEIVER_ID` is the only line that changes to audit a different contract. ```bash RPC_URL=https://rpc.mainnet.fastnear.com @@ -112,13 +140,13 @@ curl -s "$RPC_URL" \ | jq --arg receiver "$RECEIVER_ID" ' { total_keys: (.result.keys | length), - social_fcks: [ + fcks_for_receiver: [ .result.keys[] | select((.access_key.permission | type) == "object") | select(.access_key.permission.FunctionCall.receiver_id == $receiver) | { public_key, - created_near_block: (.access_key.nonce / 1000000 | floor), + added_at_block: (.access_key.nonce / 1000000 | floor), tx_count: (.access_key.nonce % 1000000), method_names: (.access_key.permission.FunctionCall.method_names | if . == [] then "ANY" else . end), allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") @@ -127,119 +155,17 @@ curl -s "$RPC_URL" \ }' ``` -For `mike.near`, this returns dozens of `social.near` function-call keys. Entries with `tx_count: 0` were created and never used — prime candidates for cleanup. `method_names: "ANY"` means the key can call any method on `social.near`; a narrowed list like `["find_grants", "insert_grant", "delete_grant"]` means the key was scoped to a specific dapp's write surface. - -To delete one, sign a `DeleteKey` action with a **full-access** key — a function-call key cannot authorize `DeleteKey` — then submit via [`send_tx`](/rpc/transaction/send-tx). Re-run the same list to confirm the deletion. The signing itself is standard near-api-js territory and not the interesting part of the audit. - -### Which transaction added this `social.near` function-call key, and who authorized it? +For `mike.near`, this returns dozens of `social.near` function-call keys. Entries with `tx_count: 0` were created and never used — prime removal candidates. `method_names: "ANY"` means the key can call any method on `social.near`; a narrowed list like `["find_grants", "insert_grant", "delete_grant"]` means the key was scoped to one dapp's write surface. -The same nonce that tracks usage also anchors the `AddKey` in block time: new keys start at roughly `block_height * 10^6`, so dividing the current nonce by a million gives a tight search window. Hydrate the candidates once, and the response carries enough to distinguish a direct `AddKey` from a delegated (meta-tx) authorization — which tells you *which key signed the decision*, not just which account paid gas. - -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=mike.near -TARGET_PUBLIC_KEY=ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs - -CURRENT_NONCE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg public_key "$TARGET_PUBLIC_KEY" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"view_access_key",account_id:$account_id,public_key:$public_key,finality:"final"} - }')" \ - | jq -r '.result.nonce')" - -ADD_KEY_BLOCK=$((CURRENT_NONCE / 1000000)) - -TX_HASHES="$(curl -s "$TX_BASE_URL/v0/account" -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" \ - --argjson from $((ADD_KEY_BLOCK - 20)) --argjson to $((ADD_KEY_BLOCK + 5)) '{ - account_id: $account_id, is_real_signer: true, - from_tx_block_height: $from, to_tx_block_height: $to, desc: false, limit: 50 - }')" \ - | jq -c '[.account_txs[].transaction_hash]')" - -curl -s "$TX_BASE_URL/v0/transactions" -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES" '{tx_hashes: $tx_hashes}')" \ - | jq --arg target "$TARGET_PUBLIC_KEY" ' - [ .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? | .AddKey? | select(.public_key == $target) - | {mode: "direct", authorizing_public_key: $tx.transaction.public_key, permission: .access_key.permission}), - ($tx.transaction.actions[]? | .Delegate? | .delegate_action as $d - | $d.actions[]? | .AddKey? | select(.public_key == $target) - | {mode: "delegated", authorizing_public_key: $d.public_key, permission: .access_key.permission}) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - signer_id: $tx.transaction.signer_id, - receiver_id: $tx.transaction.receiver_id, - add_key_receipt: ([$tx.receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]?; .AddKey.public_key? == $target)) - | {receipt_id: .receipt.receipt_id, receipt_block: .execution_outcome.block_height}][0]) - } + . - ]' -``` - -For `mike.near`'s `ed25519:7GZg…` key (the first `social.near` FCK from the audit above), this resolves to transaction `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` at outer tx block `112057390`. The outer signer is `app.herewallet.near` — HERE Wallet's relayer — and `mode: "delegated"` tells the rest of the story: the relayer paid gas, but the *authorizing* key inside the Delegate is `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu`, a `mike.near` full-access key that signed the underlying `AddKey`. That's the meta-tx distinction the top-level `signer_id` alone would hide. - -`add_key_receipt` completes the picture: the `AddKey` executed in block `112057392`, two blocks after the outer tx, because the Delegate hops from the relayer's shard to the target account's. Widen the `-20/+5` window if the key has been used heavily since creation. - -### Register FT storage if needed, then transfer tokens - -NEP-141 tokens require each recipient to pre-register storage on the contract before they can hold a balance. Two view calls answer the registration question authoritatively *before* you send — skipping that check is how `ft_transfer` ends up quietly refunded to the sender. - -```bash -RPC_URL=https://rpc.testnet.fastnear.com -TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -RECEIVER_ACCOUNT_ID=mike.testnet - -ACCOUNT_ARGS_B64="$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" - -REGISTERED="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_of",args_base64:$args,finality:"final"} - }')" \ - | jq '(.result.result | implode | fromjson) != null')" - -MIN_DEPOSIT="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_bounds",args_base64:"e30=",finality:"final"} - }')" \ - | jq -r '.result.result | implode | fromjson | .min')" - -jq -n --argjson registered "$REGISTERED" --arg min "$MIN_DEPOSIT" '{ - registered: $registered, - min_storage_deposit_yocto: $min -}' -``` - -For the pinned testnet contract, `storage_balance_of({account_id: "mike.testnet"})` returns `null` (not registered) and `storage_balance_bounds` returns `{min: "1250000000000000000000", max: "1250000000000000000000"}` — a flat 0.00125 NEAR registration fee. That's the contract's own answer, and it's all the read side you need before you write. - -The write side is two signed function calls (near-api-js `transactions.functionCall` or any NEAR signer library works identically): - -- `storage_deposit({account_id: "", registration_only: true})` with deposit `` yocto and 100 Tgas — skip if `registered: true`. -- `ft_transfer({receiver_id: "", amount: "", memo: "..."})` with deposit 1 yocto (required by NEP-141) and 100 Tgas. - -Submit each signed transaction through [`send_tx`](/rpc/transaction/send-tx) with `wait_until: "FINAL"`. Verify afterward with the contract's own view method — no need for indexed history to prove the transfer stuck: - -```bash -curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"ft_balance_of",args_base64:$args,finality:"final"} - }')" \ - | jq '{receiver_balance: (.result.result | implode | fromjson)}' -``` +To remove one, sign a `DeleteKey` action with a **full-access** key (a function-call key cannot authorize `DeleteKey`) and submit via [`send_tx`](/rpc/transaction/send-tx). Re-run the query to confirm the key is gone. ## Contract Reads and Raw State -### How do I read a contract's raw storage directly? +### Read a contract's storage without executing it + +A view method like `get_num` still makes the node load the contract's wasm and run it. If you already know the storage key, `view_state` returns the raw serialized bytes directly — no execution, and no dependency on whether the contract exposes a getter for that field at all. -Two RPC methods answer the same counter question from different layers: `view_state` pulls raw trie bytes without executing code, and `call_function` runs the contract's own view method. When they agree, you've proved the contract's view method matches its stored state. +Contracts built with `near-sdk-rs` store the top-level `#[near_bindgen]` struct under the key `STATE`. Pass `STATE` as `prefix_base64` (`U1RBVEU=` is base64 for those four ASCII bytes) and the node returns the serialized value. ```bash RPC_URL=https://rpc.testnet.fastnear.com @@ -252,26 +178,14 @@ RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ }')" \ | jq -r '.result.values[0].value')" -RAW_I8="$(python3 -c "import base64,sys;print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" +DECODED_I8="$(python3 -c "import base64; print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" -METHOD_VALUE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"get_num",args_base64:"e30=",finality:"final"} - }')" \ - | jq -r '.result.result | implode | fromjson')" - -jq -n --arg raw_b64 "$RAW_B64" --argjson raw_i8 "$RAW_I8" --argjson method "$METHOD_VALUE" '{ - raw_state_b64: $raw_b64, - raw_state_decoded: $raw_i8, - view_method_value: $method, - agree: ($raw_i8 == $method) -}' +jq -n --arg raw "$RAW_B64" --argjson val "$DECODED_I8" '{raw_bytes_base64: $raw, decoded_i8: $val}' ``` -For the live counter, `view_state` at key `STATE` (base64 `U1RBVEU=`) returns `"CQ=="` — one byte `0x09`, decoded as signed i8 to `9`; `get_num` also returns `9`. They agree because the contract stores `val: i8` at that key. The `signed=True` matters: a negative counter would show up as `"/w=="` (byte `0xff` → i8 `-1`, not u8 `255`). +For the live counter, this returns `"CQ=="` — one byte `0x09`, decoded as signed i8 to `9`. That's the same number `get_num` would return, but read straight from the trie without running any contract code. `signed=True` matters: a negative counter serializes as `"/w=="` (byte `0xff` → i8 `-1`, not u8 `255`). -`view_state` is the right tool when a contract lacks a view method for the data you need, when you want to verify a view method against actual storage, or when you need a key family the contract doesn't expose publicly. For everything else, `call_function` is lower ceremony. If the next question becomes historical rather than current, widen to [KV FastData API](/fastdata/kv). +Reach for `view_state` when a contract doesn't expose a view method for the data you need, or when you want a key family the contract doesn't publish. For most reads `call_function` is still lower ceremony. If the question turns historical rather than current, widen to [KV FastData API](/fastdata/kv). ## NEAR Social and BOS Exact Reads diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index 2e86f32..a58fd39 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -8,7 +8,32 @@ page_actions: - markdown --- -## Example +## Examples + +### What's this account's recent transfer activity? + +`/v0/transfers` with just `account_id` and `desc: true` returns the most recent transfers touching that account across every asset type, both directions mixed. Each row already carries `human_amount`, `asset_id`, and `transaction_id`, so the feed doubles as a quick activity scan before you reach for filters. + +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +ACCOUNT_ID=root.near + +curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id: $account_id, desc: true, limit: 5}')" \ + | jq '{ + recent: [.transfers[] | { + block_height, + asset_id, + human_amount, + other_account_id, + transfer_type, + tx: .transaction_id + }] + }' +``` + +For `root.near`, the latest rows mix `FtTransfer` and `MtTransfer` assets. `asset_id` uses NEP-standard URIs (`native:near`, `nep141:...`, `nep245:...`), so one field tells you which standard to reach for next. Positive `human_amount` means the account received; negative means it sent. `other_account_id: null` is normal for multi-token shapes where the counterparty sits inside a contract boundary rather than as a top-level account. ### Filter and page a transfer feed for one account diff --git a/docs/tx/examples.md b/docs/tx/examples.md index 4956972..9dc224d 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -140,48 +140,9 @@ curl -s "$RPC_URL" \ `UNKNOWN_ACCOUNT` is the proof. If `CreateAccount` had stuck, `view_account` would resolve; because it does not, the earlier `Transfer` and `AddKey` from the same batched receipt did not stick either. -### Why did this contract call look successful, but a later receipt failed? +### When a tx looks successful, what actually happened? -A single tx can end with the outer handoff reporting `SuccessReceiptId` while a descendant receipt quietly fails — that's NEAR's async model, and `/v0/transactions` surfaces the whole timeline in one call. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - tx_handoff: .transactions[0].execution_outcome.outcome.status, - outer_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, - descendant_failures: [ - .transactions[0].receipts[] - | select(.execution_outcome.outcome.status.Failure != null) - | { - receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - block_height: .execution_outcome.block_height, - failure: .execution_outcome.outcome.status.Failure - } - ], - receipt_timeline: [ - .transactions[0].receipts[] - | { - receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - status_class: (.execution_outcome.outcome.status | keys[0]) - } - ] - }' -``` - -For the pinned mainnet tx, `tx_handoff` is `SuccessReceiptId` — the tx kicked off its first receipt cleanly. Read that alone and you'd call it a win. `descendant_failures` tells a second story: `ft_on_transfer` on `v2.ref-finance.near` panicked with `E51: contract paused` — the DEX had been paused when this swap ran, so it couldn't accept the wrapped NEAR. The `receipt_timeline` then shows how the story resolved: wrap.near's callback `ft_resolve_transfer` ran anyway and emitted a `Refund` log returning the wrapped NEAR to the sender. - -Receipt success is not transitive. A protocol can hand off cleanly and still see the detached work fail later. If your app "looked successful" but money came back anyway, walk this same timeline — the split is visible on the indexed response without a separate RPC status call. To check specifically that your callback ran, see [Did my callback run at all?](#did-my-callback-run-at-all). - -### Did my callback run at all? - -NEAR cross-contract calls return through a callback receipt on the origin contract. Whether that callback actually ran is a one-line `any(...)` check against the indexed receipt list — and the full refund story falls out of the same response. +A tx's outer `execution_outcome.outcome.status` reports `SuccessReceiptId` whenever the first receipt handoff worked — it says nothing about whether downstream receipts succeeded or whether the origin callback ran. One pipeline over `/v0/transactions` answers all three questions at once. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -193,30 +154,38 @@ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ - top_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, - callback_ran: any( - .transactions[0].receipts[]; - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback - ), - receipt_chain: [ + outer: { + method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + tx_handoff: (.transactions[0].execution_outcome.outcome.status | keys[0]) + }, + callback: { + expected_on: $origin, + method: $callback, + ran: any( + .transactions[0].receipts[]; + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback + ) + }, + descendant_failures: [ .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) | { receiver_id: .receipt.receiver_id, method: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - block: .execution_outcome.block_height, - status: (.execution_outcome.outcome.status | keys[0]), - logs: .execution_outcome.outcome.logs + cause: .execution_outcome.outcome.status.Failure } ] }' ``` -For the pinned tx, `ft_transfer_call` on `wrap.near` hands off to `v2.ref-finance.near`'s `ft_on_transfer`, which **fails**. The callback `ft_resolve_transfer` still runs on `wrap.near` and logs `Refund 7278020378457059679767103 from v2.ref-finance.near to …` back to the sender — so `callback_ran: true` even though the downstream receipt failed. A downstream failure never prevents the origin contract from seeing its callback; that's how NEAR async error handling stays recoverable. The `method: "system"` rows are runtime gas refunds, not contract logic. To attribute one of the logs above to its emitting receipt, see [Which receipt emitted this log or event?](#which-receipt-emitted-this-log-or-event). +For the pinned tx, `outer.method` is `ft_transfer_call` and `outer.tx_handoff` is `SuccessReceiptId` — the tx kicked off its first receipt cleanly, and read alone you'd call it a win. `descendant_failures` tells a second story: `ft_on_transfer` on `v2.ref-finance.near` panicked with `E51: contract paused` — the DEX was paused when this swap ran, so it couldn't accept the wrapped NEAR. `callback.ran: true` tells a third: `wrap.near`'s `ft_resolve_transfer` fired anyway. A downstream failure never prevents the origin contract's callback from running — that's the mechanism by which NEP-141 refunds the sender when the receiver rejects. + +Receipt success is not transitive. A protocol can hand off cleanly and still see the detached work fail later; the origin callback runs either way. Read these three fields together and the async story is legible without chasing the receipt chain by hand. To surface the `Refund` log line itself, pivot to [Which receipt emitted this log or event?](#which-receipt-emitted-this-log-or-event). ### Pair one OutLayer request with its TEE worker resolution -[OutLayer](https://outlayer.fastnear.com) splits one logical call across two transactions: a user calls `request_execution` on `outlayer.near`, an Intel TDX worker runs the requested WASM off-chain, and `worker.outlayer.near` later calls `submit_execution_output_and_resolve` with the result. One `/v0/transactions` batch with both hashes returns the full pairing — method, correlation IDs, and TEE fingerprint — without a separate worker query. +[OutLayer](https://outlayer.fastnear.com) splits one logical call across two transactions: a user signs `request_execution` on `outlayer.near`, an Intel TDX worker runs the requested WASM off-chain, then `worker.outlayer.near` submits the result with `submit_execution_output_and_resolve`. Both halves carry the same `request_id` — passing the two tx hashes to `/v0/transactions` in one call and extracting that field from each proves the pair. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -229,30 +198,25 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | jq '[ .transactions[] | { + role: (if .transaction.actions[0].FunctionCall.method_name == "request_execution" + then "request" else "worker" end), hash: .transaction.hash, signer: .transaction.signer_id, method: .transaction.actions[0].FunctionCall.method_name, block: .execution_outcome.block_height, - evidence: ( + request_id: ( if .transaction.actions[0].FunctionCall.method_name == "request_execution" - then (.receipts[0].execution_outcome.outcome.logs - | map(select(startswith("EVENT_JSON"))) | .[0] - | sub("EVENT_JSON:"; "") | fromjson | .data[0] as $e - | ($e.request_data | fromjson) as $r - | {request_id: $r.request_id, sender_id: $r.sender_id, project_id: $r.project_id, - data_id: $e.data_id, code_hash: $r.code_source.WasmUrl.hash, - input_preview: ($r.input_data | .[0:80] + "…")}) + then (.receipts[0].execution_outcome.outcome.logs[] | select(startswith("EVENT_JSON")) + | sub("EVENT_JSON:"; "") | fromjson | .data[0].request_data | fromjson | .request_id) else (.receipts[0].receipt.receipt.Action.actions[0].FunctionCall.args - | @base64d | fromjson - | {request_id, success: .output.Json.success, encrypted_bytes: (.output.Json.encrypted_data | length), - instructions: .resources_used.instructions, time_ms: .resources_used.time_ms}) + | @base64d | fromjson | .request_id) end ) } ]' ``` -Both rows carry `request_id: 1868`. The request half, signed by `retrorn.near` in block `194832281`, calls the `zavodil.near/near-email` project with a `delete_email` action and a 32-byte `data_id` — that's NEAR's yield/resume payload identifier, used internally to pause the on-chain promise while the worker runs. The worker half lands 11 blocks later and reports `success: true`, `instructions: 53075053`, `time_ms: 2401`, and a 21,568-byte encrypted result; those `resources_used` figures are the TEE fingerprint you can audit against the request's stated limits. `/v0/transactions` serves historical pairs indefinitely, so you don't need archival RPC to trace this even weeks later — reach for [archival RPC](https://archival-rpc.mainnet.fastnear.com) only when cross-checking contract state at the request's block height. +Both rows carry `request_id: 1868`, confirming the pair. The request half, signed by `retrorn.near` in block `194832281`, lives in an `EVENT_JSON:` log on its receipt (that's NEAR's yield/resume pattern — the on-chain promise pauses while the TDX worker runs). The worker half lands 11 blocks later with `submit_execution_output_and_resolve`, signed by `worker.outlayer.near`, and its `request_id` decodes straight out of the base64 `FunctionCall.args`. The same two payloads also carry the richer fingerprint — `sender_id`, `project_id`, `code_hash`, `resources_used.instructions`, `resources_used.time_ms`, encrypted-result byte count — if you want to audit what actually ran; this minimal pipeline just confirms they belong together. `/v0/transactions` serves historical pairs indefinitely, so you don't need archival RPC weeks later. ## Common mistakes diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index 790e7b5..52a36a5 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -10,6 +10,26 @@ page_actions: ## Примеры +### Свести один аккаунт за один вызов + +`/v1/account/{id}/full` — это агрегатор аккаунтов в FastNear API: один вызов собирает NEAR-состояние аккаунта, каждый FT-контракт, которого он касался, каждую NFT-коллекцию, которую он получил, и каждый валидаторский пул, в который делегировал. Если у вас уже есть `account_id`, это самый быстрый ответ на вопрос «что это за аккаунт?». + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + near_balance_yocto: .state.balance, + ft_contracts: (.tokens | length), + nft_contracts: (.nfts | length), + staking_pool_contracts: (.pools | length) + }' +``` + +Для `mike.near`: 40 FT-контрактов в списке, 40 NFT-коллекций, 5 валидаторских пулов. Одни только счётчики контрактов говорят, что это активный mainnet-аккаунт. Все примеры ниже погружаются в какую-то одну из этих поверхностей — начинайте отсюда, когда на руках только ID аккаунта. + ### Определить аккаунт по публичному ключу и сразу получить сводку Найдите, какому аккаунту принадлежит ключ, и прочитайте его активы за один следующий запрос. @@ -30,6 +50,98 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Если `matched` больше 1, переключайтесь на [V1 Public Key Lookup All](/api/v1/public-key-all) и пройдитесь по каждому найденному аккаунту. +### Сколько NEAR на этом аккаунте реально доступно к переводу? + +Состояние аккаунта NEAR делится на три ведра, которые UI кошельков обычно сливает в одно: `balance` — это свободная часть (не в стейкинге), `locked` — NEAR, привязанный к валидаторскому стейку или lockup-контракту, а `storage_bytes` подразумевает ещё отдельную долю, пришпиленную к trie по текущей ставке 10^19 yoctoNEAR за байт. Один pipeline над `/full` разводит их по полкам. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq ' + (.state.balance | tonumber) as $amount + | (.state.locked | tonumber) as $locked + | (.state.storage_bytes * 10000000000000000000) as $pinned + | 1e24 as $ynear + | { + account_id, + near: { + total_owned: (($amount + $locked) / $ynear), + unstaked: ($amount / $ynear), + stake_or_lockup: ($locked / $ynear), + pinned_to_storage: ($pinned / $ynear), + spendable: (($amount - $pinned) / $ynear) + } + }' +``` + +Для `mike.near`: ~2613.49 NEAR всего, всё в свободной части, ~5.58 NEAR закреплено за 558 КБ on-chain-состояния, ~2607.91 NEAR доступно к переводу. Новым аккаунтам это особенно заметно — свежесозданный именованный аккаунт ~182 байта «съедает» ~0.00182 NEAR под storage, и именно поэтому CLI-утилиты не дают отправить полный баланс. + +Наведите тот же pipeline на валидаторский пул вроде `astro-stakers.poolv1.near`, и пропорции перевернутся: ~730 тыс. свободных, ~27.68 млн в `locked`. Этот `locked` — собственный протокольный валидаторский стейк пула, а не средства делегатов (те учитываются внутри состояния контракта пула). Одно и то же поле означает разное на разных типах аккаунтов. + +jq считает в IEEE-754 double, поэтому NEAR-значения выше — только для отображения; для точной бухгалтерии сохраняйте сами yocto-строки. + +### Когда в этом аккаунте что-либо последний раз менялось? + +У каждой записи в массивах `tokens`, `nfts` и `pools` внутри `/full` есть собственное `last_update_block_height` — блок, в котором индексер последний раз видел изменение этой строки для этого аккаунта. Максимум по всем трём массивам даёт дешёвый сигнал «последняя активность» без похода в Transactions API. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq ' + [ + (.tokens // [])[].last_update_block_height, + (.nfts // [])[].last_update_block_height, + (.pools // [])[].last_update_block_height + ] as $heights + | ($heights | map(select(. != null))) as $tracked + | { + account_id, + total_entries: ($heights | length), + tracked_entries: ($tracked | length), + most_recent_block: ($tracked | max), + oldest_tracked_block: ($tracked | min) + }' +``` + +Для `mike.near` относительно текущего tip это возвращает 85 записей по FT-, NFT- и pool-контрактам, 34 с отслеживаемым блоком и самый свежий блок `194711866` — примерно 125 тыс. блоков назад, или около 35 часов при темпе NEAR ~1 блок/сек. Для `root.near`: 254 записи, 158 отслеживаемых. + +Это правильный вопрос для «заброшен ли этот кошелёк?» или «двигалось ли что-то после блока X?» — дёшево, один запрос, без истории транзакций. Чтобы достать саму транзакцию, вызвавшую последнее изменение, расширяйте поверхность до [Transactions API](/tx). Записи с `last_update_block_height: null` относятся ко времени до per-row-отслеживания индексером (обычно старые airdrops) и здесь игнорируются, а не считаются свежими. + +### Показать NFT-коллекции этого кошелька от конкретного издателя + +Имена аккаунтов на NEAR кодируют иерархию: `mint.sharddog.near` — это подаккаунт `sharddog.near`, который, в свою очередь, — подаккаунт `near`. Издатели, выпускающие несколько NFT-коллекций, обычно разворачивают каждую как отдельный подаккаунт, поэтому один фильтр по суффиксу над NFT-списком аккаунта вытаскивает всё опубликованное под одним деревом — без внешнего реестра коллекций. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near +PUBLISHER=sharddog.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | jq --arg publisher "$PUBLISHER" ' + ("." + $publisher) as $suffix + | { + account_id: .account_id, + publisher: $publisher, + collections: [ + .tokens[] + | select(.contract_id | endswith($suffix)) + | { + contract_id, + last_update_block_height, + status: (if .last_update_block_height == null then "dormant" else "active" end) + } + ] | sort_by(.last_update_block_height // 0) + }' +``` + +Для `mike.near` и `sharddog.near` это возвращает четыре контракта-подаккаунта: `comic`, `mintv2`, `mint` и `claim`. Два с ненулевым `last_update_block_height` (`mint` на `115715361` и `claim` на `119718026`) — те, где позиция кошелька действительно менялась. Два других — спящие, типично для одноразовых drop-контрактов, в которые аккаунт что-то получил и больше не возвращался. + +Поменяйте `PUBLISHER` на любой аккаунт, чтобы сфокусировать фильтр на другом дереве издателя. + ### Показывает ли кошелёк прямой стейкинг, liquid staking-токены или оба варианта? Прямые позиции в пулах лежат на `/staking`; liquid staking-токены (stNEAR, LiNEAR и т. п.) лежат на `/ft` как обычные FT. Прочитайте оба эндпоинта и классифицируйте кошелёк — `root.near` оказывается `mixed`. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index 2e059fb..c5938b2 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -10,117 +10,126 @@ page_actions: ## Примеры -Каждый гидратированный документ блока NEAR Data несёт транзакции, receipts, результаты исполнения и state changes с разбивкой по shard. Три сценария ниже используют один `bash`-помощник, который сворачивает эти четыре сигнала в одну сводку с полями для перехода дальше. Определите его один раз и прогоняйте блоки через него: +NEAR Data возвращает каждый блок полностью гидратированным одним JSON-документом — header плюс per-shard chunks, receipts, результаты исполнения и state changes, — так что один `curl` уже даёт всё необходимое, чтобы отфильтровать нужный контракт без второго запроса. + +### На каком блоке NEAR сейчас? + +`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Прежде чем фильтровать по конкретному контракту, полезно увидеть, как выглядит один блок на уровне протокола: транзакции приходят с разбивкой по shard, поэтому общее число транзакций в блоке — это сумма по shards, а не одно поле верхнего уровня. ```bash -contract_touch_summary() { - jq -r --arg contract "$1" ' - [ .shards[] | { - shard_id, - direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length), - sample_tx_hash: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash] | .[0]), - sample_receipt_id: ( - [ .chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id ] + - [ .receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract) | .execution_outcome.id ] + - [ .state_changes[]? | select(.change.account_id? == $contract) | (.cause.receipt_hash? // empty) ] - | .[0] - ) - } - | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) - ] as $rows - | { - height: .block.header.height, - hash: .block.header.hash, - contract: $contract, - touched: (($rows | length) > 0), - shards: ($rows | map(.shard_id)), - evidence: { - direct_txs: (($rows | map(.direct_txs) | add) // 0), - incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), - execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), - state_changes: (($rows | map(.state_changes) | add) // 0) - }, - sample_tx_hash: ([ $rows[] | .sample_tx_hash | select(.) ] | .[0]), - sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) - }' -} +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ + | jq '{ + height: .block.header.height, + timestamp_nanosec: .block.header.timestamp_nanosec, + txs_per_shard: [.shards[] | {shard_id, tx_count: (.chunk.transactions | length)}], + total_txs: ([.shards[].chunk.transactions[]?] | length) + }' ``` +Живой блок показывает 9 shards и горсть транзакций, распределённых по ним — большинство shards в любом блоке пустые, а активность концентрируется на тех shards, где живут загруженные контракты. `timestamp_nanosec` — это Unix-время в наносекундах (делите на 1e9, чтобы получить секунды). С этим одним вызовом у вас уже есть всё необходимое для дальнейшего копания — примеры фильтрации ниже просто применяют jq к тому же ответу. + ### Был ли мой контракт затронут в последнем финализированном блоке? -`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок; пройдите по нему и направьте результат сразу в помощник. +`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Контракт может проявиться либо в `transactions` chunk (когда он `receiver_id`), либо в `receipts` (когда прилетает cross-shard-вызов), поэтому один проход jq по shards покрывает оба случая. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ - | contract_touch_summary "$TARGET_CONTRACT" + | jq --arg contract "$TARGET_CONTRACT" '{ + height: .block.header.height, + contract: $contract, + touched_shards: [ + .shards[] | { + shard_id, + txs: [.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash], + receipts: [.chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id] + } | select((.txs | length) + (.receipts | length) > 0) + ] + }' ``` -Читайте `touched: false` как полный и однозначный ответ для тихого блока. При `true` поля перехода (`sample_tx_hash`, `sample_receipt_id`) сразу ведут вас в [/tx/examples](/tx/examples) за человекочитаемой историей. Один запрос заменяет ручной просмотр chunks — и учтите: `touched: true` с `state_changes: 0` — это реальная форма: receipt может попасть в chunk, не вызвав в том же блоке мутации состояния. +`touched_shards: []` — полный ответ для тихого блока. Непустой список называет shards, где контракт появился, и отдаёт конкретные `tx`-хеши или `receipt_id` — передавайте хеш в [Transactions API](/tx), когда нужна человекочитаемая история. Receipts без парных `txs` — это нормально: cross-contract-вызов приходит как incoming receipt в этом блоке, даже если исходная транзакция была раньше. -### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? +### Увидел ли я активность в optimistic-режиме, и догнала ли её finality? -Optimistic-блоки живут по адресу `/v0/block_opt/{height}`; как только finality догоняет (обычно в пределах одного блока, ~1 с на mainnet), та же высота становится доступна и по `/v0/block/{height}`. Прогоните помощник на обеих и сравните. +Optimistic-блоки ходят по `/v0/block_opt/{height}` примерно на секунду впереди `/v0/block/{height}`. Цикл мониторинга может действовать по optimistic-сигналу и ожидать, что тот же ответ придёт на финализированный эндпоинт через один блок — если только стресс сети не расширит разрыв, и тогда финализированный fetch вернёт `null`, а вы подождёте. ```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + +count_touches() { + jq --arg contract "$1" ' + [.shards[] + | ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length) + + ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length)] + | add // 0' +} + OPT_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" OPT_HEIGHT="${OPT_LOCATION##*/}" -echo "Optimistic view at $OPT_HEIGHT:" -curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | contract_touch_summary "$TARGET_CONTRACT" - -echo "Finalized view at $OPT_HEIGHT:" +echo "optimistic @ $OPT_HEIGHT: $(curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | count_touches "$TARGET_CONTRACT") touches" FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" -if [ "$(echo "$FINAL" | jq 'type')" = '"null"' ]; then - echo "finality has not caught up to $OPT_HEIGHT yet" +if [ "$(printf '%s' "$FINAL" | jq 'type')" = '"null"' ]; then + echo "finalized @ $OPT_HEIGHT: not caught up yet" else - echo "$FINAL" | contract_touch_summary "$TARGET_CONTRACT" + echo "finalized @ $OPT_HEIGHT: $(printf '%s' "$FINAL" | count_touches "$TARGET_CONTRACT") touches" fi ``` -На здоровой сети обе сводки совпадают сразу; ценность — в самом шаблоне, а не в драматичной разнице. Цикл мониторинга, который реагирует на optimistic-сигнал, знает: тот же ответ — на один блок от надёжного. Ветку `finality has not caught up` используйте, когда действительно нужно отличить «увидено optimistically» от «подтверждено» — во время стресса сети этот разрыв расширяется. +На здоровом mainnet оба счётчика совпадают в пределах секунды. Ценность — в самом шаблоне: optimistic-поток даёт ответ, на который можно реагировать сразу, а финализированный приходит через блок как надёжное подтверждение. -### Какой shard действительно изменил мой контракт в этом блоке? +### Какой shard действительно изменил состояние моего контракта? -Блоки тонкие — в большинстве финализированных блоков нет мутаций состояния ни для одного конкретного контракта. Идите назад от финализированной головы, пока помощник не покажет `state_changes > 0`, затем откройте «победивший» shard через `/v0/block/{height}/shard/{shard_id}` ради самого payload мутации. +В большинстве финализированных блоков нет мутации состояния ни для одного конкретного контракта — активность разрежена и привязана к shard. Идите назад от финализированной головы, пока состояние контракта реально не изменится, и откройте этот shard для payload мутации. Вызов уровня блока говорит, *на каком* shard это случилось; вызов уровня shard — *как*. ```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" -TARGET_HEIGHT="" -WINNING_SHARD="" +FOUND_HEIGHT="" +FOUND_SHARD="" -for OFFSET in 0 1 2 3 4 5 6 7 8 9; do +for OFFSET in $(seq 0 15); do H=$((HEAD - OFFSET)) - SUMMARY="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" | contract_touch_summary "$TARGET_CONTRACT")" - if [ "$(echo "$SUMMARY" | jq '.evidence.state_changes')" -gt 0 ]; then - TARGET_HEIGHT=$H - WINNING_SHARD="$(echo "$SUMMARY" | jq -r '.shards[0]')" - echo "$SUMMARY" + SHARD="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" \ + | jq -r --arg contract "$TARGET_CONTRACT" ' + .shards[] + | select([.state_changes[]? | select(.change.account_id? == $contract)] | length > 0) + | .shard_id + ' | head -1)" + if [ -n "$SHARD" ]; then + FOUND_HEIGHT=$H; FOUND_SHARD=$SHARD break fi done -curl -s "$NEARDATA_BASE_URL/v0/block/$TARGET_HEIGHT/shard/$WINNING_SHARD" \ - | jq --arg contract "$TARGET_CONTRACT" '{ - shard_id, - chunk_hash: .chunk.header.chunk_hash, - matching_state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause_type: (.cause | keys[0]), account_id: .change.account_id}][0:3], - matching_execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0]), predecessor_id: .receipt.predecessor_id}][0:3] - }' +if [ -z "$FOUND_HEIGHT" ]; then + echo "no state mutation for $TARGET_CONTRACT in the last 16 finalized blocks" +else + curl -s "$NEARDATA_BASE_URL/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + | jq --arg contract "$TARGET_CONTRACT" --argjson height "$FOUND_HEIGHT" --argjson shard_id "$FOUND_SHARD" '{ + height: $height, + shard_id: $shard_id, + state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause: (.cause | keys[0])}][0:3], + execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0])}][0:3] + }' +fi ``` -На mainnet `intents.near` стабильно выполняется на shard 7, поэтому обход назад обычно попадает в цель за несколько блоков. Payload shard затем называет конкретные типы state-change (`account_update`, `data_update` и т. п.) и результаты исполнения receipt, которые их породили, — shard-локальное доказательство без догадок. Для менее активных контрактов расширьте диапазон `OFFSET`. +На mainnet `intents.near` живёт на shard 7, поэтому обход назад обычно попадает в цель за несколько блоков. Payload shard называет конкретные типы state-change (`account_update`, `data_update` и т. п.) и результаты receipt, которые их породили, — shard-локальное доказательство без догадок. Для менее активных контрактов расширьте диапазон `OFFSET`. ## Когда расширить поверхность -- Используйте [Transactions API](/tx), когда у вас уже есть `tx_hash` и нужен человекочитаемый рассказ о транзакции. +- Используйте [Transactions API](/tx), когда у вас уже есть `tx_hash` и нужна человекочитаемая история транзакции. - Используйте [RPC Reference](/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. - Используйте [Block Headers](/neardata/block-headers), когда нужна только динамика head/finality, а не проверка contract-touch. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index 0792e6b..70a2cb1 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -12,6 +12,27 @@ page_actions: Начинайте с RPC-метода, который отвечает на вопрос. Используйте `tx`, чтобы отследить включение и финальность по хешу транзакции, и расширяйте поверхность только когда нужны дерево receipts, сырой state или трассировка на уровне shard. +## Состояние аккаунта + +### Показать баланс и storage аккаунта на finality + +`view_account` — канонический RPC-запрос для текущего состояния аккаунта. Один вызов возвращает свободный баланс, сумму, заблокированную в валидаторском стейке или lockup-контракте, использованное storage и блок, на котором было сделано чтение. `finality: "final"` гарантирует, что вы читаете стабильное состояние, а не optimistic-представление. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_account",account_id:$account_id,finality:"final"} + }')" \ + | jq '.result | {amount, locked, storage_usage, block_height, block_hash}' +``` + +Для `mike.near` это возвращает `amount` (yoctoNEAR в свободной части), `locked: "0"` (ничего в валидаторском стейке или lockup-контракте) и `storage_usage: 558441` — 558 КБ on-chain-состояния. Пара `block_height`/`block_hash` фиксирует точку чтения; чтобы прочитать несколько аккаунтов *на одном и том же* блоке, переиспользуйте возвращённый `block_hash` как `block_id` в последующих запросах. + ## Включение транзакции и финальность ### Отследить транзакцию от хеша до финальности @@ -48,7 +69,7 @@ curl -s "$RPC_URL" \ ### Описать первый action первой транзакции на текущем tip -Пройдите `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. +Блок NEAR — это header поверх N shard chunks, а не плоский список транзакций. `block` возвращает headers chunks; сами транзакции лежат уровнем ниже, внутри `chunk`. Шортката `block → tx` нет — блок не несёт хешей транзакций, поэтому `tx` (которому нужен hash) в этой цепочке не участвует. Канонический проход — `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. ```bash RPC_URL=https://rpc.mainnet.fastnear.com @@ -94,9 +115,16 @@ fi ## Механика аккаунтов и ключей -### Аудит старых function-call-ключей Near Social +### Определить function-call-ключи, которые стоит удалить + +Каждый кошелёк, шлюз и dapp-сессия, в которую вы заходите, обычно оставляет за собой function-call-ключ. Большинством из них вы больше никогда не воспользуетесь. `view_access_key_list` возвращает все ключи аккаунта; структура nonce показывает, какие из них устарели. -У создателей накапливаются Social function-call-ключи от каждого кошелька и каждого BOS-шлюза, которым они пользовались. `view_access_key_list` возвращает их все; один фильтр сужает до `social.near`, а **младшие шесть цифр nonce** заодно служат счётчиком использования — новые ключи стартуют с `block_height * 10^6` и инкрементируются на единицу за каждую транзакцию. +Новые ключи стартуют с `block_height * 10^6`, и значение инкрементируется на единицу за каждую транзакцию, которую ключ подписывает, поэтому: + +- `nonce / 10^6` → блок, в котором ключ был добавлен +- `nonce % 10^6` → сколько раз ключ был использован + +Любой ключ с `tx_count: 0` был создан и ни разу не использовался — самый очевидный кандидат на очистку. Следующий по порядку — ключи, заскоупленные на контракт, с которым вы больше не работаете. Фильтр ниже сужает до `social.near`, но чтобы аудитировать другой контракт, меняется только строка `RECEIVER_ID`. ```bash RPC_URL=https://rpc.mainnet.fastnear.com @@ -112,13 +140,13 @@ curl -s "$RPC_URL" \ | jq --arg receiver "$RECEIVER_ID" ' { total_keys: (.result.keys | length), - social_fcks: [ + fcks_for_receiver: [ .result.keys[] | select((.access_key.permission | type) == "object") | select(.access_key.permission.FunctionCall.receiver_id == $receiver) | { public_key, - created_near_block: (.access_key.nonce / 1000000 | floor), + added_at_block: (.access_key.nonce / 1000000 | floor), tx_count: (.access_key.nonce % 1000000), method_names: (.access_key.permission.FunctionCall.method_names | if . == [] then "ANY" else . end), allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") @@ -127,119 +155,17 @@ curl -s "$RPC_URL" \ }' ``` -Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на очистку. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность конкретного dapp. - -Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом — function-call-ключ не может авторизовать `DeleteKey` — и отправьте через [`send_tx`](/rpc/transaction/send-tx). Повторный запуск того же списка подтверждает удаление. Само подписание — стандартная near-api-js-история и не самая интересная часть аудита. - -### Какая транзакция добавила этот `social.near` function-call-ключ и кто её авторизовал? +Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на удаление. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность одного dapp. -Тот же nonce, что считает использование, заодно якорит `AddKey` во времени блоков: новые ключи стартуют примерно с `block_height * 10^6`, так что деление текущего nonce на миллион даёт плотное окно поиска. Один раз гидратируйте кандидатов — и ответ уже несёт достаточно, чтобы отличить прямой `AddKey` от делегированной (meta-tx) авторизации, то есть показать, *какой ключ подписал решение*, а не только какой аккаунт оплатил gas. - -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=mike.near -TARGET_PUBLIC_KEY=ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs - -CURRENT_NONCE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg public_key "$TARGET_PUBLIC_KEY" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"view_access_key",account_id:$account_id,public_key:$public_key,finality:"final"} - }')" \ - | jq -r '.result.nonce')" - -ADD_KEY_BLOCK=$((CURRENT_NONCE / 1000000)) - -TX_HASHES="$(curl -s "$TX_BASE_URL/v0/account" -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" \ - --argjson from $((ADD_KEY_BLOCK - 20)) --argjson to $((ADD_KEY_BLOCK + 5)) '{ - account_id: $account_id, is_real_signer: true, - from_tx_block_height: $from, to_tx_block_height: $to, desc: false, limit: 50 - }')" \ - | jq -c '[.account_txs[].transaction_hash]')" - -curl -s "$TX_BASE_URL/v0/transactions" -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES" '{tx_hashes: $tx_hashes}')" \ - | jq --arg target "$TARGET_PUBLIC_KEY" ' - [ .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? | .AddKey? | select(.public_key == $target) - | {mode: "direct", authorizing_public_key: $tx.transaction.public_key, permission: .access_key.permission}), - ($tx.transaction.actions[]? | .Delegate? | .delegate_action as $d - | $d.actions[]? | .AddKey? | select(.public_key == $target) - | {mode: "delegated", authorizing_public_key: $d.public_key, permission: .access_key.permission}) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - signer_id: $tx.transaction.signer_id, - receiver_id: $tx.transaction.receiver_id, - add_key_receipt: ([$tx.receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]?; .AddKey.public_key? == $target)) - | {receipt_id: .receipt.receipt_id, receipt_block: .execution_outcome.block_height}][0]) - } + . - ]' -``` - -Для ключа `ed25519:7GZg…` аккаунта `mike.near` (первый `social.near` FCK из аудита выше) это разрешается в транзакцию `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` на внешнем блоке tx `112057390`. Внешний signer — `app.herewallet.near`, это relayer HERE Wallet, и `mode: "delegated"` рассказывает остальную историю: relayer оплатил gas, но *авторизующий* ключ внутри Delegate — `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu`, full-access-ключ `mike.near`, который подписал сам `AddKey`. Это та разница meta-tx, которую верхнеуровневый `signer_id` в одиночку скрыл бы. - -`add_key_receipt` замыкает картину: `AddKey` выполнился в блоке `112057392`, через два блока после внешней tx, потому что Delegate прыгает из shard relayer в shard целевого аккаунта. Расширьте окно `-20/+5`, если ключом с момента создания пользовались активно. - -### Зарегистрировать FT-хранилище при необходимости и затем перевести токены - -Токены NEP-141 требуют, чтобы каждый получатель предварительно зарегистрировал storage на контракте, прежде чем сможет держать баланс. Два view-вызова авторитетно отвечают на вопрос регистрации *до* отправки — пропуск этой проверки и есть причина, по которой `ft_transfer` в итоге тихо возвращается отправителю. - -```bash -RPC_URL=https://rpc.testnet.fastnear.com -TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -RECEIVER_ACCOUNT_ID=mike.testnet - -ACCOUNT_ARGS_B64="$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" - -REGISTERED="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_of",args_base64:$args,finality:"final"} - }')" \ - | jq '(.result.result | implode | fromjson) != null')" - -MIN_DEPOSIT="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_bounds",args_base64:"e30=",finality:"final"} - }')" \ - | jq -r '.result.result | implode | fromjson | .min')" - -jq -n --argjson registered "$REGISTERED" --arg min "$MIN_DEPOSIT" '{ - registered: $registered, - min_storage_deposit_yocto: $min -}' -``` - -Для зафиксированного testnet-контракта `storage_balance_of({account_id: "mike.testnet"})` возвращает `null` (не зарегистрирован), а `storage_balance_bounds` возвращает `{min: "1250000000000000000000", max: "1250000000000000000000"}` — плоскую комиссию регистрации 0.00125 NEAR. Это собственный ответ контракта, и большего на read-стороне до записи не нужно. - -Write-сторона — это две подписанных function call (near-api-js `transactions.functionCall` или любая NEAR-библиотека подписи работает одинаково): - -- `storage_deposit({account_id: "", registration_only: true})` с депозитом `` yocto и 100 Tgas — пропустите, если `registered: true`. -- `ft_transfer({receiver_id: "", amount: "", memo: "..."})` с депозитом 1 yocto (требует NEP-141) и 100 Tgas. - -Отправьте каждую подписанную транзакцию через [`send_tx`](/rpc/transaction/send-tx) с `wait_until: "FINAL"`. После этого подтвердите через собственный view-метод контракта — индексированная история не нужна, чтобы доказать, что перевод закрепился: - -```bash -curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"ft_balance_of",args_base64:$args,finality:"final"} - }')" \ - | jq '{receiver_balance: (.result.result | implode | fromjson)}' -``` +Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом (function-call-ключ не может авторизовать `DeleteKey`) и отправьте через [`send_tx`](/rpc/transaction/send-tx). Повторный запуск того же запроса подтвердит, что ключа больше нет. ## Чтение контрактов и сырой state -### Как прочитать сырое storage контракта напрямую? +### Прочитать storage контракта, не запуская его + +View-метод вроде `get_num` всё равно заставляет узел загрузить wasm-контракта и выполнить его. Если ключ storage уже известен, `view_state` возвращает сырые сериализованные байты напрямую — без исполнения и без зависимости от того, выставил ли контракт getter для этого поля вообще. -Два RPC-метода отвечают на один и тот же вопрос о counter с разных слоёв: `view_state` достаёт сырые байты trie без запуска кода, а `call_function` запускает собственный view-метод контракта. Когда они совпадают, вы доказали, что view-метод контракта соответствует его сохранённому состоянию. +Контракты на `near-sdk-rs` хранят верхнеуровневую `#[near_bindgen]`-структуру под ключом `STATE`. Передайте `STATE` как `prefix_base64` (`U1RBVEU=` — это base64 тех же четырёх ASCII-байт), и узел вернёт сериализованное значение. ```bash RPC_URL=https://rpc.testnet.fastnear.com @@ -252,26 +178,14 @@ RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ }')" \ | jq -r '.result.values[0].value')" -RAW_I8="$(python3 -c "import base64,sys;print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" +DECODED_I8="$(python3 -c "import base64; print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" -METHOD_VALUE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"get_num",args_base64:"e30=",finality:"final"} - }')" \ - | jq -r '.result.result | implode | fromjson')" - -jq -n --arg raw_b64 "$RAW_B64" --argjson raw_i8 "$RAW_I8" --argjson method "$METHOD_VALUE" '{ - raw_state_b64: $raw_b64, - raw_state_decoded: $raw_i8, - view_method_value: $method, - agree: ($raw_i8 == $method) -}' +jq -n --arg raw "$RAW_B64" --argjson val "$DECODED_I8" '{raw_bytes_base64: $raw, decoded_i8: $val}' ``` -Для живого counter `view_state` по ключу `STATE` (base64 `U1RBVEU=`) возвращает `"CQ=="` — один байт `0x09`, декодируется как signed i8 в `9`; `get_num` тоже возвращает `9`. Они совпадают, потому что контракт хранит `val: i8` по этому ключу. `signed=True` важен: отрицательный counter выглядел бы как `"/w=="` (байт `0xff` → i8 `-1`, а не u8 `255`). +Для живого counter это возвращает `"CQ=="` — один байт `0x09`, декодируется как signed i8 в `9`. Это то же число, которое вернул бы `get_num`, только прочитанное прямо из trie без запуска кода контракта. `signed=True` важен: отрицательный counter сериализовался бы как `"/w=="` (байт `0xff` → i8 `-1`, а не u8 `255`). -`view_state` — правильный инструмент, когда у контракта нет view-метода для нужных данных, когда нужно сверить view-метод с реальным storage или когда нужна семья ключей, которую контракт не раскрывает публично. Для всего остального `call_function` требует меньше церемоний. Если следующий вопрос становится историческим, а не текущим, расширяйте поверхность до [KV FastData API](/fastdata/kv). +Тянитесь к `view_state`, когда контракт не выставляет view-метод для нужных данных или когда нужна семья ключей, которую контракт не публикует. Для большинства чтений `call_function` всё равно требует меньше церемоний. Если вопрос становится историческим, а не текущим, расширяйте поверхность до [KV FastData API](/fastdata/kv). ## NEAR Social и точные чтения BOS diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md index 9d368a3..552c152 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -8,7 +8,32 @@ page_actions: - markdown --- -## Пример +## Примеры + +### Какая у этого аккаунта свежая активность по переводам? + +`/v0/transfers` всего с `account_id` и `desc: true` возвращает самые свежие переводы, касающиеся этого аккаунта, по всем типам активов, в обоих направлениях сразу. В каждой строке уже есть `human_amount`, `asset_id` и `transaction_id`, так что этот поток заодно служит быстрым сканом активности до того, как вы достанете фильтры. + +```bash +TRANSFERS_BASE_URL=https://transfers.main.fastnear.com +ACCOUNT_ID=root.near + +curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id: $account_id, desc: true, limit: 5}')" \ + | jq '{ + recent: [.transfers[] | { + block_height, + asset_id, + human_amount, + other_account_id, + transfer_type, + tx: .transaction_id + }] + }' +``` + +Для `root.near` последние строки смешивают активы `FtTransfer` и `MtTransfer`. `asset_id` использует URI по NEP-стандартам (`native:near`, `nep141:...`, `nep245:...`), так что одно поле уже подсказывает, к какому стандарту тянуться дальше. Положительный `human_amount` означает, что аккаунт получил; отрицательный — что отправил. `other_account_id: null` — норма для multi-token-форм, где контрагент сидит внутри границы контракта, а не как отдельный аккаунт верхнего уровня. ### Отфильтровать и листать ленту переводов одного аккаунта diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index 9626c2f..ad43fd4 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -140,48 +140,9 @@ curl -s "$RPC_URL" \ `UNKNOWN_ACCOUNT` — это и есть доказательство. Если бы `CreateAccount` закрепился, `view_account` вернул бы результат; раз нет — предыдущие `Transfer` и `AddKey` из того же batched-receipt тоже не закрепились. -### Почему этот вызов контракта выглядел успешным, но потом receipt упал? +### Когда транзакция выглядит успешной — что на самом деле произошло? -Одна транзакция может закончиться тем, что внешний handoff рапортует `SuccessReceiptId`, а дочерний receipt при этом тихо падает — это и есть async-модель NEAR, и `/v0/transactions` выдаёт весь timeline за один запрос. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - tx_handoff: .transactions[0].execution_outcome.outcome.status, - outer_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, - descendant_failures: [ - .transactions[0].receipts[] - | select(.execution_outcome.outcome.status.Failure != null) - | { - receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - block_height: .execution_outcome.block_height, - failure: .execution_outcome.outcome.status.Failure - } - ], - receipt_timeline: [ - .transactions[0].receipts[] - | { - receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - status_class: (.execution_outcome.outcome.status | keys[0]) - } - ] - }' -``` - -Для зафиксированной транзакции mainnet `tx_handoff` — `SuccessReceiptId`: транзакция чисто запустила свой первый receipt. Если смотреть только сюда, можно назвать это победой. `descendant_failures` рассказывают вторую историю: `ft_on_transfer` на `v2.ref-finance.near` упал с `E51: contract paused` — DEX был на паузе во время этого свопа и не мог принять wrapped NEAR. А `receipt_timeline` показывает, как история разрешилась: callback `ft_resolve_transfer` на `wrap.near` всё равно отработал и вывел лог `Refund`, вернув wrapped NEAR отправителю. - -Успех receipt не транзитивен. Протокол может чисто отдать handoff и при этом увидеть, как отцеплённая работа провалится позже. Если ваше приложение «выглядело успешным», но деньги всё равно вернулись, пройдите этот же timeline — разделение видно на индексированном ответе без отдельного RPC status-запроса. Чтобы отдельно проверить, что ваш callback отработал, см. [Отработал ли мой callback?](#отработал-ли-мой-callback). - -### Отработал ли мой callback? - -Кросс-контрактные вызовы NEAR возвращаются через callback-receipt на исходном контракте. Отработал ли этот callback — это одна строка с `any(...)` против индексированного списка receipts; а полная история refund выпадает из того же ответа. +Внешний `execution_outcome.outcome.status` рапортует `SuccessReceiptId`, как только сработал handoff первого receipt, — и ничего не говорит о том, успешны ли дочерние receipts и отработал ли callback на исходном контракте. Один pipeline над `/v0/transactions` отвечает сразу на все три вопроса. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -193,30 +154,38 @@ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ - top_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, - callback_ran: any( - .transactions[0].receipts[]; - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback - ), - receipt_chain: [ + outer: { + method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + tx_handoff: (.transactions[0].execution_outcome.outcome.status | keys[0]) + }, + callback: { + expected_on: $origin, + method: $callback, + ran: any( + .transactions[0].receipts[]; + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback + ) + }, + descendant_failures: [ .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) | { receiver_id: .receipt.receiver_id, method: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - block: .execution_outcome.block_height, - status: (.execution_outcome.outcome.status | keys[0]), - logs: .execution_outcome.outcome.logs + cause: .execution_outcome.outcome.status.Failure } ] }' ``` -Для зафиксированной транзакции `ft_transfer_call` на `wrap.near` передаёт управление в `ft_on_transfer` на `v2.ref-finance.near`, который **падает**. Callback `ft_resolve_transfer` всё равно выполняется на `wrap.near` и логирует `Refund 7278020378457059679767103 from v2.ref-finance.near to …` обратно отправителю — поэтому `callback_ran: true`, несмотря на сбой дочернего receipt. Сбой ниже по цепочке не мешает исходному контракту увидеть свой callback; так async-обработка ошибок NEAR остаётся восстанавливаемой. Строки с `method: "system"` — это рантайм-возвраты газа, а не логика контракта. Чтобы привязать один из этих логов к породившему его receipt, см. [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). +Для зафиксированной транзакции `outer.method` — `ft_transfer_call`, а `outer.tx_handoff` — `SuccessReceiptId`: транзакция чисто запустила свой первый receipt, и если смотреть только сюда, можно назвать это победой. `descendant_failures` рассказывают вторую историю: `ft_on_transfer` на `v2.ref-finance.near` упал с `E51: contract paused` — DEX был на паузе во время этого свопа и не мог принять wrapped NEAR. `callback.ran: true` — третью: callback `ft_resolve_transfer` на `wrap.near` всё равно отработал. Сбой ниже по цепочке никогда не мешает callback исходного контракта — именно так NEP-141 возвращает отправителю средства, когда получатель их отклонил. + +Успех receipt не транзитивен. Протокол может чисто отдать handoff и при этом увидеть, как отцеплённая работа провалится позже; callback исходного контракта отработает в любом случае. Прочитайте эти три поля вместе — и async-история становится читаемой без ручного обхода цепочки receipts. Чтобы вытянуть сам лог `Refund`, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). ### Сопоставить запрос OutLayer с его TEE-разрешением -[OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь вызывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, и позже `worker.outlayer.near` вызывает `submit_execution_output_and_resolve` с результатом. Один batch на `/v0/transactions` с обоими хешами возвращает всё сопоставление — метод, корреляционные идентификаторы и TEE-отпечаток — без отдельного worker-запроса. +[OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь подписывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, затем `worker.outlayer.near` присылает результат через `submit_execution_output_and_resolve`. Обе половины несут один и тот же `request_id` — передайте оба tx-хеша в один запрос `/v0/transactions` и извлеките это поле с каждой стороны, чтобы подтвердить пару. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -229,30 +198,25 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | jq '[ .transactions[] | { + role: (if .transaction.actions[0].FunctionCall.method_name == "request_execution" + then "request" else "worker" end), hash: .transaction.hash, signer: .transaction.signer_id, method: .transaction.actions[0].FunctionCall.method_name, block: .execution_outcome.block_height, - evidence: ( + request_id: ( if .transaction.actions[0].FunctionCall.method_name == "request_execution" - then (.receipts[0].execution_outcome.outcome.logs - | map(select(startswith("EVENT_JSON"))) | .[0] - | sub("EVENT_JSON:"; "") | fromjson | .data[0] as $e - | ($e.request_data | fromjson) as $r - | {request_id: $r.request_id, sender_id: $r.sender_id, project_id: $r.project_id, - data_id: $e.data_id, code_hash: $r.code_source.WasmUrl.hash, - input_preview: ($r.input_data | .[0:80] + "…")}) + then (.receipts[0].execution_outcome.outcome.logs[] | select(startswith("EVENT_JSON")) + | sub("EVENT_JSON:"; "") | fromjson | .data[0].request_data | fromjson | .request_id) else (.receipts[0].receipt.receipt.Action.actions[0].FunctionCall.args - | @base64d | fromjson - | {request_id, success: .output.Json.success, encrypted_bytes: (.output.Json.encrypted_data | length), - instructions: .resources_used.instructions, time_ms: .resources_used.time_ms}) + | @base64d | fromjson | .request_id) end ) } ]' ``` -Обе строки несут `request_id: 1868`. Half-запрос, подписанный `retrorn.near` в блоке `194832281`, вызывает проект `zavodil.near/near-email` с действием `delete_email` и 32-байтным `data_id` — это идентификатор payload yield/resume NEAR, который контракт использует внутри, чтобы приостановить on-chain-обещание, пока worker выполняется. Half-worker попадает через 11 блоков и рапортует `success: true`, `instructions: 53075053`, `time_ms: 2401` и 21 568-байтный зашифрованный результат; эти цифры `resources_used` — тот самый TEE-отпечаток, который можно сверить с лимитами из запроса. `/v0/transactions` отдаёт исторические пары бессрочно, поэтому archival RPC для самой трассировки не нужен — тянитесь к [archival RPC](https://archival-rpc.mainnet.fastnear.com) только когда нужно сверять состояние контракта на высоте блока запроса. +Обе строки несут `request_id: 1868`, подтверждая пару. Половина-запрос, подписанная `retrorn.near` в блоке `194832281`, лежит в логе `EVENT_JSON:` её receipt (это yield/resume-паттерн NEAR — on-chain-обещание приостанавливается, пока TDX-worker выполняется). Половина-worker приходит через 11 блоков с `submit_execution_output_and_resolve`, подписанной `worker.outlayer.near`, и её `request_id` достаётся прямо из base64-обёрнутых `FunctionCall.args`. Те же два payload несут и более богатый отпечаток — `sender_id`, `project_id`, `code_hash`, `resources_used.instructions`, `resources_used.time_ms`, размер зашифрованного результата в байтах — если нужно проверить, что именно исполнилось; этот минимальный pipeline лишь подтверждает, что половины принадлежат друг другу. `/v0/transactions` отдаёт исторические пары бессрочно, поэтому archival RPC для самой трассировки не нужен даже через недели. ## Частые ошибки From cecce9f2c39944cf632b2e5a6a9ddfeb8c05386c Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Mon, 20 Apr 2026 10:45:36 -0700 Subject: [PATCH 30/35] Refresh example docs curl commands --- docs/agents/auth-for-agents.mdx | 4 +- docs/agents/index.mdx | 8 ++- docs/api/examples.md | 52 ++++++++++-------- docs/auth/index.mdx | 16 +++--- docs/fastdata/kv/examples.md | 8 +-- docs/fastdata/kv/index.md | 5 +- docs/neardata/examples.md | 31 ++++++----- docs/rpc/examples.md | 53 ++++++++++--------- docs/transaction-flow/finality.mdx | 5 +- docs/transaction-flow/reference.mdx | 17 +++++- docs/transaction-flow/runtime-execution.mdx | 5 +- docs/transfers/examples.md | 14 ++--- docs/tx/berry-club.mdx | 8 +++ docs/tx/examples.md | 42 ++++++++------- docs/tx/socialdb-proofs.mdx | 35 ++++++------ .../current/agents/auth-for-agents.mdx | 4 +- .../current/agents/index.mdx | 8 ++- .../current/api/examples.md | 52 ++++++++++-------- .../current/auth/index.mdx | 8 ++- .../current/fastdata/kv/examples.md | 8 +-- .../current/fastdata/kv/index.md | 5 +- .../current/neardata/examples.md | 31 ++++++----- .../current/rpc/examples.md | 53 ++++++++++--------- .../current/transaction-flow/finality.mdx | 5 +- .../current/transaction-flow/reference.mdx | 17 +++++- .../transaction-flow/runtime-execution.mdx | 5 +- .../current/transfers/examples.md | 14 ++--- .../current/tx/berry-club.mdx | 8 +++ .../current/tx/examples.md | 42 ++++++++------- .../current/tx/socialdb-proofs.mdx | 35 ++++++------ 30 files changed, 366 insertions(+), 232 deletions(-) diff --git a/docs/agents/auth-for-agents.mdx b/docs/agents/auth-for-agents.mdx index 131b552..6089a97 100644 --- a/docs/agents/auth-for-agents.mdx +++ b/docs/agents/auth-for-agents.mdx @@ -36,8 +36,8 @@ Avoid browser-only agent architectures that need the FastNear key in client-side | 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. | +| `Authorization: Bearer ${FASTNEAR_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=${FASTNEAR_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. diff --git a/docs/agents/index.mdx b/docs/agents/index.mdx index 3ae2cf4..2f08a8a 100644 --- a/docs/agents/index.mdx +++ b/docs/agents/index.mdx @@ -101,14 +101,18 @@ Bad pattern: 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" +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer ${API_KEY}" \ + -H "Authorization: Bearer $FASTNEAR_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}" +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + +curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" ``` Get a key from [dashboard.fastnear.com](https://dashboard.fastnear.com). Operational posture for non-interactive runtimes: [Auth for Agents](/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](/auth). diff --git a/docs/api/examples.md b/docs/api/examples.md index 3cea43e..29b6228 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -15,10 +15,11 @@ page_actions: `/v1/account/{id}/full` is the FastNear API's account aggregator — one call bundles the account's NEAR state, every FT contract it's touched, every NFT collection it's received, and every validator pool it's delegated to. When you already have the `account_id`, this is the fastest "what does this account look like?" read. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq '{ account_id, near_balance_yocto: .state.balance, @@ -28,23 +29,25 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -For `mike.near`: 40 FT contracts in the list, 40 NFT collections, 5 staking pools. The contract counts alone tell you this is an active mainnet account. Every example below drills into one of those surfaces — start here when all you have is the account ID. +For `root.near`: 150 FT contracts in the list, 102 NFT collections, 2 staking pools. The contract counts alone tell you this is a busy mainnet account. Every example below drills into one of those surfaces — start here when all you have is the account ID. ### Resolve a public key, then fetch the account snapshot Look up which account a key belongs to, then read that account's holdings in one call. ```bash -API_BASE_URL=https://api.fastnear.com PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -LOOKUP="$(curl -s "$API_BASE_URL/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')")" +LOOKUP="$(curl -s "https://api.fastnear.com/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY")" echo "$LOOKUP" | jq '{matched: (.account_ids | length), account_ids}' ACCOUNT_ID="$(echo "$LOOKUP" | jq -r '.account_ids[0]')" -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq '{account_id, state, tokens: (.tokens|length), nfts: (.nfts|length), pools: (.pools|length)}' ``` @@ -55,10 +58,11 @@ If `matched` is greater than 1, switch to [V1 Public Key Lookup All](/api/v1/pub NEAR account state has three buckets that wallet UIs tend to conflate: `balance` is the unstaked amount, `locked` is NEAR tied up in validator stake or a lockup contract, and `storage_bytes` implies a separate amount pinned to the trie at the current rate of 10^19 yoctoNEAR per byte. One pipeline over `/full` breaks them apart. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq ' (.state.balance | tonumber) as $amount | (.state.locked | tonumber) as $locked @@ -76,7 +80,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -For `mike.near`: ~2613.49 NEAR total, all unstaked, ~5.58 NEAR pinned to 558 KB of on-chain state, and ~2607.91 NEAR spendable. New accounts feel this most acutely — a fresh named account of ~182 bytes has ~0.00182 NEAR stuck to storage, which is why CLI tools refuse to let you send an account's full balance. +For `root.near`: ~3914.67 NEAR total, all unstaked, ~0.28677 NEAR pinned to 28,677 bytes of on-chain state, and ~3914.38 NEAR spendable. New accounts feel this most acutely — a fresh named account of ~182 bytes has ~0.00182 NEAR stuck to storage, which is why CLI tools refuse to let you send an account's full balance. Point the same pipeline at a validator pool like `astro-stakers.poolv1.near` and the numbers invert: ~730 K unstaked, ~27.68 M in `locked`. That `locked` is the pool's own protocol-level validator stake, not the delegators' funds (those are tracked inside the pool contract's state). The same field means different things on different account types. @@ -87,10 +91,11 @@ jq uses IEEE-754 doubles, so the NEAR values above are display-precision only Every entry under `/full`'s `tokens`, `nfts`, and `pools` arrays carries its own `last_update_block_height` — the block at which the indexer last saw that row change for this account. Taking the max across all three arrays gives a cheap "latest activity" signal without touching the transaction API. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq ' [ (.tokens // [])[].last_update_block_height, @@ -107,7 +112,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -For `mike.near` against the current tip, this returns 85 total entries across FT, NFT, and pool contracts, 34 with a tracked block, and a most-recent block of `194711866` — about 125 K blocks back, or roughly 35 hours at NEAR's ~1 block/sec tempo. For `root.near`: 254 entries, 158 tracked. +For `root.near`, this returns 254 total entries across FT, NFT, and pool contracts, 158 with a tracked block, and a most-recent block of `194301659`. That's enough to tell you the wallet is still active without touching transaction history. This is the right question for "is this wallet abandoned?" or "has anything moved since block X?" — cheap, one call, no transaction history needed. For the transaction that caused the latest change, widen to the [Transactions API](/tx). Entries with `last_update_block_height: null` predate the indexer's per-row tracking (typically older airdrops) and are ignored here rather than counted as recent. @@ -116,11 +121,12 @@ This is the right question for "is this wallet abandoned?" or "has anything move NEAR account names encode a hierarchy: `mint.sharddog.near` is a subaccount of `sharddog.near`, which is a subaccount of `near`. Publishers that ship multiple NFT collections usually deploy each one as its own subaccount, so a single suffix filter over the account's NFT list recovers everything under one publisher tree — no external collection registry required. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near PUBLISHER=sharddog.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq --arg publisher "$PUBLISHER" ' ("." + $publisher) as $suffix | { @@ -138,7 +144,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ }' ``` -For `mike.near` and `sharddog.near`, this returns four subaccount contracts: `comic`, `mintv2`, `mint`, and `claim`. The two with a non-null `last_update_block_height` (`mint` at `115715361` and `claim` at `119718026`) are where the wallet's position actually changed. The other two are dormant — common for drop-era contracts an account received into but never interacted with again. +For `root.near` and `sharddog.near`, this returns four subaccount contracts: `ndcconstellationnft`, `mint`, `harvestmoon`, and `claim`. Only `claim` carries a non-null `last_update_block_height` (`131402024`), so that's the one contract where the wallet's position clearly changed. The others are dormant — common for drop-era contracts an account received into but never interacted with again. Swap `PUBLISHER` to any account to scope the filter to a different publisher tree. @@ -147,12 +153,14 @@ Swap `PUBLISHER` to any account to scope the filter to a different publisher tre Direct pool positions live on `/staking`; liquid staking tokens (stNEAR, LiNEAR, etc.) sit on `/ft` like any other FT. Read both, classify the wallet — `root.near` shows up as `mixed`. ```bash -API_BASE_URL=https://api.fastnear.com ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -STAKING="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking")" -FT="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft")" +STAKING="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/staking" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY")" +FT="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/ft" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY")" jq -n \ --argjson staking "$STAKING" \ diff --git a/docs/auth/index.mdx b/docs/auth/index.mdx index a198b3b..2c54abf 100644 --- a/docs/auth/index.mdx +++ b/docs/auth/index.mdx @@ -35,24 +35,26 @@ Live example pages also support `Copy example URL` for sharing prefilled request - One FastNear API key works across RPC and API endpoints. - Many public reads still work without a key. -- Prefer `Authorization: Bearer ${API_KEY}` for production backends. -- Use `?apiKey=${API_KEY}` when headers are awkward or you are doing quick curl/debug work. +- Prefer `Authorization: Bearer ${FASTNEAR_API_KEY}` for production backends. +- Use `?apiKey=${FASTNEAR_API_KEY}` when headers are awkward or you are doing quick curl/debug work. - Agents and automations should keep the key in env vars or a secret manager, not in browser storage. ## Choose the auth form | Form | Best for | Notes | | --- | --- | --- | -| `Authorization: Bearer ${API_KEY}` | production backends, workers, automations, and proxies | Best default. Keeps credentials out of copied URLs and most URL logs. | -| `?apiKey=${API_KEY}` | simple curl, one-off debugging, systems that cannot set headers easily | Valid, but the key may end up in shell history, logs, analytics, or copied links. | +| `Authorization: Bearer ${FASTNEAR_API_KEY}` | production backends, workers, automations, and proxies | Best default. Keeps credentials out of copied URLs and most URL logs. | +| `?apiKey=${FASTNEAR_API_KEY}` | simple curl, one-off debugging, systems that cannot set headers easily | Valid, but the key may end up in shell history, logs, analytics, or copied links. | If you control the client, use the header form. ## Authorization header example ```bash +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer ${API_KEY}" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` @@ -60,7 +62,9 @@ curl "https://rpc.mainnet.fastnear.com" \ ## `?apiKey=` query parameter example ```bash -curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" \ +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + +curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index b7169e3..ee0e2e5 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -15,10 +15,11 @@ page_actions: `all-by-predecessor` returns the latest indexed writes one account made across every contract it touched. Lift an interesting key and replay it through `history` to see how that row changed over time. ```bash -KV_BASE_URL=https://kv.main.fastnear.com PREDECESSOR_ID=jemartel.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -FIRST="$(curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ +FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data '{"include_metadata":true,"limit":10}')" @@ -37,7 +38,8 @@ CURRENT_ACCOUNT_ID="$(echo "$FIRST" | jq -r '.entries[0].current_account_id')" EXACT_KEY="$(echo "$FIRST" | jq -r '.entries[0].key')" ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ +curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq '{entries: [.entries[] | {block_height, value}]}' ``` diff --git a/docs/fastdata/kv/index.md b/docs/fastdata/kv/index.md index ed4267c..53804f0 100644 --- a/docs/fastdata/kv/index.md +++ b/docs/fastdata/kv/index.md @@ -27,14 +27,15 @@ https://kv.test.fastnear.com If you already know one exact key, start with the latest indexed row and stop as soon as it answers the question. ```bash -KV_BASE_URL=https://kv.main.fastnear.com CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ +curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq '{ latest: ( .entries[0] diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index e67d367..e1915b7 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -17,9 +17,10 @@ NEAR Data returns each block fully hydrated as one JSON document — header plus `/v0/last_block/final` 302-redirects to the current finalized block. Before filtering for a specific contract, it's worth seeing what one block looks like at the protocol level: transactions arrive sharded, so the tx count for a block is a sum across shards — not a single top-level number. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ +curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq '{ height: .block.header.height, timestamp_nanosec: .block.header.timestamp_nanosec, @@ -35,10 +36,11 @@ A live block shows 9 shards and a handful of transactions scattered across them `/v0/last_block/final` 302-redirects to the current finalized block. Contracts can show up in a chunk's `transactions` (when they are the `receiver_id`) or in its `receipts` (when a cross-shard call lands), so one jq pass over the shards covers both. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ +curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq --arg contract "$TARGET_CONTRACT" '{ height: .block.header.height, contract: $contract, @@ -59,8 +61,8 @@ curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ Optimistic blocks ship at `/v0/block_opt/{height}` about a second ahead of `/v0/block/{height}`. A monitoring loop can act on the optimistic signal and expect the same answer to arrive at the finalized endpoint one block later — unless network stress widens the gap, in which case the finalized fetch returns `null` and you wait. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} count_touches() { jq --arg contract "$1" ' @@ -71,13 +73,15 @@ count_touches() { } OPT_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + curl -s -D - -o /dev/null -H "Authorization: Bearer $FASTNEAR_API_KEY" "https://mainnet.neardata.xyz/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" OPT_HEIGHT="${OPT_LOCATION##*/}" -echo "optimistic @ $OPT_HEIGHT: $(curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | count_touches "$TARGET_CONTRACT") touches" -FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" +echo "optimistic @ $OPT_HEIGHT: $(curl -s "https://mainnet.neardata.xyz$OPT_LOCATION" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" | count_touches "$TARGET_CONTRACT") touches" +FINAL="$(curl -s "https://mainnet.neardata.xyz/v0/block/$OPT_HEIGHT" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY")" if [ "$(printf '%s' "$FINAL" | jq 'type')" = '"null"' ]; then echo "finalized @ $OPT_HEIGHT: not caught up yet" else @@ -92,16 +96,18 @@ On a healthy mainnet the two counts match within a second. The value is in the * Most finalized blocks show no state mutation for any given contract — activity is sparse and shard-local. Walk back from the finalized head until the contract's state actually changes, then open that shard for the mutation payload. The block-level call tells you *which* shard; the shard-level call tells you *how*. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" +HEAD="$(curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" | jq '.block.header.height')" FOUND_HEIGHT="" FOUND_SHARD="" for OFFSET in $(seq 0 15); do H=$((HEAD - OFFSET)) - SHARD="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" \ + SHARD="$(curl -s "https://mainnet.neardata.xyz/v0/block/$H" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq -r --arg contract "$TARGET_CONTRACT" ' .shards[] | select([.state_changes[]? | select(.change.account_id? == $contract)] | length > 0) @@ -116,7 +122,8 @@ done if [ -z "$FOUND_HEIGHT" ]; then echo "no state mutation for $TARGET_CONTRACT in the last 16 finalized blocks" else - curl -s "$NEARDATA_BASE_URL/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + curl -s "https://mainnet.neardata.xyz/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq --arg contract "$TARGET_CONTRACT" --argjson height "$FOUND_HEIGHT" --argjson shard_id "$FOUND_SHARD" '{ height: $height, shard_id: $shard_id, diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index cfe8225..ca5947a 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -19,10 +19,11 @@ Start with the RPC method that answers the question. Use `tx` to track inclusion `view_account` is the canonical RPC query for an account's current state. One call returns the unstaked balance, any stake-locked amount, storage consumed, and the block the reading was taken at. `finality: "final"` ensures you're reading stable state, not an optimistic view. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$RPC_URL" \ +curl -s "https://rpc.mainnet.fastnear.com" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -31,7 +32,7 @@ curl -s "$RPC_URL" \ | jq '.result | {amount, locked, storage_usage, block_height, block_hash}' ``` -For `mike.near`, this returns `amount` (yoctoNEAR held unstaked), `locked: "0"` (nothing in validator stake or a lockup contract), and `storage_usage: 558441` — 558 KB of on-chain state. The `block_height`/`block_hash` pair anchors the reading; to read multiple accounts at the *same* block, reuse the returned `block_hash` as `block_id` on follow-up queries. +For `root.near`, this returns `amount` (yoctoNEAR held unstaked), `locked: "0"` (nothing in validator stake or a lockup contract), and `storage_usage: 28677` — about 28.7 KB of on-chain state. The `block_height`/`block_hash` pair anchors the reading; to read multiple accounts at the *same* block, reuse the returned `block_hash` as `block_id` on follow-up queries. ## Transaction Inclusion and Finality @@ -40,11 +41,12 @@ For `mike.near`, this returns `amount` (yoctoNEAR held unstaked), `locked: "0"` Have a tx hash? Poll `tx` with the smallest `wait_until` threshold that answers your question. ```bash -RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow SIGNER_ACCOUNT_ID=mike.testnet +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$RPC_URL" \ +curl -s "https://archival-rpc.testnet.fastnear.com" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" --arg signer_id "$SIGNER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "tx", @@ -72,14 +74,14 @@ Two handoffs from here: A NEAR block is a header over N shard chunks, not a flat list of transactions. `block` returns chunk headers; the transactions live one level down, inside `chunk`. There's no `block → tx` shortcut — the block doesn't carry transaction hashes, so `tx` (which needs a hash) doesn't enter this flow at all. The canonical walk is `status` → `block` → `chunk`, skipping empty chunks along the way. Most chunks in a tip block are empty — their `tx_root` is the sentinel `11111111111111111111111111111111` — so the selector has to filter. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com EMPTY_TX_ROOT=11111111111111111111111111111111 +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -BLOCK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +BLOCK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ | jq -r '.result.sync_info.latest_block_hash')" -CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +CHUNK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg block_hash "$BLOCK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"block",params:{block_id:$block_hash} }')" \ @@ -89,7 +91,7 @@ CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ if [ -z "$CHUNK_HASH" ]; then echo "tip block had no transactions in any chunk — rerun on the next head" else - curl -s "$RPC_URL" -H 'content-type: application/json' \ + curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg chunk_hash "$CHUNK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"chunk",params:{chunk_id:$chunk_hash} }')" \ @@ -127,11 +129,12 @@ New keys start at `block_height * 10^6` and the value increments by one per tran Any key with `tx_count: 0` was created and never used — the clearest candidate for cleanup. Keys scoped to a contract you no longer interact with are the next tier. The filter below narrows to `social.near`, but `RECEIVER_ID` is the only line that changes to audit a different contract. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near RECEIVER_ID=social.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$RPC_URL" \ +curl -s "https://rpc.mainnet.fastnear.com" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -155,7 +158,7 @@ curl -s "$RPC_URL" \ }' ``` -For `mike.near`, this returns dozens of `social.near` function-call keys. Entries with `tx_count: 0` were created and never used — prime removal candidates. `method_names: "ANY"` means the key can call any method on `social.near`; a narrowed list like `["find_grants", "insert_grant", "delete_grant"]` means the key was scoped to one dapp's write surface. +For `root.near`, this returns 235 total keys, including 34 function-call keys for `social.near`; 21 of those were created and never used (`tx_count: 0`) and are prime cleanup candidates. `method_names: "ANY"` means the key can call any method on `social.near`; a narrowed list like `["find_grants", "insert_grant", "delete_grant"]` means the key was scoped to one dapp's write surface. To remove one, sign a `DeleteKey` action with a **full-access** key (a function-call key cannot authorize `DeleteKey`) and submit via [`send_tx`](/rpc/transaction/send-tx). Re-run the query to confirm the key is gone. @@ -168,10 +171,10 @@ A view method like `get_num` still makes the node load the contract's wasm and r Contracts built with `near-sdk-rs` store the top-level `#[near_bindgen]` struct under the key `STATE`. Pass `STATE` as `prefix_base64` (`U1RBVEU=` is base64 for those four ASCII bytes) and the node returns the serialized value. ```bash -RPC_URL=https://rpc.testnet.fastnear.com CONTRACT_ID=counter.near-examples.testnet +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +RAW_B64="$(curl -s "https://rpc.testnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"view_state",account_id:$contract,prefix_base64:"U1RBVEU=",finality:"final"} @@ -196,13 +199,13 @@ These stay on exact SocialDB reads and on-chain readiness checks until the quest `social.near` knows two things a wallet UI can only guess at: how much storage each account has left, and whether a delegated signer is allowed to write under it. Two view calls collapse the readiness question to a single boolean. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near # account you're writing under -SIGNER_ACCOUNT_ID=mike.near # account signing the transaction +ACCOUNT_ID=root.near # account you're writing under +SIGNER_ACCOUNT_ID=root.near # account signing the transaction +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -STORAGE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +STORAGE="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$STORAGE_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"get_account_storage",args_base64:$args,finality:"final"} @@ -213,7 +216,7 @@ if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then PERMISSION=true else PERM_ARGS_B64="$(jq -nc --arg pred "$SIGNER_ACCOUNT_ID" --arg key "$ACCOUNT_ID" '{predecessor_id:$pred,key:$key}' | base64 | tr -d '\n')" - PERMISSION="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + PERMISSION="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$PERM_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"is_write_permission_granted",args_base64:$args,finality:"final"} @@ -231,23 +234,23 @@ jq -n --argjson storage "$STORAGE" --argjson permission "$PERMISSION" \ }' ``` -For `mike.near` signing under itself, this returns `storage: {used_bytes: 139803, available_bytes: 83891}`, `permission_granted: true` (owner write), and `ready_to_publish: true`. If `storage` comes back `null` or `available_bytes: 0`, the account needs a `storage_deposit` on `social.near` before any new write can stick. If the signer differs from the target, the permission branch asks `is_write_permission_granted({predecessor_id, key})` — the same on-chain answer a dapp sees before writing on a user's behalf. See the [SocialDB API](https://github.com/NearSocial/social-db#api) for the full contract surface. +For `root.near` signing under itself, this returns `storage: {used_bytes: 136245, available_bytes: 42484}`, `permission_granted: true` (owner write), and `ready_to_publish: true`. If `storage` comes back `null` or `available_bytes: 0`, the account needs a `storage_deposit` on `social.near` before any new write can stick. If the signer differs from the target, the permission branch asks `is_write_permission_granted({predecessor_id, key})` — the same on-chain answer a dapp sees before writing on a user's behalf. See the [SocialDB API](https://github.com/NearSocial/social-db#api) for the full contract surface. ### What does `mob.near/widget/Profile` actually contain right now? SocialDB stores BOS widgets as `/widget/` keys on `social.near`. One `keys` call with the `BlockHeight` return type returns the catalog plus per-widget last-write anchors; one `get` call returns the exact source. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com ACCOUNT_ID=mob.near WIDGET_NAME=Profile +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} KEYS_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" '{ keys: [($account_id + "/widget/*")], options: {return_type: "BlockHeight"} }' | base64 | tr -d '\n')" -curl -s "$RPC_URL" -H 'content-type: application/json' \ +curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$KEYS_ARGS" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"keys",args_base64:$args,finality:"final"} @@ -264,7 +267,7 @@ GET_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" '{ keys: [($account_id + "/widget/" + $widget)] }' | base64 | tr -d '\n')" -curl -s "$RPC_URL" -H 'content-type: application/json' \ +curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$GET_ARGS" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"get",args_base64:$args,finality:"final"} diff --git a/docs/transaction-flow/finality.mdx b/docs/transaction-flow/finality.mdx index 8e62e5b..89db6dc 100644 --- a/docs/transaction-flow/finality.mdx +++ b/docs/transaction-flow/finality.mdx @@ -132,7 +132,10 @@ pub enum FinalExecutionStatus { ### The `tx` RPC Method ```bash -curl -X POST https://rpc.mainnet.fastnear.com \ +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + +curl -X POST https://archival-rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", diff --git a/docs/transaction-flow/reference.mdx b/docs/transaction-flow/reference.mdx index 18a0236..2515d07 100644 --- a/docs/transaction-flow/reference.mdx +++ b/docs/transaction-flow/reference.mdx @@ -178,7 +178,10 @@ You never "call" another contract. You send them a letter and ask them to send o ```bash # Submit and get hash immediately +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl -X POST https://rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", @@ -194,7 +197,10 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Submit Transaction (Wait for Final) ```bash +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl -X POST https://rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", @@ -207,7 +213,10 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Check Transaction Status ```bash -curl -X POST https://rpc.mainnet.fastnear.com \ +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + +curl -X POST https://archival-rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", @@ -224,7 +233,10 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### View Access Key (for nonce) ```bash +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl -X POST https://rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", @@ -245,7 +257,10 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Get Recent Block Hash ```bash +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl -X POST https://rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", diff --git a/docs/transaction-flow/runtime-execution.mdx b/docs/transaction-flow/runtime-execution.mdx index 9128d1b..737e094 100644 --- a/docs/transaction-flow/runtime-execution.mdx +++ b/docs/transaction-flow/runtime-execution.mdx @@ -387,7 +387,10 @@ flowchart TD Clients can poll the `tx` RPC method with `wait_until: FINAL` to get all outcomes: ```bash -curl -X POST https://rpc.mainnet.fastnear.com \ +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + +curl -X POST https://archival-rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index a58fd39..4389a9e 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -15,10 +15,11 @@ page_actions: `/v0/transfers` with just `account_id` and `desc: true` returns the most recent transfers touching that account across every asset type, both directions mixed. Each row already carries `human_amount`, `asset_id`, and `transaction_id`, so the feed doubles as a quick activity scan before you reach for filters. ```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com ACCOUNT_ID=root.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ +curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id: $account_id, desc: true, limit: 5}')" \ | jq '{ @@ -40,11 +41,11 @@ For `root.near`, the latest rows mix `FtTransfer` and `MtTransfer` assets. `asse `/v0/transfers` returns a filtered feed plus a `resume_token` you replay with *unchanged* filters to keep paging. Each row already carries `human_amount`, `usd_amount`, `transaction_id`, and `receipt_id`, so most audit questions land without a second call. ```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=root.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -FEED="$(curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ +FEED="$(curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ account_id: $account_id, @@ -68,7 +69,8 @@ When one row needs its execution anchor, take its `receipt_id` straight to `/v0/ ```bash RECEIPT_ID="$(echo "$FEED" | jq -r '.transfers[0].receipt_id')" -curl -s "$TX_BASE_URL/v0/receipt" \ +curl -s "https://tx.main.fastnear.com/v0/receipt" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ | jq '.receipt | {receipt_id, transaction_hash, receiver_id, predecessor_id, tx_block_height, is_success}' diff --git a/docs/tx/berry-club.mdx b/docs/tx/berry-club.mdx index 796172e..334d424 100644 --- a/docs/tx/berry-club.mdx +++ b/docs/tx/berry-club.mdx @@ -50,8 +50,10 @@ This is the shortest useful read: ```bash ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} curl -sS https://rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "{ \"jsonrpc\": \"2.0\", @@ -81,7 +83,10 @@ When you need history, keep the flow short: This example uses a narrow window around block `97601515`: ```bash +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl -sS https://tx.main.fastnear.com/v0/account \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data '{ "account_id": "berryclub.ek.near", @@ -100,7 +105,10 @@ If you do not know the window yet, `/v0/blocks` can Hydrate the candidate hashes and keep only top-level `draw` calls: ```bash +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data '{ "tx_hashes": [ diff --git a/docs/tx/examples.md b/docs/tx/examples.md index 9dc224d..a45a48f 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -15,10 +15,11 @@ page_actions: Paste the hash into `POST /v0/transactions` and one response usually holds the whole story. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp +TX_HASH=7ZKnhzt2MqMNmsk13dV8GAjGu3Db8aHzSBHeNeu9MJCq +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ @@ -32,18 +33,19 @@ curl -s "$TX_BASE_URL/v0/transactions" \ }' ``` -For the pinned hash, `mike.near` sent a single `Transfer` to `global-counter.mike.near` in block `194263342`, handing off into receipt `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq`. When `receipt_count > 1` or the next question is about receipt-level behavior, jump to [Which receipt emitted this log or event?](#which-receipt-emitted-this-log-or-event) or [`POST /v0/receipt`](/tx/receipt). +For the pinned hash, `root.near` sent a single `Transfer` to `escrow.ai.near` in block `188976785`, handing off into receipt `B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1`. When `receipt_count > 1` or the next question is about receipt-level behavior, jump to [Which receipt emitted this log or event?](#which-receipt-emitted-this-log-or-event) or [`POST /v0/receipt`](/tx/receipt). ### Which receipt emitted this log or event? List every logged receipt in the transaction with a flag for whether its logs contain your fragment. The match is provable rather than guessed: this pinned tx logs a `Transfer` on one receipt and a `Refund` on another, and only the `Refund` side flips to `true`. ```bash -TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL LOG_FRAGMENT=Refund +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg fragment "$LOG_FRAGMENT" ' @@ -68,10 +70,11 @@ The `Refund` fragment attributes to receipt `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2ot `POST /v0/receipt` returns the receipt record **and** its full parent transaction in one response, so a single call covers the whole story — no follow-up `/v0/transactions` fetch needed. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID=5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq +RECEIPT_ID=B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1 +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TX_BASE_URL/v0/receipt" \ +curl -s "https://tx.main.fastnear.com/v0/receipt" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ | jq '{ @@ -93,7 +96,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ }' ``` -For the pinned receipt, this returns an `Action` receipt from `mike.near` to `global-counter.mike.near` that executed successfully in block `194263343`, one block after its parent tx `AdgNifPY…` landed — a single `Transfer` (5 NEAR, visible as `5000000000000000000000000` yocto in the raw `.transaction.transaction.actions`). If the parent tx becomes the interesting anchor, you already have the hash — reuse it with [I have one transaction hash. What happened?](#i-have-one-transaction-hash-what-happened). +For the pinned receipt, this returns an `Action` receipt from `root.near` to `escrow.ai.near` that executed successfully in block `188976786`, one block after its parent tx `7ZKnhzt2…` landed — a single `Transfer` (3.5 NEAR, visible as `3500000000000000000000000` yocto in the raw `.transaction.transaction.actions`). If the parent tx becomes the interesting anchor, you already have the hash — reuse it with [I have one transaction hash. What happened?](#i-have-one-transaction-hash-what-happened). ## Failure and Async @@ -102,12 +105,12 @@ For the pinned receipt, this returns an `Action` receipt from `mike.near` to `gl One batch submitted `CreateAccount → Transfer → AddKey → FunctionCall` and the final call hit a missing method. The indexed tx record already carries the ordered batch *and* the exact receipt-level failure, so one call answers "what was tried and what broke"; a `view_account` check then proves the earlier actions rolled back. ```bash -TX_BASE_URL=https://tx.test.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.test.fastnear.com/v0/transactions" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ @@ -129,7 +132,8 @@ The tx-level status is `SuccessReceiptId` — the transaction successfully hande Now prove the earlier actions rolled back by asking for the account the batch *tried* to create: ```bash -curl -s "$RPC_URL" \ +curl -s "https://rpc.testnet.fastnear.com" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "query", @@ -145,12 +149,13 @@ curl -s "$RPC_URL" \ A tx's outer `execution_outcome.outcome.status` reports `SuccessReceiptId` whenever the first receipt handoff worked — it says nothing about whether downstream receipts succeeded or whether the origin callback ran. One pipeline over `/v0/transactions` answers all three questions at once. ```bash -TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL ORIGIN_CONTRACT_ID=wrap.near CALLBACK_METHOD=ft_resolve_transfer +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ @@ -188,11 +193,12 @@ Receipt success is not transitive. A protocol can hand off cleanly and still see [OutLayer](https://outlayer.fastnear.com) splits one logical call across two transactions: a user signs `request_execution` on `outlayer.near`, an Intel TDX worker runs the requested WASM off-chain, then `worker.outlayer.near` submits the result with `submit_execution_output_and_resolve`. Both halves carry the same `request_id` — passing the two tx hashes to `/v0/transactions` in one call and extracting that field from each proves the pair. ```bash -TX_BASE_URL=https://tx.main.fastnear.com REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg a "$REQUEST_TX" --arg b "$WORKER_TX" '{tx_hashes: [$a, $b]}')" \ | jq '[ diff --git a/docs/tx/socialdb-proofs.mdx b/docs/tx/socialdb-proofs.mdx index 91cdb6a..d57837d 100644 --- a/docs/tx/socialdb-proofs.mdx +++ b/docs/tx/socialdb-proofs.mdx @@ -13,31 +13,30 @@ Use this page only when the starting point is already a readable SocialDB value For FastNear-first jobs, start with [Transactions Examples](/tx/examples). Come here only when the question has become "which write made this readable SocialDB value true?" -## Canonical example: prove that `mike.near` set `profile.name` to `Mike Purvis` +## Canonical example: prove that `root.near` set `profile.name` to `Illia` -Use this when the readable fact is already "the current `profile.name` is `Mike Purvis`" and the remaining question is which write made that field true. +Use this when the readable fact is already "the current `profile.name` is `Illia`" and the remaining question is which write made that field true. This is the one SocialDB nuance worth keeping: for historical proof, the field-level `:block` is usually the right bridge, not the parent object's `:block`. For this live anchor: -- current `profile.name`: `Mike Purvis` -- field-level SocialDB write block: `78675795` -- receipt ID: `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN` -- originating transaction hash: `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ` -- outer transaction block: `78675794` +- current `profile.name`: `Illia` +- field-level SocialDB write block: `75590392` +- receipt ID: `GYvnvBxWA46UGa3aGEkqUBeT7hxhVXk2iZScJFZWU8Se` +- originating transaction hash: `7HtFWv51k5Bispmh1WYPbAVkxr2X4AL6n98DhcQwVw7w` +- outer transaction block: `75590391` ### Shell walkthrough 1. Read the field from NEAR Social and capture the field-level write block. ```bash -SOCIAL_API_BASE_URL=https://api.near.social -TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near PROFILE_FIELD=profile/name +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -PROFILE="$(curl -s "$SOCIAL_API_BASE_URL/get" \ +PROFILE="$(curl -s "https://api.near.social/get" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ keys: [($account_id + "/" + $profile_field)], @@ -56,7 +55,8 @@ PROFILE_BLOCK_HEIGHT="$(echo "$PROFILE" | jq -r --arg account_id "$ACCOUNT_ID" ' 2. Reuse that field-level block in FastNear block receipts and recover the receipt plus tx hash. ```bash -BLOCK_RECEIPTS="$(curl -s "$TX_BASE_URL/v0/block" \ +BLOCK_RECEIPTS="$(curl -s "https://tx.main.fastnear.com/v0/block" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ block_id: $block_id, @@ -85,7 +85,8 @@ PROFILE_TX_HASH="$(echo "$BLOCK_RECEIPTS" | jq -r --arg account_id "$ACCOUNT_ID" 3. Reuse that tx hash in `POST /v0/transactions` and decode the SocialDB write payload. ```bash -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg account_id "$ACCOUNT_ID" '{ @@ -101,8 +102,8 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | { method_name, profile_name: $profile.name, - description: $profile.description, - tags: ($profile.tags | keys) + image_fields: (($profile.image // {}) | keys), + linktree_keys: (($profile.linktree // {}) | keys) } ) }' @@ -112,7 +113,7 @@ That is the whole lookup pattern: readable value, field-level block, receipt bri The same bridge works for other readable SocialDB values too: -- follow edge variant: `mike.near -> mob.near`, block `79574924`, tx `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- widget source variant: `mob.near/widget/Profile`, block `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- follow edge variant: `root.near -> mob.near`, block `79152039`, tx `DvNoqtDrruhmcq7mPpxdFacph2ZCqSzMFF5ZqMRFG78q` +- widget source variant: `root.near/widget/Profile`, block `76029540`, tx `ELS3DrE4Upoc91ZnBh4thVugxCUBAbaLFB4nyKsoyRNP` The key idea does not change: start from the readable value and its write block, recover the `*.near -> social.near` receipt from the block, then decode the `social.near set` payload from the originating transaction. 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..7f78e3b 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 @@ -36,8 +36,8 @@ page_actions: | Способ | Используйте, когда... | Заметки | | --- | --- | --- | -| `Authorization: Bearer ${API_KEY}` | HTTP-клиентом или бэкендом управляете вы | Лучшее значение по умолчанию для агентов. Меньше шансов утечь в логи URL, аналитику или скопированные ссылки. | -| `?apiKey=${API_KEY}` | используется простой curl или система, которой сложно выставлять заголовки | Тоже допустимо, но URL обычно дальше путешествуют через логи и инструменты. Применяйте осознанно. | +| `Authorization: Bearer ${FASTNEAR_API_KEY}` | HTTP-клиентом или бэкендом управляете вы | Лучшее значение по умолчанию для агентов. Меньше шансов утечь в логи URL, аналитику или скопированные ссылки. | +| `?apiKey=${FASTNEAR_API_KEY}` | используется простой curl или система, которой сложно выставлять заголовки | Тоже допустимо, но URL обычно дальше путешествуют через логи и инструменты. Применяйте осознанно. | Если есть выбор — используйте заголовочную форму. 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..0b64405 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/agents/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/agents/index.mdx @@ -101,14 +101,18 @@ page_actions: Публичные эндпоинты часто работают и без ключа. Добавьте ключ, если нужны повышенные лимиты, единая аутентифицированная модель или платные сценарии. Один и тот же ключ работает со всеми API FastNear выше, включая обычные и архивные RPC-хосты; передавайте его либо в HTTP-заголовке, либо в URL-параметре: ```bash title="Заголовок Authorization" +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer ${API_KEY}" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` ```bash title="URL-параметр" -curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + +curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" ``` Получить ключ: [dashboard.fastnear.com](https://dashboard.fastnear.com). Операционный режим для неинтерактивных сред: [Аутентификация для агентов](/agents/auth) — ключи должны жить в переменных окружения или менеджере секретов, а не в браузерном хранилище, логах чатов или промптах. Полный сценарий и детали заголовков: [Аутентификация и доступ](/auth). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index 52a36a5..2f0bb1e 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -15,10 +15,11 @@ page_actions: `/v1/account/{id}/full` — это агрегатор аккаунтов в FastNear API: один вызов собирает NEAR-состояние аккаунта, каждый FT-контракт, которого он касался, каждую NFT-коллекцию, которую он получил, и каждый валидаторский пул, в который делегировал. Если у вас уже есть `account_id`, это самый быстрый ответ на вопрос «что это за аккаунт?». ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq '{ account_id, near_balance_yocto: .state.balance, @@ -28,23 +29,25 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -Для `mike.near`: 40 FT-контрактов в списке, 40 NFT-коллекций, 5 валидаторских пулов. Одни только счётчики контрактов говорят, что это активный mainnet-аккаунт. Все примеры ниже погружаются в какую-то одну из этих поверхностей — начинайте отсюда, когда на руках только ID аккаунта. +Для `root.near`: 150 FT-контрактов в списке, 102 NFT-коллекции, 2 валидаторских пула. Одни только счётчики контрактов говорят, что это оживлённый mainnet-аккаунт. Все примеры ниже погружаются в какую-то одну из этих поверхностей — начинайте отсюда, когда на руках только ID аккаунта. ### Определить аккаунт по публичному ключу и сразу получить сводку Найдите, какому аккаунту принадлежит ключ, и прочитайте его активы за один следующий запрос. ```bash -API_BASE_URL=https://api.fastnear.com PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -LOOKUP="$(curl -s "$API_BASE_URL/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')")" +LOOKUP="$(curl -s "https://api.fastnear.com/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY")" echo "$LOOKUP" | jq '{matched: (.account_ids | length), account_ids}' ACCOUNT_ID="$(echo "$LOOKUP" | jq -r '.account_ids[0]')" -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq '{account_id, state, tokens: (.tokens|length), nfts: (.nfts|length), pools: (.pools|length)}' ``` @@ -55,10 +58,11 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Состояние аккаунта NEAR делится на три ведра, которые UI кошельков обычно сливает в одно: `balance` — это свободная часть (не в стейкинге), `locked` — NEAR, привязанный к валидаторскому стейку или lockup-контракту, а `storage_bytes` подразумевает ещё отдельную долю, пришпиленную к trie по текущей ставке 10^19 yoctoNEAR за байт. Один pipeline над `/full` разводит их по полкам. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq ' (.state.balance | tonumber) as $amount | (.state.locked | tonumber) as $locked @@ -76,7 +80,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -Для `mike.near`: ~2613.49 NEAR всего, всё в свободной части, ~5.58 NEAR закреплено за 558 КБ on-chain-состояния, ~2607.91 NEAR доступно к переводу. Новым аккаунтам это особенно заметно — свежесозданный именованный аккаунт ~182 байта «съедает» ~0.00182 NEAR под storage, и именно поэтому CLI-утилиты не дают отправить полный баланс. +Для `root.near`: ~3914.67 NEAR всего, всё в свободной части, ~0.28677 NEAR закреплено за 28,677 байтами on-chain-состояния, ~3914.38 NEAR доступно к переводу. Новым аккаунтам это особенно заметно — свежесозданный именованный аккаунт ~182 байта «съедает» ~0.00182 NEAR под storage, и именно поэтому CLI-утилиты не дают отправить полный баланс. Наведите тот же pipeline на валидаторский пул вроде `astro-stakers.poolv1.near`, и пропорции перевернутся: ~730 тыс. свободных, ~27.68 млн в `locked`. Этот `locked` — собственный протокольный валидаторский стейк пула, а не средства делегатов (те учитываются внутри состояния контракта пула). Одно и то же поле означает разное на разных типах аккаунтов. @@ -87,10 +91,11 @@ jq считает в IEEE-754 double, поэтому NEAR-значения вы У каждой записи в массивах `tokens`, `nfts` и `pools` внутри `/full` есть собственное `last_update_block_height` — блок, в котором индексер последний раз видел изменение этой строки для этого аккаунта. Максимум по всем трём массивам даёт дешёвый сигнал «последняя активность» без похода в Transactions API. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq ' [ (.tokens // [])[].last_update_block_height, @@ -107,7 +112,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -Для `mike.near` относительно текущего tip это возвращает 85 записей по FT-, NFT- и pool-контрактам, 34 с отслеживаемым блоком и самый свежий блок `194711866` — примерно 125 тыс. блоков назад, или около 35 часов при темпе NEAR ~1 блок/сек. Для `root.near`: 254 записи, 158 отслеживаемых. +Для `root.near` это возвращает 254 записи по FT-, NFT- и pool-контрактам, 158 с отслеживаемым блоком и самый свежий блок `194301659`. Этого уже достаточно, чтобы понять, что кошелёк живой, не заходя в историю транзакций. Это правильный вопрос для «заброшен ли этот кошелёк?» или «двигалось ли что-то после блока X?» — дёшево, один запрос, без истории транзакций. Чтобы достать саму транзакцию, вызвавшую последнее изменение, расширяйте поверхность до [Transactions API](/tx). Записи с `last_update_block_height: null` относятся ко времени до per-row-отслеживания индексером (обычно старые airdrops) и здесь игнорируются, а не считаются свежими. @@ -116,11 +121,12 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Имена аккаунтов на NEAR кодируют иерархию: `mint.sharddog.near` — это подаккаунт `sharddog.near`, который, в свою очередь, — подаккаунт `near`. Издатели, выпускающие несколько NFT-коллекций, обычно разворачивают каждую как отдельный подаккаунт, поэтому один фильтр по суффиксу над NFT-списком аккаунта вытаскивает всё опубликованное под одним деревом — без внешнего реестра коллекций. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near PUBLISHER=sharddog.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq --arg publisher "$PUBLISHER" ' ("." + $publisher) as $suffix | { @@ -138,7 +144,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ }' ``` -Для `mike.near` и `sharddog.near` это возвращает четыре контракта-подаккаунта: `comic`, `mintv2`, `mint` и `claim`. Два с ненулевым `last_update_block_height` (`mint` на `115715361` и `claim` на `119718026`) — те, где позиция кошелька действительно менялась. Два других — спящие, типично для одноразовых drop-контрактов, в которые аккаунт что-то получил и больше не возвращался. +Для `root.near` и `sharddog.near` это возвращает четыре контракта-подаккаунта: `ndcconstellationnft`, `mint`, `harvestmoon` и `claim`. Только у `claim` есть ненулевой `last_update_block_height` (`131402024`), так что именно этот контракт явно менял позицию кошелька. Остальные — спящие, что типично для одноразовых drop-контрактов, в которые аккаунт что-то получил и больше не возвращался. Поменяйте `PUBLISHER` на любой аккаунт, чтобы сфокусировать фильтр на другом дереве издателя. @@ -147,12 +153,14 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ Прямые позиции в пулах лежат на `/staking`; liquid staking-токены (stNEAR, LiNEAR и т. п.) лежат на `/ft` как обычные FT. Прочитайте оба эндпоинта и классифицируйте кошелёк — `root.near` оказывается `mixed`. ```bash -API_BASE_URL=https://api.fastnear.com ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -STAKING="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking")" -FT="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft")" +STAKING="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/staking" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY")" +FT="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/ft" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY")" jq -n \ --argjson staking "$STAKING" \ diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx index 7b20fc5..855321c 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx @@ -43,8 +43,10 @@ import IconExternalLink from '@theme/Icon/ExternalLink'; ## Через заголовок Authorization ```bash +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer ${API_KEY}" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` @@ -52,7 +54,9 @@ curl "https://rpc.mainnet.fastnear.com" \ ## Через URL-параметр `?apiKey=` ```bash -curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" \ +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + +curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index 42de5db..7bd2451 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -15,10 +15,11 @@ page_actions: `all-by-predecessor` возвращает последние индексированные записи одного аккаунта по каждому контракту, которого он касался. Выберите интересный ключ и прогоните его через `history`, чтобы увидеть, как эта строка менялась со временем. ```bash -KV_BASE_URL=https://kv.main.fastnear.com PREDECESSOR_ID=jemartel.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -FIRST="$(curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ +FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data '{"include_metadata":true,"limit":10}')" @@ -37,7 +38,8 @@ CURRENT_ACCOUNT_ID="$(echo "$FIRST" | jq -r '.entries[0].current_account_id')" EXACT_KEY="$(echo "$FIRST" | jq -r '.entries[0].key')" ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ +curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq '{entries: [.entries[] | {block_height, value}]}' ``` diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md index 8832359..0c972ad 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md @@ -27,14 +27,15 @@ https://kv.test.fastnear.com Если вы уже знаете один точный ключ, начните с последней индексированной строки и остановитесь, как только она ответит на вопрос. ```bash -KV_BASE_URL=https://kv.main.fastnear.com CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ +curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq '{ latest: ( .entries[0] diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index c5938b2..a41e7fe 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -17,9 +17,10 @@ NEAR Data возвращает каждый блок полностью гидр `/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Прежде чем фильтровать по конкретному контракту, полезно увидеть, как выглядит один блок на уровне протокола: транзакции приходят с разбивкой по shard, поэтому общее число транзакций в блоке — это сумма по shards, а не одно поле верхнего уровня. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ +curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq '{ height: .block.header.height, timestamp_nanosec: .block.header.timestamp_nanosec, @@ -35,10 +36,11 @@ curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ `/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Контракт может проявиться либо в `transactions` chunk (когда он `receiver_id`), либо в `receipts` (когда прилетает cross-shard-вызов), поэтому один проход jq по shards покрывает оба случая. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ +curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq --arg contract "$TARGET_CONTRACT" '{ height: .block.header.height, contract: $contract, @@ -59,8 +61,8 @@ curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ Optimistic-блоки ходят по `/v0/block_opt/{height}` примерно на секунду впереди `/v0/block/{height}`. Цикл мониторинга может действовать по optimistic-сигналу и ожидать, что тот же ответ придёт на финализированный эндпоинт через один блок — если только стресс сети не расширит разрыв, и тогда финализированный fetch вернёт `null`, а вы подождёте. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} count_touches() { jq --arg contract "$1" ' @@ -71,13 +73,15 @@ count_touches() { } OPT_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + curl -s -D - -o /dev/null -H "Authorization: Bearer $FASTNEAR_API_KEY" "https://mainnet.neardata.xyz/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" OPT_HEIGHT="${OPT_LOCATION##*/}" -echo "optimistic @ $OPT_HEIGHT: $(curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | count_touches "$TARGET_CONTRACT") touches" -FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" +echo "optimistic @ $OPT_HEIGHT: $(curl -s "https://mainnet.neardata.xyz$OPT_LOCATION" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" | count_touches "$TARGET_CONTRACT") touches" +FINAL="$(curl -s "https://mainnet.neardata.xyz/v0/block/$OPT_HEIGHT" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY")" if [ "$(printf '%s' "$FINAL" | jq 'type')" = '"null"' ]; then echo "finalized @ $OPT_HEIGHT: not caught up yet" else @@ -92,16 +96,18 @@ fi В большинстве финализированных блоков нет мутации состояния ни для одного конкретного контракта — активность разрежена и привязана к shard. Идите назад от финализированной головы, пока состояние контракта реально не изменится, и откройте этот shard для payload мутации. Вызов уровня блока говорит, *на каком* shard это случилось; вызов уровня shard — *как*. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" +HEAD="$(curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" | jq '.block.header.height')" FOUND_HEIGHT="" FOUND_SHARD="" for OFFSET in $(seq 0 15); do H=$((HEAD - OFFSET)) - SHARD="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" \ + SHARD="$(curl -s "https://mainnet.neardata.xyz/v0/block/$H" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq -r --arg contract "$TARGET_CONTRACT" ' .shards[] | select([.state_changes[]? | select(.change.account_id? == $contract)] | length > 0) @@ -116,7 +122,8 @@ done if [ -z "$FOUND_HEIGHT" ]; then echo "no state mutation for $TARGET_CONTRACT in the last 16 finalized blocks" else - curl -s "$NEARDATA_BASE_URL/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + curl -s "https://mainnet.neardata.xyz/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ | jq --arg contract "$TARGET_CONTRACT" --argjson height "$FOUND_HEIGHT" --argjson shard_id "$FOUND_SHARD" '{ height: $height, shard_id: $shard_id, diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index 70a2cb1..419b94c 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -19,10 +19,11 @@ page_actions: `view_account` — канонический RPC-запрос для текущего состояния аккаунта. Один вызов возвращает свободный баланс, сумму, заблокированную в валидаторском стейке или lockup-контракте, использованное storage и блок, на котором было сделано чтение. `finality: "final"` гарантирует, что вы читаете стабильное состояние, а не optimistic-представление. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$RPC_URL" \ +curl -s "https://rpc.mainnet.fastnear.com" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -31,7 +32,7 @@ curl -s "$RPC_URL" \ | jq '.result | {amount, locked, storage_usage, block_height, block_hash}' ``` -Для `mike.near` это возвращает `amount` (yoctoNEAR в свободной части), `locked: "0"` (ничего в валидаторском стейке или lockup-контракте) и `storage_usage: 558441` — 558 КБ on-chain-состояния. Пара `block_height`/`block_hash` фиксирует точку чтения; чтобы прочитать несколько аккаунтов *на одном и том же* блоке, переиспользуйте возвращённый `block_hash` как `block_id` в последующих запросах. +Для `root.near` это возвращает `amount` (yoctoNEAR в свободной части), `locked: "0"` (ничего в валидаторском стейке или lockup-контракте) и `storage_usage: 28677` — примерно 28.7 КБ on-chain-состояния. Пара `block_height`/`block_hash` фиксирует точку чтения; чтобы прочитать несколько аккаунтов *на одном и том же* блоке, переиспользуйте возвращённый `block_hash` как `block_id` в последующих запросах. ## Включение транзакции и финальность @@ -40,11 +41,12 @@ curl -s "$RPC_URL" \ Есть tx hash? Опрашивайте `tx` с минимальным порогом `wait_until`, который отвечает на ваш вопрос. ```bash -RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow SIGNER_ACCOUNT_ID=mike.testnet +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$RPC_URL" \ +curl -s "https://archival-rpc.testnet.fastnear.com" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" --arg signer_id "$SIGNER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "tx", @@ -72,14 +74,14 @@ curl -s "$RPC_URL" \ Блок NEAR — это header поверх N shard chunks, а не плоский список транзакций. `block` возвращает headers chunks; сами транзакции лежат уровнем ниже, внутри `chunk`. Шортката `block → tx` нет — блок не несёт хешей транзакций, поэтому `tx` (которому нужен hash) в этой цепочке не участвует. Канонический проход — `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com EMPTY_TX_ROOT=11111111111111111111111111111111 +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -BLOCK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +BLOCK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ | jq -r '.result.sync_info.latest_block_hash')" -CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +CHUNK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg block_hash "$BLOCK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"block",params:{block_id:$block_hash} }')" \ @@ -89,7 +91,7 @@ CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ if [ -z "$CHUNK_HASH" ]; then echo "tip block had no transactions in any chunk — rerun on the next head" else - curl -s "$RPC_URL" -H 'content-type: application/json' \ + curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg chunk_hash "$CHUNK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"chunk",params:{chunk_id:$chunk_hash} }')" \ @@ -127,11 +129,12 @@ fi Любой ключ с `tx_count: 0` был создан и ни разу не использовался — самый очевидный кандидат на очистку. Следующий по порядку — ключи, заскоупленные на контракт, с которым вы больше не работаете. Фильтр ниже сужает до `social.near`, но чтобы аудитировать другой контракт, меняется только строка `RECEIVER_ID`. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near RECEIVER_ID=social.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$RPC_URL" \ +curl -s "https://rpc.mainnet.fastnear.com" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -155,7 +158,7 @@ curl -s "$RPC_URL" \ }' ``` -Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на удаление. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность одного dapp. +Для `root.near` это возвращает 235 ключей всего, включая 34 function-call-ключа на `social.near`; 21 из них были созданы и ни разу не использовались (`tx_count: 0`) и потому являются прямыми кандидатами на удаление. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность одного dapp. Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом (function-call-ключ не может авторизовать `DeleteKey`) и отправьте через [`send_tx`](/rpc/transaction/send-tx). Повторный запуск того же запроса подтвердит, что ключа больше нет. @@ -168,10 +171,10 @@ View-метод вроде `get_num` всё равно заставляет уз Контракты на `near-sdk-rs` хранят верхнеуровневую `#[near_bindgen]`-структуру под ключом `STATE`. Передайте `STATE` как `prefix_base64` (`U1RBVEU=` — это base64 тех же четырёх ASCII-байт), и узел вернёт сериализованное значение. ```bash -RPC_URL=https://rpc.testnet.fastnear.com CONTRACT_ID=counter.near-examples.testnet +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +RAW_B64="$(curl -s "https://rpc.testnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"view_state",account_id:$contract,prefix_base64:"U1RBVEU=",finality:"final"} @@ -196,13 +199,13 @@ jq -n --arg raw "$RAW_B64" --argjson val "$DECODED_I8" '{raw_bytes_base64: $raw, `social.near` знает две вещи, о которых UI кошелька может только догадываться: сколько storage осталось у каждого аккаунта и разрешена ли делегированному signer запись под этим аккаунтом. Два view-вызова сворачивают вопрос готовности к одному boolean. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near # account you're writing under -SIGNER_ACCOUNT_ID=mike.near # account signing the transaction +ACCOUNT_ID=root.near # account you're writing under +SIGNER_ACCOUNT_ID=root.near # account signing the transaction +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -STORAGE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +STORAGE="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$STORAGE_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"get_account_storage",args_base64:$args,finality:"final"} @@ -213,7 +216,7 @@ if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then PERMISSION=true else PERM_ARGS_B64="$(jq -nc --arg pred "$SIGNER_ACCOUNT_ID" --arg key "$ACCOUNT_ID" '{predecessor_id:$pred,key:$key}' | base64 | tr -d '\n')" - PERMISSION="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + PERMISSION="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$PERM_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"is_write_permission_granted",args_base64:$args,finality:"final"} @@ -231,23 +234,23 @@ jq -n --argjson storage "$STORAGE" --argjson permission "$PERMISSION" \ }' ``` -Для `mike.near`, подписывающего под собой, это возвращает `storage: {used_bytes: 139803, available_bytes: 83891}`, `permission_granted: true` (владельческая запись) и `ready_to_publish: true`. Если `storage` приходит как `null` или `available_bytes: 0`, аккаунту нужен `storage_deposit` на `social.near`, прежде чем новая запись сможет закрепиться. Если signer отличается от цели, ветка permission спрашивает `is_write_permission_granted({predecessor_id, key})` — тот же on-chain-ответ, который dapp видит, прежде чем писать от имени пользователя. Полную поверхность контракта см. в [SocialDB API](https://github.com/NearSocial/social-db#api). +Для `root.near`, подписывающего под собой, это возвращает `storage: {used_bytes: 136245, available_bytes: 42484}`, `permission_granted: true` (владельческая запись) и `ready_to_publish: true`. Если `storage` приходит как `null` или `available_bytes: 0`, аккаунту нужен `storage_deposit` на `social.near`, прежде чем новая запись сможет закрепиться. Если signer отличается от цели, ветка permission спрашивает `is_write_permission_granted({predecessor_id, key})` — тот же on-chain-ответ, который dapp видит, прежде чем писать от имени пользователя. Полную поверхность контракта см. в [SocialDB API](https://github.com/NearSocial/social-db#api). ### Что `mob.near/widget/Profile` содержит прямо сейчас? SocialDB хранит BOS-виджеты как ключи `/widget/` на `social.near`. Один `keys` с типом возврата `BlockHeight` возвращает каталог плюс якоря последней записи по каждому виджету; один `get` возвращает точный исходник. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com ACCOUNT_ID=mob.near WIDGET_NAME=Profile +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} KEYS_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" '{ keys: [($account_id + "/widget/*")], options: {return_type: "BlockHeight"} }' | base64 | tr -d '\n')" -curl -s "$RPC_URL" -H 'content-type: application/json' \ +curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$KEYS_ARGS" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"keys",args_base64:$args,finality:"final"} @@ -264,7 +267,7 @@ GET_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" '{ keys: [($account_id + "/widget/" + $widget)] }' | base64 | tr -d '\n')" -curl -s "$RPC_URL" -H 'content-type: application/json' \ +curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$GET_ARGS" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"get",args_base64:$args,finality:"final"} diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transaction-flow/finality.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/transaction-flow/finality.mdx index a0be915..adb6b1a 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transaction-flow/finality.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transaction-flow/finality.mdx @@ -141,7 +141,10 @@ pub enum FinalExecutionStatus { ### The `tx` RPC Method ```bash -curl -X POST https://rpc.mainnet.fastnear.com \ +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + +curl -X POST https://archival-rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transaction-flow/reference.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/transaction-flow/reference.mdx index 6e8cc5b..19a995c 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transaction-flow/reference.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transaction-flow/reference.mdx @@ -178,7 +178,10 @@ You never "вызов" another контракта. You отправка them a l ```bash # Submit and get hash immediately +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl -X POST https://rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", @@ -194,7 +197,10 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Submit Транзакция (Wait for Final) ```bash +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl -X POST https://rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", @@ -207,7 +213,10 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Check Транзакция Статус ```bash -curl -X POST https://rpc.mainnet.fastnear.com \ +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + +curl -X POST https://archival-rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", @@ -224,7 +233,10 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Просмотр доступ Key (for nonce) ```bash +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl -X POST https://rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", @@ -245,7 +257,10 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Get Recent Блок Hash ```bash +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl -X POST https://rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transaction-flow/runtime-execution.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/transaction-flow/runtime-execution.mdx index e09d955..22b59ca 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transaction-flow/runtime-execution.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transaction-flow/runtime-execution.mdx @@ -390,7 +390,10 @@ flowchart TD Clients can poll the `tx` RPC method with `wait_until: FINAL` to get all outcomes: ```bash -curl -X POST https://rpc.mainnet.fastnear.com \ +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + +curl -X POST https://archival-rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md index 552c152..cfb7271 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -15,10 +15,11 @@ page_actions: `/v0/transfers` всего с `account_id` и `desc: true` возвращает самые свежие переводы, касающиеся этого аккаунта, по всем типам активов, в обоих направлениях сразу. В каждой строке уже есть `human_amount`, `asset_id` и `transaction_id`, так что этот поток заодно служит быстрым сканом активности до того, как вы достанете фильтры. ```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com ACCOUNT_ID=root.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ +curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id: $account_id, desc: true, limit: 5}')" \ | jq '{ @@ -40,11 +41,11 @@ curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ `/v0/transfers` возвращает отфильтрованную ленту плюс `resume_token`, который вы переиспользуете *без изменения фильтров*, чтобы продолжать листать. В каждой строке уже есть `human_amount`, `usd_amount`, `transaction_id` и `receipt_id` — большинство audit-вопросов закрываются без второго запроса. ```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=root.near +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -FEED="$(curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ +FEED="$(curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ account_id: $account_id, @@ -68,7 +69,8 @@ echo "$FEED" | jq '{ ```bash RECEIPT_ID="$(echo "$FEED" | jq -r '.transfers[0].receipt_id')" -curl -s "$TX_BASE_URL/v0/receipt" \ +curl -s "https://tx.main.fastnear.com/v0/receipt" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ | jq '.receipt | {receipt_id, transaction_hash, receiver_id, predecessor_id, tx_block_height, is_success}' diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx index 938cbc1..c9759eb 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/berry-club.mdx @@ -50,8 +50,10 @@ import BerryClubLiveBoard from '@site/src/components/BerryClubLiveBoard'; ```bash ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} curl -sS https://rpc.mainnet.fastnear.com \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "{ \"jsonrpc\": \"2.0\", @@ -81,7 +83,10 @@ curl -sS https://rpc.mainnet.fastnear.com \ В этом примере используется узкое окно вокруг блока `97601515`: ```bash +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl -sS https://tx.main.fastnear.com/v0/account \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data '{ "account_id": "berryclub.ek.near", @@ -100,7 +105,10 @@ curl -sS https://tx.main.fastnear.com/v0/account \ Раскройте кандидатные хеши и оставьте только верхнеуровневые вызовы `draw`: ```bash +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} + curl -sS https://tx.main.fastnear.com/v0/transactions \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data '{ "tx_hashes": [ diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index ad43fd4..a11d2b2 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -15,10 +15,11 @@ page_actions: Вставьте хеш в `POST /v0/transactions` — один ответ обычно содержит всю историю. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp +TX_HASH=7ZKnhzt2MqMNmsk13dV8GAjGu3Db8aHzSBHeNeu9MJCq +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ @@ -32,18 +33,19 @@ curl -s "$TX_BASE_URL/v0/transactions" \ }' ``` -Для зафиксированного хеша `mike.near` отправил один `Transfer` на `global-counter.mike.near` в блоке `194263342`, с передачей в receipt `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq`. Если `receipt_count > 1` или следующий вопрос касается поведения на уровне receipt, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие) или [`POST /v0/receipt`](/tx/receipt). +Для зафиксированного хеша `root.near` отправил один `Transfer` на `escrow.ai.near` в блоке `188976785`, с передачей в receipt `B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1`. Если `receipt_count > 1` или следующий вопрос касается поведения на уровне receipt, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие) или [`POST /v0/receipt`](/tx/receipt). ### Какой receipt испустил этот лог или событие? Выведите список всех receipt транзакции с логами и флагом, содержат ли их логи ваш фрагмент. Совпадение доказывается, а не угадывается: у зафиксированной транзакции один receipt логирует `Transfer`, другой — `Refund`, и только сторона `Refund` переключается в `true`. ```bash -TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL LOG_FRAGMENT=Refund +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg fragment "$LOG_FRAGMENT" ' @@ -68,10 +70,11 @@ curl -s "$TX_BASE_URL/v0/transactions" \ `POST /v0/receipt` возвращает запись receipt **и** его полную родительскую транзакцию в одном ответе, поэтому единственного запроса хватает на всю историю — дополнительный `/v0/transactions` не нужен. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID=5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq +RECEIPT_ID=B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1 +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TX_BASE_URL/v0/receipt" \ +curl -s "https://tx.main.fastnear.com/v0/receipt" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ | jq '{ @@ -93,7 +96,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ }' ``` -Для зафиксированного receipt это возвращает `Action`-receipt от `mike.near` к `global-counter.mike.near`, который успешно выполнился в блоке `194263343`, через один блок после попадания родительской транзакции `AdgNifPY…`, — один `Transfer` (5 NEAR, в сыром `.transaction.transaction.actions` видимо как `5000000000000000000000000` yocto). Если интересным якорем становится родительская транзакция, хеш у вас уже есть — переиспользуйте его в [У меня один хеш транзакции. Что произошло?](#у-меня-один-хеш-транзакции-что-произошло). +Для зафиксированного receipt это возвращает `Action`-receipt от `root.near` к `escrow.ai.near`, который успешно выполнился в блоке `188976786`, через один блок после попадания родительской транзакции `7ZKnhzt2…`, — один `Transfer` (3.5 NEAR, в сыром `.transaction.transaction.actions` видимо как `3500000000000000000000000` yocto). Если интересным якорем становится родительская транзакция, хеш у вас уже есть — переиспользуйте его в [У меня один хеш транзакции. Что произошло?](#у-меня-один-хеш-транзакции-что-произошло). ## Сбои и async @@ -102,12 +105,12 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Один batch отправил `CreateAccount → Transfer → AddKey → FunctionCall`, и финальный вызов попал в отсутствующий метод. Индексированная запись транзакции уже несёт упорядоченный batch *и* точный сбой на уровне receipt, поэтому одного запроса хватает, чтобы ответить «что пытались и что сломалось»; проверка через `view_account` затем доказывает, что предыдущие actions откатились. ```bash -TX_BASE_URL=https://tx.test.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.test.fastnear.com/v0/transactions" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ @@ -129,7 +132,8 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Теперь докажите откат предыдущих actions: спросите аккаунт, который batch *пытался* создать: ```bash -curl -s "$RPC_URL" \ +curl -s "https://rpc.testnet.fastnear.com" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "query", @@ -145,12 +149,13 @@ curl -s "$RPC_URL" \ Внешний `execution_outcome.outcome.status` рапортует `SuccessReceiptId`, как только сработал handoff первого receipt, — и ничего не говорит о том, успешны ли дочерние receipts и отработал ли callback на исходном контракте. Один pipeline над `/v0/transactions` отвечает сразу на все три вопроса. ```bash -TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL ORIGIN_CONTRACT_ID=wrap.near CALLBACK_METHOD=ft_resolve_transfer +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ @@ -188,11 +193,12 @@ curl -s "$TX_BASE_URL/v0/transactions" \ [OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь подписывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, затем `worker.outlayer.near` присылает результат через `submit_execution_output_and_resolve`. Обе половины несут один и тот же `request_id` — передайте оба tx-хеша в один запрос `/v0/transactions` и извлеките это поле с каждой стороны, чтобы подтвердить пару. ```bash -TX_BASE_URL=https://tx.main.fastnear.com REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg a "$REQUEST_TX" --arg b "$WORKER_TX" '{tx_hashes: [$a, $b]}')" \ | jq '[ diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx index db53c66..17d23cf 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/socialdb-proofs.mdx @@ -13,31 +13,30 @@ page_actions: Для FastNear-first-задач сначала откройте [Transactions Examples](/tx/examples). Сюда переходите только тогда, когда вопрос звучит как «какая запись сделала это читаемое значение SocialDB истинным?». -## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` +## Канонический пример: доказать, что `root.near` установил `profile.name` в `Illia` -Используйте этот сценарий, когда читаемый факт уже звучит как «текущее `profile.name` равно `Mike Purvis`», а остаётся вопрос, какая запись сделала это поле истинным. +Используйте этот сценарий, когда читаемый факт уже звучит как «текущее `profile.name` равно `Illia`», а остаётся вопрос, какая запись сделала это поле истинным. Это единственный нюанс SocialDB, который стоит запомнить: для исторического доказательства правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. Для этого живого якоря: -- текущее `profile.name`: `Mike Purvis` -- блок записи SocialDB на уровне поля: `78675795` -- receipt ID: `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN` -- хеш исходной транзакции: `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ` -- внешний блок транзакции: `78675794` +- текущее `profile.name`: `Illia` +- блок записи SocialDB на уровне поля: `75590392` +- receipt ID: `GYvnvBxWA46UGa3aGEkqUBeT7hxhVXk2iZScJFZWU8Se` +- хеш исходной транзакции: `7HtFWv51k5Bispmh1WYPbAVkxr2X4AL6n98DhcQwVw7w` +- внешний блок транзакции: `75590391` ### Shell-сценарий 1. Прочитайте поле из NEAR Social и сохраните блок записи на уровне поля. ```bash -SOCIAL_API_BASE_URL=https://api.near.social -TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near PROFILE_FIELD=profile/name +FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} -PROFILE="$(curl -s "$SOCIAL_API_BASE_URL/get" \ +PROFILE="$(curl -s "https://api.near.social/get" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ keys: [($account_id + "/" + $profile_field)], @@ -56,7 +55,8 @@ PROFILE_BLOCK_HEIGHT="$(echo "$PROFILE" | jq -r --arg account_id "$ACCOUNT_ID" ' 2. Переиспользуйте этот блок уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. ```bash -BLOCK_RECEIPTS="$(curl -s "$TX_BASE_URL/v0/block" \ +BLOCK_RECEIPTS="$(curl -s "https://tx.main.fastnear.com/v0/block" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ block_id: $block_id, @@ -85,7 +85,8 @@ PROFILE_TX_HASH="$(echo "$BLOCK_RECEIPTS" | jq -r --arg account_id "$ACCOUNT_ID" 3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. ```bash -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg account_id "$ACCOUNT_ID" '{ @@ -101,8 +102,8 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | { method_name, profile_name: $profile.name, - description: $profile.description, - tags: ($profile.tags | keys) + image_fields: (($profile.image // {}) | keys), + linktree_keys: (($profile.linktree // {}) | keys) } ) }' @@ -112,7 +113,7 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Тот же мост работает и для других читаемых значений SocialDB: -- вариант для связи подписки: `mike.near -> mob.near`, блок `79574924`, tx `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- вариант для исходника виджета: `mob.near/widget/Profile`, блок `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- вариант для связи подписки: `root.near -> mob.near`, блок `79152039`, tx `DvNoqtDrruhmcq7mPpxdFacph2ZCqSzMFF5ZqMRFG78q` +- вариант для исходника виджета: `root.near/widget/Profile`, блок `76029540`, tx `ELS3DrE4Upoc91ZnBh4thVugxCUBAbaLFB4nyKsoyRNP` Ключевая идея не меняется: начните с читаемого значения и его write-block, восстановите receipt `*.near -> social.near` из блока, а затем декодируйте payload `social.near set` из исходной транзакции. From 286dc7765ad20f5f99e44d2f9b41a25d9a54a248 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Mon, 20 Apr 2026 11:16:27 -0700 Subject: [PATCH 31/35] Polish examples wording and KV intros --- docs/api/examples.md | 14 +++---- docs/fastdata/kv/examples.md | 41 +++++++++++++++++-- docs/transfers/examples.md | 6 +-- docs/tx/examples.md | 14 +++---- .../current/api/examples.md | 14 +++---- .../current/fastdata/kv/examples.md | 41 +++++++++++++++++-- .../current/transfers/examples.md | 6 +-- .../current/tx/examples.md | 14 +++---- 8 files changed, 110 insertions(+), 40 deletions(-) diff --git a/docs/api/examples.md b/docs/api/examples.md index 29b6228..b23f626 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -16,7 +16,7 @@ page_actions: ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -37,7 +37,7 @@ Look up which account a key belongs to, then read that account's holdings in one ```bash PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key LOOKUP="$(curl -s "https://api.fastnear.com/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')" \ -H "Authorization: Bearer $FASTNEAR_API_KEY")" @@ -59,7 +59,7 @@ NEAR account state has three buckets that wallet UIs tend to conflate: `balance` ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -92,7 +92,7 @@ Every entry under `/full`'s `tokens`, `nfts`, and `pools` arrays carries its own ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -114,7 +114,7 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ For `root.near`, this returns 254 total entries across FT, NFT, and pool contracts, 158 with a tracked block, and a most-recent block of `194301659`. That's enough to tell you the wallet is still active without touching transaction history. -This is the right question for "is this wallet abandoned?" or "has anything moved since block X?" — cheap, one call, no transaction history needed. For the transaction that caused the latest change, widen to the [Transactions API](/tx). Entries with `last_update_block_height: null` predate the indexer's per-row tracking (typically older airdrops) and are ignored here rather than counted as recent. +This is the right question for "has this wallet been recently active?" or "has anything moved since block X?" — cheap, one call, no transaction history needed. For the transaction that caused the latest change, widen to the [Transactions API](/tx). Entries with `last_update_block_height: null` predate the indexer's per-row tracking (typically older airdrops) and are ignored here rather than counted as recent. ### Show NFT collections this wallet holds from a specific publisher @@ -123,7 +123,7 @@ NEAR account names encode a hierarchy: `mint.sharddog.near` is a subaccount of ` ```bash ACCOUNT_ID=root.near PUBLISHER=sharddog.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -155,7 +155,7 @@ Direct pool positions live on `/staking`; liquid staking tokens (stNEAR, LiNEAR, ```bash ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key STAKING="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/staking" \ -H "Authorization: Bearer $FASTNEAR_API_KEY")" diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index ee0e2e5..be4bbc3 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -2,13 +2,48 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: KV FastData Examples -description: Task-first KV FastData examples for scoped writes, key history, and exact state checks. +description: Task-first KV FastData examples for exact-key checks, scoped writes, key history, and exact state follow-up. displayed_sidebar: kvFastDataSidebar page_actions: - markdown --- -## Example +## Examples + +### Check one exact key, then replay its history + +When you already know the contract, predecessor, and exact key, start narrow. `latest` answers the present-tense question; `history` shows whether that one row changed over time. + +```bash +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' +FASTNEAR_API_KEY=your_api_key + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +LATEST="$(curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY")" + +echo "$LATEST" | jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' + +curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + | jq '{writes: [.entries[] | {block_height, value}]}' +``` + +For an exact follow-edge style key like this, `latest` tells you the current indexed value in one row and `history` shows whether the edge was written once or toggled over time. Start here when you already know the storage path; widen to predecessor scans only when you need discovery rather than proof. ### Inspect one predecessor's indexed writes, then narrow to the key that changed @@ -16,7 +51,7 @@ page_actions: ```bash PREDECESSOR_ID=jemartel.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index 4389a9e..6897a44 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -16,7 +16,7 @@ page_actions: ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://transfers.main.fastnear.com/v0/transfers" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -42,7 +42,7 @@ For `root.near`, the latest rows mix `FtTransfer` and `MtTransfer` assets. `asse ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key FEED="$(curl -s "https://transfers.main.fastnear.com/v0/transfers" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -76,7 +76,7 @@ curl -s "https://tx.main.fastnear.com/v0/receipt" \ | jq '.receipt | {receipt_id, transaction_hash, receiver_id, predecessor_id, tx_block_height, is_success}' ``` -That's the same handoff covered in [Turn one ugly receipt ID from logs into a human story](/tx/examples#turn-one-ugly-receipt-id-from-logs-into-a-human-story) — one call gets the receipt and its full parent transaction. +That's the same handoff covered in [Turn one receipt ID into a readable transaction story](/tx/examples#receipt-id-to-readable-story) — one call gets the receipt and its full parent transaction. ## Common mistakes diff --git a/docs/tx/examples.md b/docs/tx/examples.md index a45a48f..5353666 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -16,7 +16,7 @@ Paste the hash into `POST /v0/transactions` and one response usually holds the w ```bash TX_HASH=7ZKnhzt2MqMNmsk13dV8GAjGu3Db8aHzSBHeNeu9MJCq -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://tx.main.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -42,7 +42,7 @@ List every logged receipt in the transaction with a flag for whether its logs co ```bash TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL LOG_FRAGMENT=Refund -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://tx.main.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -65,13 +65,13 @@ curl -s "https://tx.main.fastnear.com/v0/transactions" \ The `Refund` fragment attributes to receipt `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` on `wrap.near`, method `ft_resolve_transfer`. Receipt logs live on receipts, not on the transaction, so this single pass is enough — no deeper async trace needed. -### Turn one ugly receipt ID from logs into a human story +### Turn one receipt ID into a readable transaction story {#receipt-id-to-readable-story} `POST /v0/receipt` returns the receipt record **and** its full parent transaction in one response, so a single call covers the whole story — no follow-up `/v0/transactions` fetch needed. ```bash RECEIPT_ID=B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1 -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://tx.main.fastnear.com/v0/receipt" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -107,7 +107,7 @@ One batch submitted `CreateAccount → Transfer → AddKey → FunctionCall` and ```bash TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://tx.test.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -152,7 +152,7 @@ A tx's outer `execution_outcome.outcome.status` reports `SuccessReceiptId` whene TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL ORIGIN_CONTRACT_ID=wrap.near CALLBACK_METHOD=ft_resolve_transfer -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://tx.main.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -195,7 +195,7 @@ Receipt success is not transitive. A protocol can hand off cleanly and still see ```bash REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://tx.main.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index 2f0bb1e..3cb4cbe 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -16,7 +16,7 @@ page_actions: ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -37,7 +37,7 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ ```bash PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key LOOKUP="$(curl -s "https://api.fastnear.com/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')" \ -H "Authorization: Bearer $FASTNEAR_API_KEY")" @@ -59,7 +59,7 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -92,7 +92,7 @@ jq считает в IEEE-754 double, поэтому NEAR-значения вы ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -114,7 +114,7 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ Для `root.near` это возвращает 254 записи по FT-, NFT- и pool-контрактам, 158 с отслеживаемым блоком и самый свежий блок `194301659`. Этого уже достаточно, чтобы понять, что кошелёк живой, не заходя в историю транзакций. -Это правильный вопрос для «заброшен ли этот кошелёк?» или «двигалось ли что-то после блока X?» — дёшево, один запрос, без истории транзакций. Чтобы достать саму транзакцию, вызвавшую последнее изменение, расширяйте поверхность до [Transactions API](/tx). Записи с `last_update_block_height: null` относятся ко времени до per-row-отслеживания индексером (обычно старые airdrops) и здесь игнорируются, а не считаются свежими. +Это правильный вопрос для «был ли этот кошелёк недавно активен?» или «двигалось ли что-то после блока X?» — дёшево, один запрос, без истории транзакций. Чтобы достать саму транзакцию, вызвавшую последнее изменение, расширяйте поверхность до [Transactions API](/tx). Записи с `last_update_block_height: null` относятся ко времени до per-row-отслеживания индексером (обычно старые airdrops) и здесь игнорируются, а не считаются свежими. ### Показать NFT-коллекции этого кошелька от конкретного издателя @@ -123,7 +123,7 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ ```bash ACCOUNT_ID=root.near PUBLISHER=sharddog.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -155,7 +155,7 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ ```bash ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key STAKING="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/staking" \ -H "Authorization: Bearer $FASTNEAR_API_KEY")" diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index 7bd2451..d5330e7 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -2,13 +2,48 @@ sidebar_label: Examples slug: /fastdata/kv/examples title: "Примеры KV FastData" -description: "Практические примеры KV FastData: scoped-записи, история ключа и переход к точному состоянию." +description: "Практические примеры KV FastData: точные ключи, scoped-записи, история ключа и переход к точному состоянию." displayed_sidebar: kvFastDataSidebar page_actions: - markdown --- -## Пример +## Примеры + +### Проверить один точный ключ и сразу посмотреть его историю + +Если контракт, `predecessor_id` и точный ключ уже известны, начинайте с узкого запроса. `latest` отвечает на вопрос о текущем состоянии, а `history` показывает, менялась ли именно эта строка со временем. + +```bash +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' +FASTNEAR_API_KEY=your_api_key + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +LATEST="$(curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY")" + +echo "$LATEST" | jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' + +curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + | jq '{writes: [.entries[] | {block_height, value}]}' +``` + +Для точного ключа вроде этого follow-edge `latest` даёт текущее индексированное значение одной строкой, а `history` показывает, была ли запись однократной или переключалась со временем. Начинайте отсюда, когда путь в storage уже известен; расширяйтесь до выборок по `predecessor_id` только тогда, когда нужно не доказательство, а поиск. ### Посмотреть индексированные записи одного `predecessor_id` и сузиться до изменившегося ключа @@ -16,7 +51,7 @@ page_actions: ```bash PREDECESSOR_ID=jemartel.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md index cfb7271..268be78 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/transfers/examples.md @@ -16,7 +16,7 @@ page_actions: ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://transfers.main.fastnear.com/v0/transfers" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -42,7 +42,7 @@ curl -s "https://transfers.main.fastnear.com/v0/transfers" \ ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key FEED="$(curl -s "https://transfers.main.fastnear.com/v0/transfers" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -76,7 +76,7 @@ curl -s "https://tx.main.fastnear.com/v0/receipt" \ | jq '.receipt | {receipt_id, transaction_hash, receiver_id, predecessor_id, tx_block_height, is_success}' ``` -Это тот же переход, что описан в [Превратить один неказистый receipt ID из логов в человекочитаемую историю](/tx/examples#превратить-один-неказистый-receipt-id-из-логов-в-человекочитаемую-историю) — один запрос возвращает и квитанцию, и её родительскую транзакцию целиком. +Это тот же переход, что описан в [Превратить один receipt ID в читаемую историю транзакции](/tx/examples#receipt-id-to-readable-story) — один запрос возвращает и квитанцию, и её родительскую транзакцию целиком. ## Частые ошибки diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md index a11d2b2..db74e97 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/tx/examples.md @@ -16,7 +16,7 @@ page_actions: ```bash TX_HASH=7ZKnhzt2MqMNmsk13dV8GAjGu3Db8aHzSBHeNeu9MJCq -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://tx.main.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -42,7 +42,7 @@ curl -s "https://tx.main.fastnear.com/v0/transactions" \ ```bash TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL LOG_FRAGMENT=Refund -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://tx.main.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -65,13 +65,13 @@ curl -s "https://tx.main.fastnear.com/v0/transactions" \ Фрагмент `Refund` атрибутируется receipt `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` на `wrap.near`, метод `ft_resolve_transfer`. Логи receipt живут на receipts, а не на транзакции, поэтому одного прохода достаточно — более глубокая async-трассировка не нужна. -### Превратить один неказистый receipt ID из логов в человекочитаемую историю +### Превратить один receipt ID в читаемую историю транзакции {#receipt-id-to-readable-story} `POST /v0/receipt` возвращает запись receipt **и** его полную родительскую транзакцию в одном ответе, поэтому единственного запроса хватает на всю историю — дополнительный `/v0/transactions` не нужен. ```bash RECEIPT_ID=B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1 -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://tx.main.fastnear.com/v0/receipt" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -107,7 +107,7 @@ curl -s "https://tx.main.fastnear.com/v0/receipt" \ ```bash TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://tx.test.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -152,7 +152,7 @@ curl -s "https://rpc.testnet.fastnear.com" \ TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL ORIGIN_CONTRACT_ID=wrap.near CALLBACK_METHOD=ft_resolve_transfer -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://tx.main.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -195,7 +195,7 @@ curl -s "https://tx.main.fastnear.com/v0/transactions" \ ```bash REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://tx.main.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ From 05b6dfc14f980b53aadf2a26aa16f8101f9cd0c2 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Mon, 20 Apr 2026 11:24:38 -0700 Subject: [PATCH 32/35] Shorten API key placeholders in docs --- docs/agents/index.mdx | 4 ++-- docs/auth/index.mdx | 4 ++-- docs/fastdata/kv/index.md | 2 +- docs/neardata/examples.md | 8 ++++---- docs/rpc/examples.md | 14 +++++++------- docs/transaction-flow/finality.mdx | 2 +- docs/transaction-flow/reference.mdx | 10 +++++----- docs/transaction-flow/runtime-execution.mdx | 2 +- docs/tx/berry-club.mdx | 6 +++--- docs/tx/socialdb-proofs.mdx | 2 +- .../current/agents/index.mdx | 4 ++-- .../current/auth/index.mdx | 4 ++-- .../current/fastdata/kv/index.md | 2 +- .../current/neardata/examples.md | 8 ++++---- .../current/rpc/examples.md | 14 +++++++------- .../current/transaction-flow/finality.mdx | 2 +- .../current/transaction-flow/reference.mdx | 10 +++++----- .../current/transaction-flow/runtime-execution.mdx | 2 +- .../current/tx/berry-club.mdx | 6 +++--- .../current/tx/socialdb-proofs.mdx | 2 +- 20 files changed, 54 insertions(+), 54 deletions(-) diff --git a/docs/agents/index.mdx b/docs/agents/index.mdx index 2f08a8a..b0508d0 100644 --- a/docs/agents/index.mdx +++ b/docs/agents/index.mdx @@ -101,7 +101,7 @@ Bad pattern: 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" -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -110,7 +110,7 @@ curl "https://rpc.mainnet.fastnear.com" \ ``` ```bash title="URL parameter" -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" ``` diff --git a/docs/auth/index.mdx b/docs/auth/index.mdx index 2c54abf..7cfdd85 100644 --- a/docs/auth/index.mdx +++ b/docs/auth/index.mdx @@ -51,7 +51,7 @@ If you control the client, use the header form. ## Authorization header example ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -62,7 +62,7 @@ curl "https://rpc.mainnet.fastnear.com" \ ## `?apiKey=` query parameter example ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ diff --git a/docs/fastdata/kv/index.md b/docs/fastdata/kv/index.md index 53804f0..034c790 100644 --- a/docs/fastdata/kv/index.md +++ b/docs/fastdata/kv/index.md @@ -30,7 +30,7 @@ If you already know one exact key, start with the latest indexed row and stop as CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index e1915b7..eabe378 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -17,7 +17,7 @@ NEAR Data returns each block fully hydrated as one JSON document — header plus `/v0/last_block/final` 302-redirects to the current finalized block. Before filtering for a specific contract, it's worth seeing what one block looks like at the protocol level: transactions arrive sharded, so the tx count for a block is a sum across shards — not a single top-level number. ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -37,7 +37,7 @@ A live block shows 9 shards and a handful of transactions scattered across them ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -62,7 +62,7 @@ Optimistic blocks ship at `/v0/block_opt/{height}` about a second ahead of `/v0/ ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key count_touches() { jq --arg contract "$1" ' @@ -97,7 +97,7 @@ Most finalized blocks show no state mutation for any given contract — activity ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key HEAD="$(curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" | jq '.block.header.height')" diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index ca5947a..13c4a4a 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -20,7 +20,7 @@ Start with the RPC method that answers the question. Use `tx` to track inclusion ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -43,7 +43,7 @@ Have a tx hash? Poll `tx` with the smallest `wait_until` threshold that answers ```bash TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow SIGNER_ACCOUNT_ID=mike.testnet -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://archival-rpc.testnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -75,7 +75,7 @@ A NEAR block is a header over N shard chunks, not a flat list of transactions. ` ```bash EMPTY_TX_ROOT=11111111111111111111111111111111 -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key BLOCK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ @@ -131,7 +131,7 @@ Any key with `tx_count: 0` was created and never used — the clearest candidate ```bash ACCOUNT_ID=root.near RECEIVER_ID=social.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -172,7 +172,7 @@ Contracts built with `near-sdk-rs` store the top-level `#[near_bindgen]` struct ```bash CONTRACT_ID=counter.near-examples.testnet -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key RAW_B64="$(curl -s "https://rpc.testnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ @@ -201,7 +201,7 @@ These stay on exact SocialDB reads and on-chain readiness checks until the quest ```bash ACCOUNT_ID=root.near # account you're writing under SIGNER_ACCOUNT_ID=root.near # account signing the transaction -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" @@ -243,7 +243,7 @@ SocialDB stores BOS widgets as `/widget/` keys on `social.near`. ```bash ACCOUNT_ID=mob.near WIDGET_NAME=Profile -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key KEYS_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" '{ keys: [($account_id + "/widget/*")], diff --git a/docs/transaction-flow/finality.mdx b/docs/transaction-flow/finality.mdx index 89db6dc..28b9b33 100644 --- a/docs/transaction-flow/finality.mdx +++ b/docs/transaction-flow/finality.mdx @@ -132,7 +132,7 @@ pub enum FinalExecutionStatus { ### The `tx` RPC Method ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -X POST https://archival-rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/docs/transaction-flow/reference.mdx b/docs/transaction-flow/reference.mdx index 2515d07..8083782 100644 --- a/docs/transaction-flow/reference.mdx +++ b/docs/transaction-flow/reference.mdx @@ -178,7 +178,7 @@ You never "call" another contract. You send them a letter and ask them to send o ```bash # Submit and get hash immediately -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -X POST https://rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -197,7 +197,7 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Submit Transaction (Wait for Final) ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -X POST https://rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -213,7 +213,7 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Check Transaction Status ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -X POST https://archival-rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -233,7 +233,7 @@ curl -X POST https://archival-rpc.mainnet.fastnear.com \ #### View Access Key (for nonce) ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -X POST https://rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -257,7 +257,7 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Get Recent Block Hash ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -X POST https://rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/docs/transaction-flow/runtime-execution.mdx b/docs/transaction-flow/runtime-execution.mdx index 737e094..e9b4142 100644 --- a/docs/transaction-flow/runtime-execution.mdx +++ b/docs/transaction-flow/runtime-execution.mdx @@ -387,7 +387,7 @@ flowchart TD Clients can poll the `tx` RPC method with `wait_until: FINAL` to get all outcomes: ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -X POST https://archival-rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/docs/tx/berry-club.mdx b/docs/tx/berry-club.mdx index 334d424..97a993f 100644 --- a/docs/tx/berry-club.mdx +++ b/docs/tx/berry-club.mdx @@ -50,7 +50,7 @@ This is the shortest useful read: ```bash ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -sS https://rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -83,7 +83,7 @@ When you need history, keep the flow short: This example uses a narrow window around block `97601515`: ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -sS https://tx.main.fastnear.com/v0/account \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -105,7 +105,7 @@ If you do not know the window yet, `/v0/blocks` can Hydrate the candidate hashes and keep only top-level `draw` calls: ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -sS https://tx.main.fastnear.com/v0/transactions \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/docs/tx/socialdb-proofs.mdx b/docs/tx/socialdb-proofs.mdx index d57837d..b6e5bdc 100644 --- a/docs/tx/socialdb-proofs.mdx +++ b/docs/tx/socialdb-proofs.mdx @@ -34,7 +34,7 @@ For this live anchor: ```bash ACCOUNT_ID=root.near PROFILE_FIELD=profile/name -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key PROFILE="$(curl -s "https://api.near.social/get" \ -H 'content-type: application/json' \ 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 0b64405..1d3f0cf 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/agents/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/agents/index.mdx @@ -101,7 +101,7 @@ page_actions: Публичные эндпоинты часто работают и без ключа. Добавьте ключ, если нужны повышенные лимиты, единая аутентифицированная модель или платные сценарии. Один и тот же ключ работает со всеми API FastNear выше, включая обычные и архивные RPC-хосты; передавайте его либо в HTTP-заголовке, либо в URL-параметре: ```bash title="Заголовок Authorization" -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -110,7 +110,7 @@ curl "https://rpc.mainnet.fastnear.com" \ ``` ```bash title="URL-параметр" -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" ``` diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx index 855321c..b4ef18b 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx @@ -43,7 +43,7 @@ import IconExternalLink from '@theme/Icon/ExternalLink'; ## Через заголовок Authorization ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -54,7 +54,7 @@ curl "https://rpc.mainnet.fastnear.com" \ ## Через URL-параметр `?apiKey=` ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md index 0c972ad..618ccb5 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md @@ -30,7 +30,7 @@ https://kv.test.fastnear.com CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index a41e7fe..6349c0d 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -17,7 +17,7 @@ NEAR Data возвращает каждый блок полностью гидр `/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Прежде чем фильтровать по конкретному контракту, полезно увидеть, как выглядит один блок на уровне протокола: транзакции приходят с разбивкой по shard, поэтому общее число транзакций в блоке — это сумма по shards, а не одно поле верхнего уровня. ```bash -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -37,7 +37,7 @@ curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -62,7 +62,7 @@ Optimistic-блоки ходят по `/v0/block_opt/{height}` примерно ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key count_touches() { jq --arg contract "$1" ' @@ -97,7 +97,7 @@ fi ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key HEAD="$(curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" | jq '.block.header.height')" diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index 419b94c..3c10d09 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -20,7 +20,7 @@ page_actions: ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -43,7 +43,7 @@ curl -s "https://rpc.mainnet.fastnear.com" \ ```bash TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow SIGNER_ACCOUNT_ID=mike.testnet -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://archival-rpc.testnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -75,7 +75,7 @@ curl -s "https://archival-rpc.testnet.fastnear.com" \ ```bash EMPTY_TX_ROOT=11111111111111111111111111111111 -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key BLOCK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ @@ -131,7 +131,7 @@ fi ```bash ACCOUNT_ID=root.near RECEIVER_ID=social.near -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key curl -s "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -172,7 +172,7 @@ View-метод вроде `get_num` всё равно заставляет уз ```bash CONTRACT_ID=counter.near-examples.testnet -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key RAW_B64="$(curl -s "https://rpc.testnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ @@ -201,7 +201,7 @@ jq -n --arg raw "$RAW_B64" --argjson val "$DECODED_I8" '{raw_bytes_base64: $raw, ```bash ACCOUNT_ID=root.near # account you're writing under SIGNER_ACCOUNT_ID=root.near # account signing the transaction -FASTNEAR_API_KEY=${FASTNEAR_API_KEY:-your_api_key_here} +FASTNEAR_API_KEY=your_api_key STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" @@ -243,7 +243,7 @@ SocialDB хранит BOS-виджеты как ключи `/widget/ Date: Mon, 20 Apr 2026 11:56:49 -0700 Subject: [PATCH 33/35] better to have FASTNEAR_API_KEY= (nothing) so it doesn't trigger cf stuff --- docs/agents/index.mdx | 4 ++-- docs/api/examples.md | 12 ++++++------ docs/auth/index.mdx | 4 ++-- docs/fastdata/kv/examples.md | 4 ++-- docs/fastdata/kv/index.md | 2 +- docs/neardata/examples.md | 8 ++++---- docs/rpc/examples.md | 14 +++++++------- docs/transaction-flow/finality.mdx | 2 +- docs/transaction-flow/reference.mdx | 10 +++++----- docs/transaction-flow/runtime-execution.mdx | 2 +- docs/transfers/examples.md | 4 ++-- docs/tx/berry-club.mdx | 6 +++--- docs/tx/examples.md | 12 ++++++------ docs/tx/socialdb-proofs.mdx | 2 +- .../current/agents/index.mdx | 4 ++-- .../current/api/examples.md | 12 ++++++------ .../current/auth/index.mdx | 4 ++-- .../current/fastdata/kv/examples.md | 4 ++-- .../current/fastdata/kv/index.md | 2 +- .../current/neardata/examples.md | 8 ++++---- .../current/rpc/examples.md | 14 +++++++------- .../current/transaction-flow/finality.mdx | 2 +- .../current/transaction-flow/reference.mdx | 10 +++++----- .../current/transaction-flow/runtime-execution.mdx | 2 +- .../current/transfers/examples.md | 4 ++-- .../current/tx/berry-club.mdx | 6 +++--- .../current/tx/examples.md | 12 ++++++------ .../current/tx/socialdb-proofs.mdx | 2 +- 28 files changed, 86 insertions(+), 86 deletions(-) diff --git a/docs/agents/index.mdx b/docs/agents/index.mdx index b0508d0..15ef0f1 100644 --- a/docs/agents/index.mdx +++ b/docs/agents/index.mdx @@ -101,7 +101,7 @@ Bad pattern: 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" -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -110,7 +110,7 @@ curl "https://rpc.mainnet.fastnear.com" \ ``` ```bash title="URL parameter" -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" ``` diff --git a/docs/api/examples.md b/docs/api/examples.md index b23f626..02ad053 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -16,7 +16,7 @@ page_actions: ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -37,7 +37,7 @@ Look up which account a key belongs to, then read that account's holdings in one ```bash PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= LOOKUP="$(curl -s "https://api.fastnear.com/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')" \ -H "Authorization: Bearer $FASTNEAR_API_KEY")" @@ -59,7 +59,7 @@ NEAR account state has three buckets that wallet UIs tend to conflate: `balance` ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -92,7 +92,7 @@ Every entry under `/full`'s `tokens`, `nfts`, and `pools` arrays carries its own ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -123,7 +123,7 @@ NEAR account names encode a hierarchy: `mint.sharddog.near` is a subaccount of ` ```bash ACCOUNT_ID=root.near PUBLISHER=sharddog.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -155,7 +155,7 @@ Direct pool positions live on `/staking`; liquid staking tokens (stNEAR, LiNEAR, ```bash ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= STAKING="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/staking" \ -H "Authorization: Bearer $FASTNEAR_API_KEY")" diff --git a/docs/auth/index.mdx b/docs/auth/index.mdx index 7cfdd85..59b25fc 100644 --- a/docs/auth/index.mdx +++ b/docs/auth/index.mdx @@ -51,7 +51,7 @@ If you control the client, use the header form. ## Authorization header example ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -62,7 +62,7 @@ curl "https://rpc.mainnet.fastnear.com" \ ## `?apiKey=` query parameter example ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index be4bbc3..11366ee 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -18,7 +18,7 @@ When you already know the contract, predecessor, and exact key, start narrow. `l CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" @@ -51,7 +51,7 @@ For an exact follow-edge style key like this, `latest` tells you the current ind ```bash PREDECESSOR_ID=jemartel.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/docs/fastdata/kv/index.md b/docs/fastdata/kv/index.md index 034c790..43c453e 100644 --- a/docs/fastdata/kv/index.md +++ b/docs/fastdata/kv/index.md @@ -30,7 +30,7 @@ If you already know one exact key, start with the latest indexed row and stop as CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index eabe378..43e7fce 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -17,7 +17,7 @@ NEAR Data returns each block fully hydrated as one JSON document — header plus `/v0/last_block/final` 302-redirects to the current finalized block. Before filtering for a specific contract, it's worth seeing what one block looks like at the protocol level: transactions arrive sharded, so the tx count for a block is a sum across shards — not a single top-level number. ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -37,7 +37,7 @@ A live block shows 9 shards and a handful of transactions scattered across them ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -62,7 +62,7 @@ Optimistic blocks ship at `/v0/block_opt/{height}` about a second ahead of `/v0/ ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= count_touches() { jq --arg contract "$1" ' @@ -97,7 +97,7 @@ Most finalized blocks show no state mutation for any given contract — activity ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= HEAD="$(curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" | jq '.block.header.height')" diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index 13c4a4a..bc3c851 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -20,7 +20,7 @@ Start with the RPC method that answers the question. Use `tx` to track inclusion ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -43,7 +43,7 @@ Have a tx hash? Poll `tx` with the smallest `wait_until` threshold that answers ```bash TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow SIGNER_ACCOUNT_ID=mike.testnet -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://archival-rpc.testnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -75,7 +75,7 @@ A NEAR block is a header over N shard chunks, not a flat list of transactions. ` ```bash EMPTY_TX_ROOT=11111111111111111111111111111111 -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= BLOCK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ @@ -131,7 +131,7 @@ Any key with `tx_count: 0` was created and never used — the clearest candidate ```bash ACCOUNT_ID=root.near RECEIVER_ID=social.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -172,7 +172,7 @@ Contracts built with `near-sdk-rs` store the top-level `#[near_bindgen]` struct ```bash CONTRACT_ID=counter.near-examples.testnet -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= RAW_B64="$(curl -s "https://rpc.testnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ @@ -201,7 +201,7 @@ These stay on exact SocialDB reads and on-chain readiness checks until the quest ```bash ACCOUNT_ID=root.near # account you're writing under SIGNER_ACCOUNT_ID=root.near # account signing the transaction -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" @@ -243,7 +243,7 @@ SocialDB stores BOS widgets as `/widget/` keys on `social.near`. ```bash ACCOUNT_ID=mob.near WIDGET_NAME=Profile -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= KEYS_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" '{ keys: [($account_id + "/widget/*")], diff --git a/docs/transaction-flow/finality.mdx b/docs/transaction-flow/finality.mdx index 28b9b33..631f784 100644 --- a/docs/transaction-flow/finality.mdx +++ b/docs/transaction-flow/finality.mdx @@ -132,7 +132,7 @@ pub enum FinalExecutionStatus { ### The `tx` RPC Method ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -X POST https://archival-rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/docs/transaction-flow/reference.mdx b/docs/transaction-flow/reference.mdx index 8083782..7f8b800 100644 --- a/docs/transaction-flow/reference.mdx +++ b/docs/transaction-flow/reference.mdx @@ -178,7 +178,7 @@ You never "call" another contract. You send them a letter and ask them to send o ```bash # Submit and get hash immediately -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -X POST https://rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -197,7 +197,7 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Submit Transaction (Wait for Final) ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -X POST https://rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -213,7 +213,7 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Check Transaction Status ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -X POST https://archival-rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -233,7 +233,7 @@ curl -X POST https://archival-rpc.mainnet.fastnear.com \ #### View Access Key (for nonce) ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -X POST https://rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -257,7 +257,7 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Get Recent Block Hash ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -X POST https://rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/docs/transaction-flow/runtime-execution.mdx b/docs/transaction-flow/runtime-execution.mdx index e9b4142..12c5368 100644 --- a/docs/transaction-flow/runtime-execution.mdx +++ b/docs/transaction-flow/runtime-execution.mdx @@ -387,7 +387,7 @@ flowchart TD Clients can poll the `tx` RPC method with `wait_until: FINAL` to get all outcomes: ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -X POST https://archival-rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index 6897a44..f7a7fe9 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -16,7 +16,7 @@ page_actions: ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://transfers.main.fastnear.com/v0/transfers" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -42,7 +42,7 @@ For `root.near`, the latest rows mix `FtTransfer` and `MtTransfer` assets. `asse ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= FEED="$(curl -s "https://transfers.main.fastnear.com/v0/transfers" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/docs/tx/berry-club.mdx b/docs/tx/berry-club.mdx index 97a993f..51552e1 100644 --- a/docs/tx/berry-club.mdx +++ b/docs/tx/berry-club.mdx @@ -50,7 +50,7 @@ This is the shortest useful read: ```bash ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -sS https://rpc.mainnet.fastnear.com \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -83,7 +83,7 @@ When you need history, keep the flow short: This example uses a narrow window around block `97601515`: ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -sS https://tx.main.fastnear.com/v0/account \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -105,7 +105,7 @@ If you do not know the window yet, `/v0/blocks` can Hydrate the candidate hashes and keep only top-level `draw` calls: ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -sS https://tx.main.fastnear.com/v0/transactions \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/docs/tx/examples.md b/docs/tx/examples.md index 5353666..396ca87 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -16,7 +16,7 @@ Paste the hash into `POST /v0/transactions` and one response usually holds the w ```bash TX_HASH=7ZKnhzt2MqMNmsk13dV8GAjGu3Db8aHzSBHeNeu9MJCq -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://tx.main.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -42,7 +42,7 @@ List every logged receipt in the transaction with a flag for whether its logs co ```bash TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL LOG_FRAGMENT=Refund -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://tx.main.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -71,7 +71,7 @@ The `Refund` fragment attributes to receipt `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2ot ```bash RECEIPT_ID=B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1 -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://tx.main.fastnear.com/v0/receipt" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -107,7 +107,7 @@ One batch submitted `CreateAccount → Transfer → AddKey → FunctionCall` and ```bash TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://tx.test.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -152,7 +152,7 @@ A tx's outer `execution_outcome.outcome.status` reports `SuccessReceiptId` whene TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL ORIGIN_CONTRACT_ID=wrap.near CALLBACK_METHOD=ft_resolve_transfer -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://tx.main.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -195,7 +195,7 @@ Receipt success is not transitive. A protocol can hand off cleanly and still see ```bash REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://tx.main.fastnear.com/v0/transactions" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/docs/tx/socialdb-proofs.mdx b/docs/tx/socialdb-proofs.mdx index b6e5bdc..fcee464 100644 --- a/docs/tx/socialdb-proofs.mdx +++ b/docs/tx/socialdb-proofs.mdx @@ -34,7 +34,7 @@ For this live anchor: ```bash ACCOUNT_ID=root.near PROFILE_FIELD=profile/name -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= PROFILE="$(curl -s "https://api.near.social/get" \ -H 'content-type: application/json' \ 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 1d3f0cf..1a7520b 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/agents/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/agents/index.mdx @@ -101,7 +101,7 @@ page_actions: Публичные эндпоинты часто работают и без ключа. Добавьте ключ, если нужны повышенные лимиты, единая аутентифицированная модель или платные сценарии. Один и тот же ключ работает со всеми API FastNear выше, включая обычные и архивные RPC-хосты; передавайте его либо в HTTP-заголовке, либо в URL-параметре: ```bash title="Заголовок Authorization" -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -110,7 +110,7 @@ curl "https://rpc.mainnet.fastnear.com" \ ``` ```bash title="URL-параметр" -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" ``` diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index 3cb4cbe..d381fa5 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -16,7 +16,7 @@ page_actions: ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -37,7 +37,7 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ ```bash PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= LOOKUP="$(curl -s "https://api.fastnear.com/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')" \ -H "Authorization: Bearer $FASTNEAR_API_KEY")" @@ -59,7 +59,7 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -92,7 +92,7 @@ jq считает в IEEE-754 double, поэтому NEAR-значения вы ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -123,7 +123,7 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ ```bash ACCOUNT_ID=root.near PUBLISHER=sharddog.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -155,7 +155,7 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ ```bash ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= STAKING="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/staking" \ -H "Authorization: Bearer $FASTNEAR_API_KEY")" diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx index b4ef18b..3a75532 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx @@ -43,7 +43,7 @@ import IconExternalLink from '@theme/Icon/ExternalLink'; ## Через заголовок Authorization ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -54,7 +54,7 @@ curl "https://rpc.mainnet.fastnear.com" \ ## Через URL-параметр `?apiKey=` ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index d5330e7..db241ec 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -18,7 +18,7 @@ page_actions: CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" @@ -51,7 +51,7 @@ curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSO ```bash PREDECESSOR_ID=jemartel.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md index 618ccb5..c393264 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md @@ -30,7 +30,7 @@ https://kv.test.fastnear.com CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index 6349c0d..8475604 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -17,7 +17,7 @@ NEAR Data возвращает каждый блок полностью гидр `/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Прежде чем фильтровать по конкретному контракту, полезно увидеть, как выглядит один блок на уровне протокола: транзакции приходят с разбивкой по shard, поэтому общее число транзакций в блоке — это сумма по shards, а не одно поле верхнего уровня. ```bash -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -37,7 +37,7 @@ curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -62,7 +62,7 @@ Optimistic-блоки ходят по `/v0/block_opt/{height}` примерно ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= count_touches() { jq --arg contract "$1" ' @@ -97,7 +97,7 @@ fi ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= HEAD="$(curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" | jq '.block.header.height')" diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index 3c10d09..6a2c156 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -20,7 +20,7 @@ page_actions: ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -43,7 +43,7 @@ curl -s "https://rpc.mainnet.fastnear.com" \ ```bash TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow SIGNER_ACCOUNT_ID=mike.testnet -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://archival-rpc.testnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -75,7 +75,7 @@ curl -s "https://archival-rpc.testnet.fastnear.com" \ ```bash EMPTY_TX_ROOT=11111111111111111111111111111111 -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= BLOCK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ @@ -131,7 +131,7 @@ fi ```bash ACCOUNT_ID=root.near RECEIVER_ID=social.near -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= curl -s "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -172,7 +172,7 @@ View-метод вроде `get_num` всё равно заставляет уз ```bash CONTRACT_ID=counter.near-examples.testnet -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= RAW_B64="$(curl -s "https://rpc.testnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ @@ -201,7 +201,7 @@ jq -n --arg raw "$RAW_B64" --argjson val "$DECODED_I8" '{raw_bytes_base64: $raw, ```bash ACCOUNT_ID=root.near # account you're writing under SIGNER_ACCOUNT_ID=root.near # account signing the transaction -FASTNEAR_API_KEY=your_api_key +FASTNEAR_API_KEY= STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" @@ -243,7 +243,7 @@ SocialDB хранит BOS-виджеты как ключи `/widget/ Date: Mon, 20 Apr 2026 11:57:20 -0700 Subject: [PATCH 34/35] add rest of the markdown describing basics of fastnear --- scripts/generate-ai-surfaces.js | 48 +- src/data/fastnearAiMarkdownFooter.json | 20 + src/theme/DocItem/Content/index.js | 3 +- src/utils/markdownExport.js | 29 +- static/internationalization/index.md | 7 + static/ru/agents.md | 7 + static/ru/agents/auth.md | 7 + static/ru/agents/auth/index.md | 7 + static/ru/agents/choosing-surfaces.md | 7 + static/ru/agents/choosing-surfaces/index.md | 7 + static/ru/agents/index.md | 7 + static/ru/agents/playbooks.md | 7 + static/ru/agents/playbooks/index.md | 7 + static/ru/api.md | 7 + static/ru/api/examples.md | 119 ++++ static/ru/api/examples/index.md | 119 ++++ static/ru/api/index.md | 7 + static/ru/api/reference.md | 7 + static/ru/api/reference/index.md | 7 + static/ru/api/system/health.md | 7 + static/ru/api/system/health/index.md | 7 + static/ru/api/system/status.md | 7 + static/ru/api/system/status/index.md | 7 + static/ru/api/v0/account-ft.md | 7 + static/ru/api/v0/account-ft/index.md | 7 + static/ru/api/v0/account-nft.md | 7 + static/ru/api/v0/account-nft/index.md | 7 + static/ru/api/v0/account-staking.md | 7 + static/ru/api/v0/account-staking/index.md | 7 + static/ru/api/v0/public-key-all.md | 7 + static/ru/api/v0/public-key-all/index.md | 7 + static/ru/api/v0/public-key.md | 7 + static/ru/api/v0/public-key/index.md | 7 + static/ru/api/v1/account-ft.md | 7 + static/ru/api/v1/account-ft/index.md | 7 + static/ru/api/v1/account-full.md | 7 + static/ru/api/v1/account-full/index.md | 7 + static/ru/api/v1/account-nft.md | 7 + static/ru/api/v1/account-nft/index.md | 7 + static/ru/api/v1/account-staking.md | 7 + static/ru/api/v1/account-staking/index.md | 7 + static/ru/api/v1/ft-top.md | 7 + static/ru/api/v1/ft-top/index.md | 7 + static/ru/api/v1/public-key-all.md | 7 + static/ru/api/v1/public-key-all/index.md | 7 + static/ru/api/v1/public-key.md | 7 + static/ru/api/v1/public-key/index.md | 7 + static/ru/apis/fastnear/system/health.md | 7 + .../ru/apis/fastnear/system/health/index.md | 7 + static/ru/apis/fastnear/system/status.md | 7 + .../ru/apis/fastnear/system/status/index.md | 7 + static/ru/apis/fastnear/v0/account_ft.md | 7 + .../ru/apis/fastnear/v0/account_ft/index.md | 7 + static/ru/apis/fastnear/v0/account_nft.md | 7 + .../ru/apis/fastnear/v0/account_nft/index.md | 7 + static/ru/apis/fastnear/v0/account_staking.md | 7 + .../apis/fastnear/v0/account_staking/index.md | 7 + .../ru/apis/fastnear/v0/public_key_lookup.md | 7 + .../fastnear/v0/public_key_lookup/index.md | 7 + .../apis/fastnear/v0/public_key_lookup_all.md | 7 + .../v0/public_key_lookup_all/index.md | 7 + static/ru/apis/fastnear/v1/account_ft.md | 7 + .../ru/apis/fastnear/v1/account_ft/index.md | 7 + static/ru/apis/fastnear/v1/account_full.md | 7 + .../ru/apis/fastnear/v1/account_full/index.md | 7 + static/ru/apis/fastnear/v1/account_nft.md | 7 + .../ru/apis/fastnear/v1/account_nft/index.md | 7 + static/ru/apis/fastnear/v1/account_staking.md | 7 + .../apis/fastnear/v1/account_staking/index.md | 7 + static/ru/apis/fastnear/v1/ft_top.md | 7 + static/ru/apis/fastnear/v1/ft_top/index.md | 7 + .../ru/apis/fastnear/v1/public_key_lookup.md | 7 + .../fastnear/v1/public_key_lookup/index.md | 7 + .../apis/fastnear/v1/public_key_lookup_all.md | 7 + .../v1/public_key_lookup_all/index.md | 7 + .../apis/kv-fastdata/v0/all_by_predecessor.md | 7 + .../v0/all_by_predecessor/index.md | 7 + .../ru/apis/kv-fastdata/v0/get_history_key.md | 7 + .../kv-fastdata/v0/get_history_key/index.md | 7 + .../ru/apis/kv-fastdata/v0/get_latest_key.md | 7 + .../kv-fastdata/v0/get_latest_key/index.md | 7 + .../apis/kv-fastdata/v0/history_by_account.md | 7 + .../v0/history_by_account/index.md | 7 + .../ru/apis/kv-fastdata/v0/history_by_key.md | 7 + .../kv-fastdata/v0/history_by_key/index.md | 7 + .../kv-fastdata/v0/history_by_predecessor.md | 7 + .../v0/history_by_predecessor/index.md | 7 + .../apis/kv-fastdata/v0/latest_by_account.md | 7 + .../kv-fastdata/v0/latest_by_account/index.md | 7 + .../kv-fastdata/v0/latest_by_predecessor.md | 7 + .../v0/latest_by_predecessor/index.md | 7 + static/ru/apis/kv-fastdata/v0/multi.md | 7 + static/ru/apis/kv-fastdata/v0/multi/index.md | 7 + static/ru/apis/neardata/system/health.md | 7 + .../ru/apis/neardata/system/health/index.md | 7 + static/ru/apis/neardata/v0/block.md | 7 + static/ru/apis/neardata/v0/block/index.md | 7 + static/ru/apis/neardata/v0/block_chunk.md | 7 + .../ru/apis/neardata/v0/block_chunk/index.md | 7 + static/ru/apis/neardata/v0/block_headers.md | 7 + .../apis/neardata/v0/block_headers/index.md | 7 + .../ru/apis/neardata/v0/block_optimistic.md | 7 + .../neardata/v0/block_optimistic/index.md | 7 + static/ru/apis/neardata/v0/block_shard.md | 7 + .../ru/apis/neardata/v0/block_shard/index.md | 7 + static/ru/apis/neardata/v0/first_block.md | 7 + .../ru/apis/neardata/v0/first_block/index.md | 7 + .../ru/apis/neardata/v0/last_block_final.md | 7 + .../neardata/v0/last_block_final/index.md | 7 + .../apis/neardata/v0/last_block_optimistic.md | 7 + .../v0/last_block_optimistic/index.md | 7 + static/ru/apis/transactions/v0/account.md | 7 + .../ru/apis/transactions/v0/account/index.md | 7 + static/ru/apis/transactions/v0/block.md | 7 + static/ru/apis/transactions/v0/block/index.md | 7 + static/ru/apis/transactions/v0/blocks.md | 7 + .../ru/apis/transactions/v0/blocks/index.md | 7 + static/ru/apis/transactions/v0/receipt.md | 7 + .../ru/apis/transactions/v0/receipt/index.md | 7 + .../ru/apis/transactions/v0/transactions.md | 7 + .../transactions/v0/transactions/index.md | 7 + static/ru/apis/transfers/v0/transfers.md | 7 + .../ru/apis/transfers/v0/transfers/index.md | 7 + static/ru/auth.md | 7 + static/ru/auth/index.md | 7 + static/ru/fastdata/kv.md | 7 + static/ru/fastdata/kv/all-by-predecessor.md | 7 + .../fastdata/kv/all-by-predecessor/index.md | 7 + static/ru/fastdata/kv/examples.md | 7 + static/ru/fastdata/kv/examples/index.md | 7 + static/ru/fastdata/kv/get-history-key.md | 7 + .../ru/fastdata/kv/get-history-key/index.md | 7 + static/ru/fastdata/kv/get-latest-key.md | 7 + static/ru/fastdata/kv/get-latest-key/index.md | 7 + static/ru/fastdata/kv/history-by-account.md | 7 + .../fastdata/kv/history-by-account/index.md | 7 + static/ru/fastdata/kv/history-by-key.md | 7 + static/ru/fastdata/kv/history-by-key/index.md | 7 + .../ru/fastdata/kv/history-by-predecessor.md | 7 + .../kv/history-by-predecessor/index.md | 7 + static/ru/fastdata/kv/index.md | 7 + static/ru/fastdata/kv/latest-by-account.md | 7 + .../ru/fastdata/kv/latest-by-account/index.md | 7 + .../ru/fastdata/kv/latest-by-predecessor.md | 7 + .../kv/latest-by-predecessor/index.md | 7 + static/ru/fastdata/kv/multi.md | 7 + static/ru/fastdata/kv/multi/index.md | 7 + static/ru/index.md | 7 + static/ru/internationalization.md | 7 + static/ru/internationalization/index.md | 7 + static/ru/llms-full.txt | 528 +++++++++--------- static/ru/neardata.md | 7 + static/ru/neardata/block-chunk.md | 7 + static/ru/neardata/block-chunk/index.md | 7 + static/ru/neardata/block-headers.md | 7 + static/ru/neardata/block-headers/index.md | 7 + static/ru/neardata/block-optimistic.md | 7 + static/ru/neardata/block-optimistic/index.md | 7 + static/ru/neardata/block-shard.md | 7 + static/ru/neardata/block-shard/index.md | 7 + static/ru/neardata/block.md | 7 + static/ru/neardata/block/index.md | 7 + static/ru/neardata/examples.md | 150 ++--- static/ru/neardata/examples/index.md | 150 ++--- static/ru/neardata/first-block.md | 7 + static/ru/neardata/first-block/index.md | 7 + static/ru/neardata/index.md | 7 + static/ru/neardata/last-block-final.md | 7 + static/ru/neardata/last-block-final/index.md | 7 + static/ru/neardata/last-block-optimistic.md | 7 + .../neardata/last-block-optimistic/index.md | 7 + static/ru/neardata/system/health.md | 7 + static/ru/neardata/system/health/index.md | 7 + static/ru/redocly-config.md | 7 + static/ru/redocly-config/index.md | 7 + static/ru/rpc.md | 7 + static/ru/rpc/account/view-access-key-list.md | 7 + .../rpc/account/view-access-key-list/index.md | 7 + static/ru/rpc/account/view-access-key.md | 7 + .../ru/rpc/account/view-access-key/index.md | 7 + static/ru/rpc/account/view-account.md | 7 + static/ru/rpc/account/view-account/index.md | 7 + static/ru/rpc/block/block-by-height.md | 7 + static/ru/rpc/block/block-by-height/index.md | 7 + static/ru/rpc/block/block-by-id.md | 7 + static/ru/rpc/block/block-by-id/index.md | 7 + static/ru/rpc/block/block-effects.md | 7 + static/ru/rpc/block/block-effects/index.md | 7 + static/ru/rpc/contract/call-function.md | 7 + static/ru/rpc/contract/call-function/index.md | 7 + static/ru/rpc/contract/view-code.md | 7 + static/ru/rpc/contract/view-code/index.md | 7 + ...view-global-contract-code-by-account-id.md | 7 + .../index.md | 7 + .../rpc/contract/view-global-contract-code.md | 7 + .../view-global-contract-code/index.md | 7 + static/ru/rpc/contract/view-state.md | 7 + static/ru/rpc/contract/view-state/index.md | 7 + static/ru/rpc/examples.md | 179 ++---- static/ru/rpc/examples/index.md | 179 ++---- static/ru/rpc/index.md | 7 + static/ru/rpc/protocol/changes.md | 7 + static/ru/rpc/protocol/changes/index.md | 7 + .../ru/rpc/protocol/chunk-by-block-shard.md | 7 + .../protocol/chunk-by-block-shard/index.md | 7 + static/ru/rpc/protocol/chunk-by-hash.md | 7 + static/ru/rpc/protocol/chunk-by-hash/index.md | 7 + static/ru/rpc/protocol/client-config.md | 7 + static/ru/rpc/protocol/client-config/index.md | 7 + .../protocol/experimental-congestion-level.md | 7 + .../experimental-congestion-level/index.md | 7 + .../experimental-light-client-block-proof.md | 7 + .../index.md | 7 + .../experimental-light-client-proof.md | 7 + .../experimental-light-client-proof/index.md | 7 + .../protocol/experimental-protocol-config.md | 7 + .../experimental-protocol-config/index.md | 7 + .../experimental-split-storage-info.md | 7 + .../experimental-split-storage-info/index.md | 7 + static/ru/rpc/protocol/gas-price-by-block.md | 7 + .../rpc/protocol/gas-price-by-block/index.md | 7 + static/ru/rpc/protocol/gas-price.md | 7 + static/ru/rpc/protocol/gas-price/index.md | 7 + static/ru/rpc/protocol/genesis-config.md | 7 + .../ru/rpc/protocol/genesis-config/index.md | 7 + static/ru/rpc/protocol/health.md | 7 + static/ru/rpc/protocol/health/index.md | 7 + static/ru/rpc/protocol/latest-block.md | 7 + static/ru/rpc/protocol/latest-block/index.md | 7 + static/ru/rpc/protocol/light-client-proof.md | 7 + .../rpc/protocol/light-client-proof/index.md | 7 + static/ru/rpc/protocol/maintenance-windows.md | 7 + .../rpc/protocol/maintenance-windows/index.md | 7 + static/ru/rpc/protocol/metrics.md | 7 + static/ru/rpc/protocol/metrics/index.md | 7 + static/ru/rpc/protocol/network-info.md | 7 + static/ru/rpc/protocol/network-info/index.md | 7 + .../rpc/protocol/next-light-client-block.md | 7 + .../protocol/next-light-client-block/index.md | 7 + static/ru/rpc/protocol/status.md | 7 + static/ru/rpc/protocol/status/index.md | 7 + .../ru/rpc/transaction/broadcast-tx-async.md | 7 + .../transaction/broadcast-tx-async/index.md | 7 + .../ru/rpc/transaction/broadcast-tx-commit.md | 7 + .../transaction/broadcast-tx-commit/index.md | 7 + .../rpc/transaction/experimental-receipt.md | 7 + .../transaction/experimental-receipt/index.md | 7 + .../rpc/transaction/experimental-tx-status.md | 7 + .../experimental-tx-status/index.md | 7 + static/ru/rpc/transaction/send-tx.md | 7 + static/ru/rpc/transaction/send-tx/index.md | 7 + static/ru/rpc/transaction/tx-status.md | 7 + static/ru/rpc/transaction/tx-status/index.md | 7 + .../experimental-validators-ordered.md | 7 + .../experimental-validators-ordered/index.md | 7 + .../ru/rpc/validators/validators-by-epoch.md | 7 + .../validators/validators-by-epoch/index.md | 7 + .../ru/rpc/validators/validators-current.md | 7 + .../validators/validators-current/index.md | 7 + static/ru/rpcs/account/view_access_key.md | 7 + .../ru/rpcs/account/view_access_key/index.md | 7 + .../ru/rpcs/account/view_access_key_list.md | 7 + .../account/view_access_key_list/index.md | 7 + static/ru/rpcs/account/view_account.md | 7 + static/ru/rpcs/account/view_account/index.md | 7 + static/ru/rpcs/block/block_by_height.md | 7 + static/ru/rpcs/block/block_by_height/index.md | 7 + static/ru/rpcs/block/block_by_id.md | 7 + static/ru/rpcs/block/block_by_id/index.md | 7 + static/ru/rpcs/block/block_effects.md | 7 + static/ru/rpcs/block/block_effects/index.md | 7 + static/ru/rpcs/contract/call.md | 7 + static/ru/rpcs/contract/call/index.md | 7 + static/ru/rpcs/contract/view_code.md | 7 + static/ru/rpcs/contract/view_code/index.md | 7 + .../contract/view_global_contract_code.md | 7 + .../view_global_contract_code/index.md | 7 + ...view_global_contract_code_by_account_id.md | 7 + .../index.md | 7 + static/ru/rpcs/contract/view_state.md | 7 + static/ru/rpcs/contract/view_state/index.md | 7 + .../protocol/EXPERIMENTAL_congestion_level.md | 7 + .../EXPERIMENTAL_congestion_level/index.md | 7 + .../EXPERIMENTAL_light_client_block_proof.md | 7 + .../index.md | 7 + .../EXPERIMENTAL_light_client_proof.md | 7 + .../EXPERIMENTAL_light_client_proof/index.md | 7 + .../protocol/EXPERIMENTAL_protocol_config.md | 7 + .../EXPERIMENTAL_protocol_config/index.md | 7 + .../EXPERIMENTAL_split_storage_info.md | 7 + .../EXPERIMENTAL_split_storage_info/index.md | 7 + static/ru/rpcs/protocol/changes.md | 7 + static/ru/rpcs/protocol/changes/index.md | 7 + .../ru/rpcs/protocol/chunk_by_block_shard.md | 7 + .../protocol/chunk_by_block_shard/index.md | 7 + static/ru/rpcs/protocol/chunk_by_hash.md | 7 + .../ru/rpcs/protocol/chunk_by_hash/index.md | 7 + static/ru/rpcs/protocol/client_config.md | 7 + .../ru/rpcs/protocol/client_config/index.md | 7 + static/ru/rpcs/protocol/gas_price.md | 7 + static/ru/rpcs/protocol/gas_price/index.md | 7 + static/ru/rpcs/protocol/gas_price_by_block.md | 7 + .../rpcs/protocol/gas_price_by_block/index.md | 7 + static/ru/rpcs/protocol/genesis_config.md | 7 + .../ru/rpcs/protocol/genesis_config/index.md | 7 + static/ru/rpcs/protocol/health.md | 7 + static/ru/rpcs/protocol/health/index.md | 7 + static/ru/rpcs/protocol/latest_block.md | 7 + static/ru/rpcs/protocol/latest_block/index.md | 7 + static/ru/rpcs/protocol/light_client_proof.md | 7 + .../rpcs/protocol/light_client_proof/index.md | 7 + .../ru/rpcs/protocol/maintenance_windows.md | 7 + .../protocol/maintenance_windows/index.md | 7 + static/ru/rpcs/protocol/metrics.md | 7 + static/ru/rpcs/protocol/metrics/index.md | 7 + static/ru/rpcs/protocol/network_info.md | 7 + static/ru/rpcs/protocol/network_info/index.md | 7 + .../rpcs/protocol/next_light_client_block.md | 7 + .../protocol/next_light_client_block/index.md | 7 + static/ru/rpcs/protocol/status.md | 7 + static/ru/rpcs/protocol/status/index.md | 7 + .../rpcs/transaction/EXPERIMENTAL_receipt.md | 7 + .../transaction/EXPERIMENTAL_receipt/index.md | 7 + .../transaction/EXPERIMENTAL_tx_status.md | 7 + .../EXPERIMENTAL_tx_status/index.md | 7 + .../ru/rpcs/transaction/broadcast_tx_async.md | 7 + .../transaction/broadcast_tx_async/index.md | 7 + .../rpcs/transaction/broadcast_tx_commit.md | 7 + .../transaction/broadcast_tx_commit/index.md | 7 + static/ru/rpcs/transaction/send_tx.md | 7 + static/ru/rpcs/transaction/send_tx/index.md | 7 + static/ru/rpcs/transaction/tx_status.md | 7 + static/ru/rpcs/transaction/tx_status/index.md | 7 + .../EXPERIMENTAL_validators_ordered.md | 7 + .../EXPERIMENTAL_validators_ordered/index.md | 7 + .../ru/rpcs/validators/validators_by_epoch.md | 7 + .../validators/validators_by_epoch/index.md | 7 + .../ru/rpcs/validators/validators_current.md | 7 + .../validators/validators_current/index.md | 7 + static/ru/snapshots.md | 7 + static/ru/snapshots/examples.md | 7 + static/ru/snapshots/examples/index.md | 7 + static/ru/snapshots/index.md | 7 + static/ru/snapshots/mainnet.md | 7 + static/ru/snapshots/mainnet/index.md | 7 + static/ru/snapshots/testnet.md | 7 + static/ru/snapshots/testnet/index.md | 7 + static/ru/transfers.md | 7 + static/ru/transfers/examples.md | 7 + static/ru/transfers/examples/index.md | 7 + static/ru/transfers/index.md | 7 + static/ru/transfers/query.md | 7 + static/ru/transfers/query/index.md | 7 + static/ru/tx.md | 7 + static/ru/tx/account.md | 7 + static/ru/tx/account/index.md | 7 + static/ru/tx/block.md | 7 + static/ru/tx/block/index.md | 7 + static/ru/tx/blocks.md | 7 + static/ru/tx/blocks/index.md | 7 + static/ru/tx/examples.md | 101 ++-- static/ru/tx/examples/berry-club.md | 7 + static/ru/tx/examples/berry-club/index.md | 7 + static/ru/tx/examples/index.md | 101 ++-- static/ru/tx/index.md | 7 + static/ru/tx/receipt.md | 7 + static/ru/tx/receipt/index.md | 7 + static/ru/tx/socialdb-proofs.md | 7 + static/ru/tx/socialdb-proofs/index.md | 7 + static/ru/tx/transactions.md | 7 + static/ru/tx/transactions/index.md | 7 + 371 files changed, 3438 insertions(+), 794 deletions(-) create mode 100644 src/data/fastnearAiMarkdownFooter.json diff --git a/scripts/generate-ai-surfaces.js b/scripts/generate-ai-surfaces.js index 32710e6..38478e6 100644 --- a/scripts/generate-ai-surfaces.js +++ b/scripts/generate-ai-surfaces.js @@ -3,6 +3,7 @@ const fs = require("fs"); const path = require("path"); const { isSecretQueryParam } = require("../src/utils/fastnearOperationUrlState"); +const AI_MARKDOWN_FOOTER_COPY = require("../src/data/fastnearAiMarkdownFooter.json"); const { DEFAULT_LOCALE, @@ -412,6 +413,32 @@ function normalizeMarkdown(markdown) { return markdown.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim(); } +function getFastnearAiFooterCopy(locale = DEFAULT_LOCALE) { + return ( + AI_MARKDOWN_FOOTER_COPY[locale] || + AI_MARKDOWN_FOOTER_COPY[DEFAULT_LOCALE] || + AI_MARKDOWN_FOOTER_COPY.en + ); +} + +function buildFastnearAiFooter(locale = DEFAULT_LOCALE) { + const footerCopy = getFastnearAiFooterCopy(locale); + const sections = [ + `## ${footerCopy.title}`, + "", + ...footerCopy.bullets.map((bullet) => `- ${bullet}`), + ]; + + return `${normalizeMarkdown(sections.join("\n"))}\n`; +} + +function appendFastnearAiFooter(markdown, locale = DEFAULT_LOCALE) { + const normalizedBody = normalizeMarkdown(markdown || ""); + const footer = buildFastnearAiFooter(locale).trim(); + + return `${normalizeMarkdown([normalizedBody, "---", "", footer].filter(Boolean).join("\n"))}\n`; +} + function rewriteRootRelativeMarkdownLinks(markdown, locale = DEFAULT_LOCALE) { return markdown.replace( /(!?\[[^\]]*]\()((?:<)?\/[^)\s>]+(?:>)?)(\))/g, @@ -1405,12 +1432,15 @@ function createAuthoredDocEntries(locale = DEFAULT_LOCALE) { return null; } + const markdownBody = buildOperationMarkdownForRoute(pageModel, route, locale); + return { description: pageModel.info.summary || pageModel.info.description || "", htmlPath: route, group: getDocSectionLabel(baseRoute, locale), kind: "wrapper", - markdown: buildOperationMarkdownForRoute(pageModel, route, locale), + markdownBody, + markdown: appendFastnearAiFooter(markdownBody, locale), markdownPath: buildMarkdownMirrorPath(route), markdownPaths: buildMarkdownMirrorAliases(route), route, @@ -1418,14 +1448,16 @@ function createAuthoredDocEntries(locale = DEFAULT_LOCALE) { }; } - const markdown = renderAuthoredMarkdown(content, route, relativePath, locale); + const markdownBody = renderAuthoredMarkdown(content, route, relativePath, locale); return { description: - data.description || getFirstMeaningfulParagraph(markdown).replace(/^\*\*Source:\*\*.+$/m, "").trim(), + data.description || + getFirstMeaningfulParagraph(markdownBody).replace(/^\*\*Source:\*\*.+$/m, "").trim(), htmlPath: route, group: getDocSectionLabel(baseRoute, locale), kind: "authored", - markdown, + markdownBody, + markdown: appendFastnearAiFooter(markdownBody, locale), markdownPath: buildMarkdownMirrorPath(route), markdownPaths: buildMarkdownMirrorAliases(route), route, @@ -1445,6 +1477,7 @@ function createCanonicalEntries(locale = DEFAULT_LOCALE) { const route = localizeRoute(baseRoute, locale); const topLevel = baseRoute.split("/")[1]; const groupKey = baseRoute.split("/")[2]; + const markdownBody = buildOperationMarkdownForRoute(localizedPageModel, route, locale); return { description: localizedPageModel.info.summary || localizedPageModel.info.description || "", @@ -1454,7 +1487,8 @@ function createCanonicalEntries(locale = DEFAULT_LOCALE) { : API_SERVICE_LABELS[locale]?.[groupKey] || API_SERVICE_LABELS[DEFAULT_LOCALE]?.[groupKey] || groupKey, htmlPath: route, kind: topLevel === "rpcs" ? "rpc" : "api", - markdown: buildOperationMarkdownForRoute(localizedPageModel, route, locale), + markdownBody, + markdown: appendFastnearAiFooter(markdownBody, locale), markdownPath: buildMarkdownMirrorPath(route), markdownPaths: buildMarkdownMirrorAliases(route), route, @@ -1703,10 +1737,10 @@ function buildFullArchive(entries, locale = DEFAULT_LOCALE) { `- ${labels.markdownPath}: ${buildAbsoluteUrl(entry.markdownPath)}`, "" ); - sections.push(entry.markdown.trim(), ""); + sections.push((entry.markdownBody || entry.markdown).trim(), ""); } - return `${normalizeMarkdown(sections.join("\n"))}\n`; + return appendFastnearAiFooter(sections.join("\n"), locale); } function main() { diff --git a/src/data/fastnearAiMarkdownFooter.json b/src/data/fastnearAiMarkdownFooter.json new file mode 100644 index 0000000..46914a6 --- /dev/null +++ b/src/data/fastnearAiMarkdownFooter.json @@ -0,0 +1,20 @@ +{ + "en": { + "title": "About FastNear", + "bullets": [ + "FastNear handles 10B+ requests per month.", + "FastNear runs 100+ nodes worldwide.", + "FastNear offers generous credits with a free trial.", + "Get a trial account quickly at [dashboard.fastnear.com](https://dashboard.fastnear.com)." + ] + }, + "ru": { + "title": "О FastNear", + "bullets": [ + "FastNear обрабатывает более 10 млрд запросов в месяц.", + "FastNear управляет более чем 100 нодами по всему миру.", + "FastNear предлагает щедрые кредиты и бесплатный пробный период.", + "Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com)." + ] + } +} diff --git a/src/theme/DocItem/Content/index.js b/src/theme/DocItem/Content/index.js index 6c701bc..30c72da 100644 --- a/src/theme/DocItem/Content/index.js +++ b/src/theme/DocItem/Content/index.js @@ -66,6 +66,7 @@ export default function WrappedDocItemContent(props) { onSelect: async () => { const markdownRoot = contentRef.current?.querySelector('.theme-doc-markdown'); const markdown = buildMarkdownFromDocContent(markdownRoot, { + locale: currentLocale, sourceUrl: typeof window !== 'undefined' ? sanitizePublicUrl(window.location.href) : metadata.permalink, }); @@ -74,7 +75,7 @@ export default function WrappedDocItemContent(props) { }, }, ]; - }, [metadata.permalink, pageActions]); + }, [currentLocale, metadata.permalink, pageActions]); const seoMeta = useMemo(() => { if (!shouldExposeSeo) { diff --git a/src/utils/markdownExport.js b/src/utils/markdownExport.js index 9b8fa11..2b42b29 100644 --- a/src/utils/markdownExport.js +++ b/src/utils/markdownExport.js @@ -1,6 +1,7 @@ import TurndownService from 'turndown'; import { gfm } from 'turndown-plugin-gfm'; import { isNonShareableOperationQueryParam } from './fastnearOperationUrlState'; +import AI_MARKDOWN_FOOTER_COPY from '@site/src/data/fastnearAiMarkdownFooter.json'; const DOC_SKIP_SELECTORS = [ '[data-markdown-skip]', 'button', @@ -172,6 +173,28 @@ function normalizeMarkdown(markdown) { return markdown.replace(/\n{3,}/g, '\n\n').trim(); } +function getFastnearAiFooterCopy(locale = 'en') { + return AI_MARKDOWN_FOOTER_COPY[locale] || AI_MARKDOWN_FOOTER_COPY.en; +} + +function buildFastnearAiFooter(locale = 'en') { + const footerCopy = getFastnearAiFooterCopy(locale); + const sections = [ + `## ${footerCopy.title}`, + '', + ...footerCopy.bullets.map((bullet) => `- ${bullet}`), + ]; + + return `${normalizeMarkdown(sections.join('\n'))}\n`; +} + +function appendFastnearAiFooter(markdown, locale = 'en') { + const normalizedBody = normalizeMarkdown(markdown || ''); + const footer = buildFastnearAiFooter(locale).trim(); + + return `${normalizeMarkdown([normalizedBody, '---', '', footer].filter(Boolean).join('\n'))}\n`; +} + function toAbsoluteUrl(href, baseUrl) { if (!href) { return href; @@ -462,14 +485,14 @@ function formatResponseReference(response, locale = 'en') { return lines.filter(Boolean).join('\n'); } -export function buildMarkdownFromDocContent(rootElement, { sourceUrl } = {}) { +export function buildMarkdownFromDocContent(rootElement, { locale = 'en', sourceUrl } = {}) { if (!rootElement) { return ''; } const clone = prepareDocClone(rootElement, sourceUrl); const markdown = turndownService.turndown(clone.innerHTML); - return `${normalizeMarkdown(markdown)}\n`; + return appendFastnearAiFooter(markdown, locale); } export function buildOperationMarkdown({ @@ -535,5 +558,5 @@ export function buildOperationMarkdown({ sections.push(''); sections.push(formatResponseReference(pageModel.responses?.[0], locale)); - return `${normalizeMarkdown(sections.filter(Boolean).join('\n'))}\n`; + return appendFastnearAiFooter(sections.filter(Boolean).join('\n'), locale); } diff --git a/static/internationalization/index.md b/static/internationalization/index.md index d32bb77..ba91a83 100644 --- a/static/internationalization/index.md +++ b/static/internationalization/index.md @@ -252,3 +252,10 @@ Use this checklist when adding the next language: 9. Add targeted browser checks only if the locale introduces new runtime behavior worth smoke-testing. If those steps are followed, later locales should mostly be editorial work layered onto a stable framework. +--- +## About FastNear + +- FastNear handles 10B+ requests per month. +- FastNear runs 100+ nodes worldwide. +- FastNear offers generous credits with a free trial. +- Get a trial account quickly at [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents.md b/static/ru/agents.md index 1acbaf4..728f740 100644 --- a/static/ru/agents.md +++ b/static/ru/agents.md @@ -137,3 +137,10 @@ 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) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/auth.md b/static/ru/agents/auth.md index 3ddae5d..f0ff966 100644 --- a/static/ru/agents/auth.md +++ b/static/ru/agents/auth.md @@ -93,3 +93,10 @@ 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) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/auth/index.md b/static/ru/agents/auth/index.md index 3ddae5d..f0ff966 100644 --- a/static/ru/agents/auth/index.md +++ b/static/ru/agents/auth/index.md @@ -93,3 +93,10 @@ 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) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/choosing-surfaces.md b/static/ru/agents/choosing-surfaces.md index f78d329..3d3f1e8 100644 --- a/static/ru/agents/choosing-surfaces.md +++ b/static/ru/agents/choosing-surfaces.md @@ -255,3 +255,10 @@ - [Агенты на FastNear](https://docs.fastnear.com/ru/agents) — полная карта поверхностей, базовые URL и подсказки по поглощению промптов. - [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) — работа с учётными данными и операционный режим. - [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks) — примеры многошаговых сценариев. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/choosing-surfaces/index.md b/static/ru/agents/choosing-surfaces/index.md index f78d329..3d3f1e8 100644 --- a/static/ru/agents/choosing-surfaces/index.md +++ b/static/ru/agents/choosing-surfaces/index.md @@ -255,3 +255,10 @@ - [Агенты на FastNear](https://docs.fastnear.com/ru/agents) — полная карта поверхностей, базовые URL и подсказки по поглощению промптов. - [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) — работа с учётными данными и операционный режим. - [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks) — примеры многошаговых сценариев. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/index.md b/static/ru/agents/index.md index 1acbaf4..728f740 100644 --- a/static/ru/agents/index.md +++ b/static/ru/agents/index.md @@ -137,3 +137,10 @@ 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) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/playbooks.md b/static/ru/agents/playbooks.md index fa1cd05..dda9124 100644 --- a/static/ru/agents/playbooks.md +++ b/static/ru/agents/playbooks.md @@ -260,3 +260,10 @@ - используйте [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces), чтобы выбрать первый API - используйте [Аутентификацию для агентов](https://docs.fastnear.com/ru/agents/auth), если блокер — работа с учётными данными - возвращайтесь к [Агентам на FastNear](https://docs.fastnear.com/ru/agents) за правилами рабочего цикла по умолчанию и формы ответа +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/playbooks/index.md b/static/ru/agents/playbooks/index.md index fa1cd05..dda9124 100644 --- a/static/ru/agents/playbooks/index.md +++ b/static/ru/agents/playbooks/index.md @@ -260,3 +260,10 @@ - используйте [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces), чтобы выбрать первый API - используйте [Аутентификацию для агентов](https://docs.fastnear.com/ru/agents/auth), если блокер — работа с учётными данными - возвращайтесь к [Агентам на FastNear](https://docs.fastnear.com/ru/agents) за правилами рабочего цикла по умолчанию и формы ответа +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api.md b/static/ru/api.md index 0524cda..db1de67 100644 --- a/static/ru/api.md +++ b/static/ru/api.md @@ -63,3 +63,10 @@ https://test.api.fastnear.com ### Мне нужны транзакции, а не балансы Переходите в [Транзакции API](https://docs.fastnear.com/ru/tx), чтобы не перегружать поверхность представления аккаунта запросами по истории. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index c49209b..5b947ac 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -2,6 +2,26 @@ ## Примеры +### Свести один аккаунт за один вызов + +`/v1/account/{id}/full` — это агрегатор аккаунтов в FastNear API: один вызов собирает NEAR-состояние аккаунта, каждый FT-контракт, которого он касался, каждую NFT-коллекцию, которую он получил, и каждый валидаторский пул, в который делегировал. Если у вас уже есть `account_id`, это самый быстрый ответ на вопрос «что это за аккаунт?». + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + near_balance_yocto: .state.balance, + ft_contracts: (.tokens | length), + nft_contracts: (.nfts | length), + staking_pool_contracts: (.pools | length) + }' +``` + +Для `mike.near`: 40 FT-контрактов в списке, 40 NFT-коллекций, 5 валидаторских пулов. Одни только счётчики контрактов говорят, что это активный mainnet-аккаунт. Все примеры ниже погружаются в какую-то одну из этих поверхностей — начинайте отсюда, когда на руках только ID аккаунта. + ### Определить аккаунт по публичному ключу и сразу получить сводку Найдите, какому аккаунту принадлежит ключ, и прочитайте его активы за один следующий запрос. @@ -22,6 +42,98 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Если `matched` больше 1, переключайтесь на [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) и пройдитесь по каждому найденному аккаунту. +### Сколько NEAR на этом аккаунте реально доступно к переводу? + +Состояние аккаунта NEAR делится на три ведра, которые UI кошельков обычно сливает в одно: `balance` — это свободная часть (не в стейкинге), `locked` — NEAR, привязанный к валидаторскому стейку или lockup-контракту, а `storage_bytes` подразумевает ещё отдельную долю, пришпиленную к trie по текущей ставке 10^19 yoctoNEAR за байт. Один pipeline над `/full` разводит их по полкам. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq ' + (.state.balance | tonumber) as $amount + | (.state.locked | tonumber) as $locked + | (.state.storage_bytes * 10000000000000000000) as $pinned + | 1e24 as $ynear + | { + account_id, + near: { + total_owned: (($amount + $locked) / $ynear), + unstaked: ($amount / $ynear), + stake_or_lockup: ($locked / $ynear), + pinned_to_storage: ($pinned / $ynear), + spendable: (($amount - $pinned) / $ynear) + } + }' +``` + +Для `mike.near`: ~2613.49 NEAR всего, всё в свободной части, ~5.58 NEAR закреплено за 558 КБ on-chain-состояния, ~2607.91 NEAR доступно к переводу. Новым аккаунтам это особенно заметно — свежесозданный именованный аккаунт ~182 байта «съедает» ~0.00182 NEAR под storage, и именно поэтому CLI-утилиты не дают отправить полный баланс. + +Наведите тот же pipeline на валидаторский пул вроде `astro-stakers.poolv1.near`, и пропорции перевернутся: ~730 тыс. свободных, ~27.68 млн в `locked`. Этот `locked` — собственный протокольный валидаторский стейк пула, а не средства делегатов (те учитываются внутри состояния контракта пула). Одно и то же поле означает разное на разных типах аккаунтов. + +jq считает в IEEE-754 double, поэтому NEAR-значения выше — только для отображения; для точной бухгалтерии сохраняйте сами yocto-строки. + +### Когда в этом аккаунте что-либо последний раз менялось? + +У каждой записи в массивах `tokens`, `nfts` и `pools` внутри `/full` есть собственное `last_update_block_height` — блок, в котором индексер последний раз видел изменение этой строки для этого аккаунта. Максимум по всем трём массивам даёт дешёвый сигнал «последняя активность» без похода в Transactions API. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq ' + [ + (.tokens // [])[].last_update_block_height, + (.nfts // [])[].last_update_block_height, + (.pools // [])[].last_update_block_height + ] as $heights + | ($heights | map(select(. != null))) as $tracked + | { + account_id, + total_entries: ($heights | length), + tracked_entries: ($tracked | length), + most_recent_block: ($tracked | max), + oldest_tracked_block: ($tracked | min) + }' +``` + +Для `mike.near` относительно текущего tip это возвращает 85 записей по FT-, NFT- и pool-контрактам, 34 с отслеживаемым блоком и самый свежий блок `194711866` — примерно 125 тыс. блоков назад, или около 35 часов при темпе NEAR ~1 блок/сек. Для `root.near`: 254 записи, 158 отслеживаемых. + +Это правильный вопрос для «заброшен ли этот кошелёк?» или «двигалось ли что-то после блока X?» — дёшево, один запрос, без истории транзакций. Чтобы достать саму транзакцию, вызвавшую последнее изменение, расширяйте поверхность до [Transactions API](https://docs.fastnear.com/ru/tx). Записи с `last_update_block_height: null` относятся ко времени до per-row-отслеживания индексером (обычно старые airdrops) и здесь игнорируются, а не считаются свежими. + +### Показать NFT-коллекции этого кошелька от конкретного издателя + +Имена аккаунтов на NEAR кодируют иерархию: `mint.sharddog.near` — это подаккаунт `sharddog.near`, который, в свою очередь, — подаккаунт `near`. Издатели, выпускающие несколько NFT-коллекций, обычно разворачивают каждую как отдельный подаккаунт, поэтому один фильтр по суффиксу над NFT-списком аккаунта вытаскивает всё опубликованное под одним деревом — без внешнего реестра коллекций. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near +PUBLISHER=sharddog.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | jq --arg publisher "$PUBLISHER" ' + ("." + $publisher) as $suffix + | { + account_id: .account_id, + publisher: $publisher, + collections: [ + .tokens[] + | select(.contract_id | endswith($suffix)) + | { + contract_id, + last_update_block_height, + status: (if .last_update_block_height == null then "dormant" else "active" end) + } + ] | sort_by(.last_update_block_height // 0) + }' +``` + +Для `mike.near` и `sharddog.near` это возвращает четыре контракта-подаккаунта: `comic`, `mintv2`, `mint` и `claim`. Два с ненулевым `last_update_block_height` (`mint` на `115715361` и `claim` на `119718026`) — те, где позиция кошелька действительно менялась. Два других — спящие, типично для одноразовых drop-контрактов, в которые аккаунт что-то получил и больше не возвращался. + +Поменяйте `PUBLISHER` на любой аккаунт, чтобы сфокусировать фильтр на другом дереве издателя. + ### Показывает ли кошелёк прямой стейкинг, liquid staking-токены или оба варианта? Прямые позиции в пулах лежат на `/staking`; liquid staking-токены (stNEAR, LiNEAR и т. п.) лежат на `/ft` как обычные FT. Прочитайте оба эндпоинта и классифицируйте кошелёк — `root.near` оказывается `mixed`. @@ -67,3 +179,10 @@ jq -n \ - [Transactions API](https://docs.fastnear.com/ru/tx) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index c49209b..5b947ac 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -2,6 +2,26 @@ ## Примеры +### Свести один аккаунт за один вызов + +`/v1/account/{id}/full` — это агрегатор аккаунтов в FastNear API: один вызов собирает NEAR-состояние аккаунта, каждый FT-контракт, которого он касался, каждую NFT-коллекцию, которую он получил, и каждый валидаторский пул, в который делегировал. Если у вас уже есть `account_id`, это самый быстрый ответ на вопрос «что это за аккаунт?». + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + near_balance_yocto: .state.balance, + ft_contracts: (.tokens | length), + nft_contracts: (.nfts | length), + staking_pool_contracts: (.pools | length) + }' +``` + +Для `mike.near`: 40 FT-контрактов в списке, 40 NFT-коллекций, 5 валидаторских пулов. Одни только счётчики контрактов говорят, что это активный mainnet-аккаунт. Все примеры ниже погружаются в какую-то одну из этих поверхностей — начинайте отсюда, когда на руках только ID аккаунта. + ### Определить аккаунт по публичному ключу и сразу получить сводку Найдите, какому аккаунту принадлежит ключ, и прочитайте его активы за один следующий запрос. @@ -22,6 +42,98 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Если `matched` больше 1, переключайтесь на [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) и пройдитесь по каждому найденному аккаунту. +### Сколько NEAR на этом аккаунте реально доступно к переводу? + +Состояние аккаунта NEAR делится на три ведра, которые UI кошельков обычно сливает в одно: `balance` — это свободная часть (не в стейкинге), `locked` — NEAR, привязанный к валидаторскому стейку или lockup-контракту, а `storage_bytes` подразумевает ещё отдельную долю, пришпиленную к trie по текущей ставке 10^19 yoctoNEAR за байт. Один pipeline над `/full` разводит их по полкам. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq ' + (.state.balance | tonumber) as $amount + | (.state.locked | tonumber) as $locked + | (.state.storage_bytes * 10000000000000000000) as $pinned + | 1e24 as $ynear + | { + account_id, + near: { + total_owned: (($amount + $locked) / $ynear), + unstaked: ($amount / $ynear), + stake_or_lockup: ($locked / $ynear), + pinned_to_storage: ($pinned / $ynear), + spendable: (($amount - $pinned) / $ynear) + } + }' +``` + +Для `mike.near`: ~2613.49 NEAR всего, всё в свободной части, ~5.58 NEAR закреплено за 558 КБ on-chain-состояния, ~2607.91 NEAR доступно к переводу. Новым аккаунтам это особенно заметно — свежесозданный именованный аккаунт ~182 байта «съедает» ~0.00182 NEAR под storage, и именно поэтому CLI-утилиты не дают отправить полный баланс. + +Наведите тот же pipeline на валидаторский пул вроде `astro-stakers.poolv1.near`, и пропорции перевернутся: ~730 тыс. свободных, ~27.68 млн в `locked`. Этот `locked` — собственный протокольный валидаторский стейк пула, а не средства делегатов (те учитываются внутри состояния контракта пула). Одно и то же поле означает разное на разных типах аккаунтов. + +jq считает в IEEE-754 double, поэтому NEAR-значения выше — только для отображения; для точной бухгалтерии сохраняйте сами yocto-строки. + +### Когда в этом аккаунте что-либо последний раз менялось? + +У каждой записи в массивах `tokens`, `nfts` и `pools` внутри `/full` есть собственное `last_update_block_height` — блок, в котором индексер последний раз видел изменение этой строки для этого аккаунта. Максимум по всем трём массивам даёт дешёвый сигнал «последняя активность» без похода в Transactions API. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq ' + [ + (.tokens // [])[].last_update_block_height, + (.nfts // [])[].last_update_block_height, + (.pools // [])[].last_update_block_height + ] as $heights + | ($heights | map(select(. != null))) as $tracked + | { + account_id, + total_entries: ($heights | length), + tracked_entries: ($tracked | length), + most_recent_block: ($tracked | max), + oldest_tracked_block: ($tracked | min) + }' +``` + +Для `mike.near` относительно текущего tip это возвращает 85 записей по FT-, NFT- и pool-контрактам, 34 с отслеживаемым блоком и самый свежий блок `194711866` — примерно 125 тыс. блоков назад, или около 35 часов при темпе NEAR ~1 блок/сек. Для `root.near`: 254 записи, 158 отслеживаемых. + +Это правильный вопрос для «заброшен ли этот кошелёк?» или «двигалось ли что-то после блока X?» — дёшево, один запрос, без истории транзакций. Чтобы достать саму транзакцию, вызвавшую последнее изменение, расширяйте поверхность до [Transactions API](https://docs.fastnear.com/ru/tx). Записи с `last_update_block_height: null` относятся ко времени до per-row-отслеживания индексером (обычно старые airdrops) и здесь игнорируются, а не считаются свежими. + +### Показать NFT-коллекции этого кошелька от конкретного издателя + +Имена аккаунтов на NEAR кодируют иерархию: `mint.sharddog.near` — это подаккаунт `sharddog.near`, который, в свою очередь, — подаккаунт `near`. Издатели, выпускающие несколько NFT-коллекций, обычно разворачивают каждую как отдельный подаккаунт, поэтому один фильтр по суффиксу над NFT-списком аккаунта вытаскивает всё опубликованное под одним деревом — без внешнего реестра коллекций. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near +PUBLISHER=sharddog.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | jq --arg publisher "$PUBLISHER" ' + ("." + $publisher) as $suffix + | { + account_id: .account_id, + publisher: $publisher, + collections: [ + .tokens[] + | select(.contract_id | endswith($suffix)) + | { + contract_id, + last_update_block_height, + status: (if .last_update_block_height == null then "dormant" else "active" end) + } + ] | sort_by(.last_update_block_height // 0) + }' +``` + +Для `mike.near` и `sharddog.near` это возвращает четыре контракта-подаккаунта: `comic`, `mintv2`, `mint` и `claim`. Два с ненулевым `last_update_block_height` (`mint` на `115715361` и `claim` на `119718026`) — те, где позиция кошелька действительно менялась. Два других — спящие, типично для одноразовых drop-контрактов, в которые аккаунт что-то получил и больше не возвращался. + +Поменяйте `PUBLISHER` на любой аккаунт, чтобы сфокусировать фильтр на другом дереве издателя. + ### Показывает ли кошелёк прямой стейкинг, liquid staking-токены или оба варианта? Прямые позиции в пулах лежат на `/staking`; liquid staking-токены (stNEAR, LiNEAR и т. п.) лежат на `/ft` как обычные FT. Прочитайте оба эндпоинта и классифицируйте кошелёк — `root.near` оказывается `mixed`. @@ -67,3 +179,10 @@ jq -n \ - [Transactions API](https://docs.fastnear.com/ru/tx) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/index.md b/static/ru/api/index.md index 0524cda..db1de67 100644 --- a/static/ru/api/index.md +++ b/static/ru/api/index.md @@ -63,3 +63,10 @@ https://test.api.fastnear.com ### Мне нужны транзакции, а не балансы Переходите в [Транзакции API](https://docs.fastnear.com/ru/tx), чтобы не перегружать поверхность представления аккаунта запросами по истории. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/reference.md b/static/ru/api/reference.md index 236514f..2c25971 100644 --- a/static/ru/api/reference.md +++ b/static/ru/api/reference.md @@ -48,3 +48,10 @@ - [Агенты на FastNear](https://docs.fastnear.com/ru/agents) - [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/reference/index.md b/static/ru/api/reference/index.md index 236514f..2c25971 100644 --- a/static/ru/api/reference/index.md +++ b/static/ru/api/reference/index.md @@ -48,3 +48,10 @@ - [Агенты на FastNear](https://docs.fastnear.com/ru/agents) - [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/system/health.md b/static/ru/api/system/health.md index c52c3c5..6782bec 100644 --- a/static/ru/api/system/health.md +++ b/static/ru/api/system/health.md @@ -59,3 +59,10 @@ "refName": "HealthResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/system/health/index.md b/static/ru/api/system/health/index.md index c52c3c5..6782bec 100644 --- a/static/ru/api/system/health/index.md +++ b/static/ru/api/system/health/index.md @@ -59,3 +59,10 @@ "refName": "HealthResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/system/status.md b/static/ru/api/system/status.md index 3d3b230..bc6a8a2 100644 --- a/static/ru/api/system/status.md +++ b/static/ru/api/system/status.md @@ -94,3 +94,10 @@ "refName": "StatusResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/system/status/index.md b/static/ru/api/system/status/index.md index 3d3b230..bc6a8a2 100644 --- a/static/ru/api/system/status/index.md +++ b/static/ru/api/system/status/index.md @@ -94,3 +94,10 @@ "refName": "StatusResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/account-ft.md b/static/ru/api/v0/account-ft.md index 4e003b6..684a169 100644 --- a/static/ru/api/v0/account-ft.md +++ b/static/ru/api/v0/account-ft.md @@ -78,3 +78,10 @@ "refName": "V0ContractsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/account-ft/index.md b/static/ru/api/v0/account-ft/index.md index 4e003b6..684a169 100644 --- a/static/ru/api/v0/account-ft/index.md +++ b/static/ru/api/v0/account-ft/index.md @@ -78,3 +78,10 @@ "refName": "V0ContractsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/account-nft.md b/static/ru/api/v0/account-nft.md index 1e25eec..3e2c3bd 100644 --- a/static/ru/api/v0/account-nft.md +++ b/static/ru/api/v0/account-nft.md @@ -78,3 +78,10 @@ "refName": "V0ContractsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/account-nft/index.md b/static/ru/api/v0/account-nft/index.md index 1e25eec..3e2c3bd 100644 --- a/static/ru/api/v0/account-nft/index.md +++ b/static/ru/api/v0/account-nft/index.md @@ -78,3 +78,10 @@ "refName": "V0ContractsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/account-staking.md b/static/ru/api/v0/account-staking.md index ea5a4ce..ffe5615 100644 --- a/static/ru/api/v0/account-staking.md +++ b/static/ru/api/v0/account-staking.md @@ -78,3 +78,10 @@ "refName": "V0StakingResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/account-staking/index.md b/static/ru/api/v0/account-staking/index.md index ea5a4ce..ffe5615 100644 --- a/static/ru/api/v0/account-staking/index.md +++ b/static/ru/api/v0/account-staking/index.md @@ -78,3 +78,10 @@ "refName": "V0StakingResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/public-key-all.md b/static/ru/api/v0/public-key-all.md index be03dd0..4c748d5 100644 --- a/static/ru/api/v0/public-key-all.md +++ b/static/ru/api/v0/public-key-all.md @@ -78,3 +78,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/public-key-all/index.md b/static/ru/api/v0/public-key-all/index.md index be03dd0..4c748d5 100644 --- a/static/ru/api/v0/public-key-all/index.md +++ b/static/ru/api/v0/public-key-all/index.md @@ -78,3 +78,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/public-key.md b/static/ru/api/v0/public-key.md index 7e23c9c..98960e8 100644 --- a/static/ru/api/v0/public-key.md +++ b/static/ru/api/v0/public-key.md @@ -78,3 +78,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/public-key/index.md b/static/ru/api/v0/public-key/index.md index 7e23c9c..98960e8 100644 --- a/static/ru/api/v0/public-key/index.md +++ b/static/ru/api/v0/public-key/index.md @@ -78,3 +78,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-ft.md b/static/ru/api/v1/account-ft.md index 04cd04a..5e469eb 100644 --- a/static/ru/api/v1/account-ft.md +++ b/static/ru/api/v1/account-ft.md @@ -109,3 +109,10 @@ "refName": "V1FtResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-ft/index.md b/static/ru/api/v1/account-ft/index.md index 04cd04a..5e469eb 100644 --- a/static/ru/api/v1/account-ft/index.md +++ b/static/ru/api/v1/account-ft/index.md @@ -109,3 +109,10 @@ "refName": "V1FtResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-full.md b/static/ru/api/v1/account-full.md index 7fba3f7..9059a28 100644 --- a/static/ru/api/v1/account-full.md +++ b/static/ru/api/v1/account-full.md @@ -185,3 +185,10 @@ "refName": "AccountFullResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-full/index.md b/static/ru/api/v1/account-full/index.md index 7fba3f7..9059a28 100644 --- a/static/ru/api/v1/account-full/index.md +++ b/static/ru/api/v1/account-full/index.md @@ -185,3 +185,10 @@ "refName": "AccountFullResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-nft.md b/static/ru/api/v1/account-nft.md index 33ca813..bd381d4 100644 --- a/static/ru/api/v1/account-nft.md +++ b/static/ru/api/v1/account-nft.md @@ -101,3 +101,10 @@ "refName": "V1NftResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-nft/index.md b/static/ru/api/v1/account-nft/index.md index 33ca813..bd381d4 100644 --- a/static/ru/api/v1/account-nft/index.md +++ b/static/ru/api/v1/account-nft/index.md @@ -101,3 +101,10 @@ "refName": "V1NftResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-staking.md b/static/ru/api/v1/account-staking.md index 586bd82..608ef2a 100644 --- a/static/ru/api/v1/account-staking.md +++ b/static/ru/api/v1/account-staking.md @@ -101,3 +101,10 @@ "refName": "V1StakingResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-staking/index.md b/static/ru/api/v1/account-staking/index.md index 586bd82..608ef2a 100644 --- a/static/ru/api/v1/account-staking/index.md +++ b/static/ru/api/v1/account-staking/index.md @@ -101,3 +101,10 @@ "refName": "V1StakingResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/ft-top.md b/static/ru/api/v1/ft-top.md index ce74821..2500266 100644 --- a/static/ru/api/v1/ft-top.md +++ b/static/ru/api/v1/ft-top.md @@ -100,3 +100,10 @@ "refName": "TokenAccountsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/ft-top/index.md b/static/ru/api/v1/ft-top/index.md index ce74821..2500266 100644 --- a/static/ru/api/v1/ft-top/index.md +++ b/static/ru/api/v1/ft-top/index.md @@ -100,3 +100,10 @@ "refName": "TokenAccountsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/public-key-all.md b/static/ru/api/v1/public-key-all.md index 8a1d15f..29e216c 100644 --- a/static/ru/api/v1/public-key-all.md +++ b/static/ru/api/v1/public-key-all.md @@ -78,3 +78,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/public-key-all/index.md b/static/ru/api/v1/public-key-all/index.md index 8a1d15f..29e216c 100644 --- a/static/ru/api/v1/public-key-all/index.md +++ b/static/ru/api/v1/public-key-all/index.md @@ -78,3 +78,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/public-key.md b/static/ru/api/v1/public-key.md index 418eb5a..bb680bf 100644 --- a/static/ru/api/v1/public-key.md +++ b/static/ru/api/v1/public-key.md @@ -78,3 +78,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/public-key/index.md b/static/ru/api/v1/public-key/index.md index 418eb5a..bb680bf 100644 --- a/static/ru/api/v1/public-key/index.md +++ b/static/ru/api/v1/public-key/index.md @@ -78,3 +78,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/system/health.md b/static/ru/apis/fastnear/system/health.md index 1cff258..e6dc9d4 100644 --- a/static/ru/apis/fastnear/system/health.md +++ b/static/ru/apis/fastnear/system/health.md @@ -58,3 +58,10 @@ "refName": "HealthResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/system/health/index.md b/static/ru/apis/fastnear/system/health/index.md index 1cff258..e6dc9d4 100644 --- a/static/ru/apis/fastnear/system/health/index.md +++ b/static/ru/apis/fastnear/system/health/index.md @@ -58,3 +58,10 @@ "refName": "HealthResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/system/status.md b/static/ru/apis/fastnear/system/status.md index 8726dd0..785d01c 100644 --- a/static/ru/apis/fastnear/system/status.md +++ b/static/ru/apis/fastnear/system/status.md @@ -93,3 +93,10 @@ "refName": "StatusResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/system/status/index.md b/static/ru/apis/fastnear/system/status/index.md index 8726dd0..785d01c 100644 --- a/static/ru/apis/fastnear/system/status/index.md +++ b/static/ru/apis/fastnear/system/status/index.md @@ -93,3 +93,10 @@ "refName": "StatusResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/account_ft.md b/static/ru/apis/fastnear/v0/account_ft.md index 84991c5..293cb96 100644 --- a/static/ru/apis/fastnear/v0/account_ft.md +++ b/static/ru/apis/fastnear/v0/account_ft.md @@ -77,3 +77,10 @@ "refName": "V0ContractsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/account_ft/index.md b/static/ru/apis/fastnear/v0/account_ft/index.md index 84991c5..293cb96 100644 --- a/static/ru/apis/fastnear/v0/account_ft/index.md +++ b/static/ru/apis/fastnear/v0/account_ft/index.md @@ -77,3 +77,10 @@ "refName": "V0ContractsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/account_nft.md b/static/ru/apis/fastnear/v0/account_nft.md index 1e35646..219f2ed 100644 --- a/static/ru/apis/fastnear/v0/account_nft.md +++ b/static/ru/apis/fastnear/v0/account_nft.md @@ -77,3 +77,10 @@ "refName": "V0ContractsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/account_nft/index.md b/static/ru/apis/fastnear/v0/account_nft/index.md index 1e35646..219f2ed 100644 --- a/static/ru/apis/fastnear/v0/account_nft/index.md +++ b/static/ru/apis/fastnear/v0/account_nft/index.md @@ -77,3 +77,10 @@ "refName": "V0ContractsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/account_staking.md b/static/ru/apis/fastnear/v0/account_staking.md index 5d43ba0..5d1b951 100644 --- a/static/ru/apis/fastnear/v0/account_staking.md +++ b/static/ru/apis/fastnear/v0/account_staking.md @@ -77,3 +77,10 @@ "refName": "V0StakingResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/account_staking/index.md b/static/ru/apis/fastnear/v0/account_staking/index.md index 5d43ba0..5d1b951 100644 --- a/static/ru/apis/fastnear/v0/account_staking/index.md +++ b/static/ru/apis/fastnear/v0/account_staking/index.md @@ -77,3 +77,10 @@ "refName": "V0StakingResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/public_key_lookup.md b/static/ru/apis/fastnear/v0/public_key_lookup.md index 5dd30be..05aa9f2 100644 --- a/static/ru/apis/fastnear/v0/public_key_lookup.md +++ b/static/ru/apis/fastnear/v0/public_key_lookup.md @@ -77,3 +77,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/public_key_lookup/index.md b/static/ru/apis/fastnear/v0/public_key_lookup/index.md index 5dd30be..05aa9f2 100644 --- a/static/ru/apis/fastnear/v0/public_key_lookup/index.md +++ b/static/ru/apis/fastnear/v0/public_key_lookup/index.md @@ -77,3 +77,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/public_key_lookup_all.md b/static/ru/apis/fastnear/v0/public_key_lookup_all.md index 7a1470a..df998e1 100644 --- a/static/ru/apis/fastnear/v0/public_key_lookup_all.md +++ b/static/ru/apis/fastnear/v0/public_key_lookup_all.md @@ -77,3 +77,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/public_key_lookup_all/index.md b/static/ru/apis/fastnear/v0/public_key_lookup_all/index.md index 7a1470a..df998e1 100644 --- a/static/ru/apis/fastnear/v0/public_key_lookup_all/index.md +++ b/static/ru/apis/fastnear/v0/public_key_lookup_all/index.md @@ -77,3 +77,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_ft.md b/static/ru/apis/fastnear/v1/account_ft.md index cc3f74e..39302f1 100644 --- a/static/ru/apis/fastnear/v1/account_ft.md +++ b/static/ru/apis/fastnear/v1/account_ft.md @@ -108,3 +108,10 @@ "refName": "V1FtResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_ft/index.md b/static/ru/apis/fastnear/v1/account_ft/index.md index cc3f74e..39302f1 100644 --- a/static/ru/apis/fastnear/v1/account_ft/index.md +++ b/static/ru/apis/fastnear/v1/account_ft/index.md @@ -108,3 +108,10 @@ "refName": "V1FtResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_full.md b/static/ru/apis/fastnear/v1/account_full.md index e011b9d..4d8e204 100644 --- a/static/ru/apis/fastnear/v1/account_full.md +++ b/static/ru/apis/fastnear/v1/account_full.md @@ -184,3 +184,10 @@ "refName": "AccountFullResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_full/index.md b/static/ru/apis/fastnear/v1/account_full/index.md index e011b9d..4d8e204 100644 --- a/static/ru/apis/fastnear/v1/account_full/index.md +++ b/static/ru/apis/fastnear/v1/account_full/index.md @@ -184,3 +184,10 @@ "refName": "AccountFullResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_nft.md b/static/ru/apis/fastnear/v1/account_nft.md index 4d3db21..1292525 100644 --- a/static/ru/apis/fastnear/v1/account_nft.md +++ b/static/ru/apis/fastnear/v1/account_nft.md @@ -100,3 +100,10 @@ "refName": "V1NftResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_nft/index.md b/static/ru/apis/fastnear/v1/account_nft/index.md index 4d3db21..1292525 100644 --- a/static/ru/apis/fastnear/v1/account_nft/index.md +++ b/static/ru/apis/fastnear/v1/account_nft/index.md @@ -100,3 +100,10 @@ "refName": "V1NftResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_staking.md b/static/ru/apis/fastnear/v1/account_staking.md index 79c2c29..5356377 100644 --- a/static/ru/apis/fastnear/v1/account_staking.md +++ b/static/ru/apis/fastnear/v1/account_staking.md @@ -100,3 +100,10 @@ "refName": "V1StakingResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_staking/index.md b/static/ru/apis/fastnear/v1/account_staking/index.md index 79c2c29..5356377 100644 --- a/static/ru/apis/fastnear/v1/account_staking/index.md +++ b/static/ru/apis/fastnear/v1/account_staking/index.md @@ -100,3 +100,10 @@ "refName": "V1StakingResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/ft_top.md b/static/ru/apis/fastnear/v1/ft_top.md index 26efa18..c1acd6a 100644 --- a/static/ru/apis/fastnear/v1/ft_top.md +++ b/static/ru/apis/fastnear/v1/ft_top.md @@ -99,3 +99,10 @@ "refName": "TokenAccountsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/ft_top/index.md b/static/ru/apis/fastnear/v1/ft_top/index.md index 26efa18..c1acd6a 100644 --- a/static/ru/apis/fastnear/v1/ft_top/index.md +++ b/static/ru/apis/fastnear/v1/ft_top/index.md @@ -99,3 +99,10 @@ "refName": "TokenAccountsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/public_key_lookup.md b/static/ru/apis/fastnear/v1/public_key_lookup.md index 162e5ea..97caa03 100644 --- a/static/ru/apis/fastnear/v1/public_key_lookup.md +++ b/static/ru/apis/fastnear/v1/public_key_lookup.md @@ -77,3 +77,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/public_key_lookup/index.md b/static/ru/apis/fastnear/v1/public_key_lookup/index.md index 162e5ea..97caa03 100644 --- a/static/ru/apis/fastnear/v1/public_key_lookup/index.md +++ b/static/ru/apis/fastnear/v1/public_key_lookup/index.md @@ -77,3 +77,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/public_key_lookup_all.md b/static/ru/apis/fastnear/v1/public_key_lookup_all.md index dbbb336..1874eb3 100644 --- a/static/ru/apis/fastnear/v1/public_key_lookup_all.md +++ b/static/ru/apis/fastnear/v1/public_key_lookup_all.md @@ -77,3 +77,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/public_key_lookup_all/index.md b/static/ru/apis/fastnear/v1/public_key_lookup_all/index.md index dbbb336..1874eb3 100644 --- a/static/ru/apis/fastnear/v1/public_key_lookup_all/index.md +++ b/static/ru/apis/fastnear/v1/public_key_lookup_all/index.md @@ -77,3 +77,10 @@ "refName": "PublicKeyLookupResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/all_by_predecessor.md b/static/ru/apis/kv-fastdata/v0/all_by_predecessor.md index 44226ab..0d35dae 100644 --- a/static/ru/apis/kv-fastdata/v0/all_by_predecessor.md +++ b/static/ru/apis/kv-fastdata/v0/all_by_predecessor.md @@ -206,3 +206,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/all_by_predecessor/index.md b/static/ru/apis/kv-fastdata/v0/all_by_predecessor/index.md index 44226ab..0d35dae 100644 --- a/static/ru/apis/kv-fastdata/v0/all_by_predecessor/index.md +++ b/static/ru/apis/kv-fastdata/v0/all_by_predecessor/index.md @@ -206,3 +206,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/get_history_key.md b/static/ru/apis/kv-fastdata/v0/get_history_key.md index 08b3d24..ca7d050 100644 --- a/static/ru/apis/kv-fastdata/v0/get_history_key.md +++ b/static/ru/apis/kv-fastdata/v0/get_history_key.md @@ -163,3 +163,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/get_history_key/index.md b/static/ru/apis/kv-fastdata/v0/get_history_key/index.md index 08b3d24..ca7d050 100644 --- a/static/ru/apis/kv-fastdata/v0/get_history_key/index.md +++ b/static/ru/apis/kv-fastdata/v0/get_history_key/index.md @@ -163,3 +163,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/get_latest_key.md b/static/ru/apis/kv-fastdata/v0/get_latest_key.md index 0aef926..8e41a7d 100644 --- a/static/ru/apis/kv-fastdata/v0/get_latest_key.md +++ b/static/ru/apis/kv-fastdata/v0/get_latest_key.md @@ -163,3 +163,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/get_latest_key/index.md b/static/ru/apis/kv-fastdata/v0/get_latest_key/index.md index 0aef926..8e41a7d 100644 --- a/static/ru/apis/kv-fastdata/v0/get_latest_key/index.md +++ b/static/ru/apis/kv-fastdata/v0/get_latest_key/index.md @@ -163,3 +163,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/history_by_account.md b/static/ru/apis/kv-fastdata/v0/history_by_account.md index 761dc86..2d9c8da 100644 --- a/static/ru/apis/kv-fastdata/v0/history_by_account.md +++ b/static/ru/apis/kv-fastdata/v0/history_by_account.md @@ -238,3 +238,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/history_by_account/index.md b/static/ru/apis/kv-fastdata/v0/history_by_account/index.md index 761dc86..2d9c8da 100644 --- a/static/ru/apis/kv-fastdata/v0/history_by_account/index.md +++ b/static/ru/apis/kv-fastdata/v0/history_by_account/index.md @@ -238,3 +238,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/history_by_key.md b/static/ru/apis/kv-fastdata/v0/history_by_key.md index 830b479..9f6e7d5 100644 --- a/static/ru/apis/kv-fastdata/v0/history_by_key.md +++ b/static/ru/apis/kv-fastdata/v0/history_by_key.md @@ -224,3 +224,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/history_by_key/index.md b/static/ru/apis/kv-fastdata/v0/history_by_key/index.md index 830b479..9f6e7d5 100644 --- a/static/ru/apis/kv-fastdata/v0/history_by_key/index.md +++ b/static/ru/apis/kv-fastdata/v0/history_by_key/index.md @@ -224,3 +224,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/history_by_predecessor.md b/static/ru/apis/kv-fastdata/v0/history_by_predecessor.md index bf60890..b62baf0 100644 --- a/static/ru/apis/kv-fastdata/v0/history_by_predecessor.md +++ b/static/ru/apis/kv-fastdata/v0/history_by_predecessor.md @@ -241,3 +241,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/history_by_predecessor/index.md b/static/ru/apis/kv-fastdata/v0/history_by_predecessor/index.md index bf60890..b62baf0 100644 --- a/static/ru/apis/kv-fastdata/v0/history_by_predecessor/index.md +++ b/static/ru/apis/kv-fastdata/v0/history_by_predecessor/index.md @@ -241,3 +241,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/latest_by_account.md b/static/ru/apis/kv-fastdata/v0/latest_by_account.md index a9b46ec..f7f352f 100644 --- a/static/ru/apis/kv-fastdata/v0/latest_by_account.md +++ b/static/ru/apis/kv-fastdata/v0/latest_by_account.md @@ -226,3 +226,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/latest_by_account/index.md b/static/ru/apis/kv-fastdata/v0/latest_by_account/index.md index a9b46ec..f7f352f 100644 --- a/static/ru/apis/kv-fastdata/v0/latest_by_account/index.md +++ b/static/ru/apis/kv-fastdata/v0/latest_by_account/index.md @@ -226,3 +226,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/latest_by_predecessor.md b/static/ru/apis/kv-fastdata/v0/latest_by_predecessor.md index bcb4d1d..a71abd6 100644 --- a/static/ru/apis/kv-fastdata/v0/latest_by_predecessor.md +++ b/static/ru/apis/kv-fastdata/v0/latest_by_predecessor.md @@ -229,3 +229,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/latest_by_predecessor/index.md b/static/ru/apis/kv-fastdata/v0/latest_by_predecessor/index.md index bcb4d1d..a71abd6 100644 --- a/static/ru/apis/kv-fastdata/v0/latest_by_predecessor/index.md +++ b/static/ru/apis/kv-fastdata/v0/latest_by_predecessor/index.md @@ -229,3 +229,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/multi.md b/static/ru/apis/kv-fastdata/v0/multi.md index b6deeb2..8ea9608 100644 --- a/static/ru/apis/kv-fastdata/v0/multi.md +++ b/static/ru/apis/kv-fastdata/v0/multi.md @@ -197,3 +197,10 @@ "refName": "MultiResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/multi/index.md b/static/ru/apis/kv-fastdata/v0/multi/index.md index b6deeb2..8ea9608 100644 --- a/static/ru/apis/kv-fastdata/v0/multi/index.md +++ b/static/ru/apis/kv-fastdata/v0/multi/index.md @@ -197,3 +197,10 @@ "refName": "MultiResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/system/health.md b/static/ru/apis/neardata/system/health.md index 4a987c3..1189dbb 100644 --- a/static/ru/apis/neardata/system/health.md +++ b/static/ru/apis/neardata/system/health.md @@ -58,3 +58,10 @@ "refName": "HealthResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/system/health/index.md b/static/ru/apis/neardata/system/health/index.md index 4a987c3..1189dbb 100644 --- a/static/ru/apis/neardata/system/health/index.md +++ b/static/ru/apis/neardata/system/health/index.md @@ -58,3 +58,10 @@ "refName": "HealthResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block.md b/static/ru/apis/neardata/v0/block.md index 15c9ddc..b2c9ad7 100644 --- a/static/ru/apis/neardata/v0/block.md +++ b/static/ru/apis/neardata/v0/block.md @@ -1850,3 +1850,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block/index.md b/static/ru/apis/neardata/v0/block/index.md index 15c9ddc..b2c9ad7 100644 --- a/static/ru/apis/neardata/v0/block/index.md +++ b/static/ru/apis/neardata/v0/block/index.md @@ -1850,3 +1850,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_chunk.md b/static/ru/apis/neardata/v0/block_chunk.md index 5e588db..d7ffffb 100644 --- a/static/ru/apis/neardata/v0/block_chunk.md +++ b/static/ru/apis/neardata/v0/block_chunk.md @@ -905,3 +905,10 @@ "refName": "ChunkDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_chunk/index.md b/static/ru/apis/neardata/v0/block_chunk/index.md index 5e588db..d7ffffb 100644 --- a/static/ru/apis/neardata/v0/block_chunk/index.md +++ b/static/ru/apis/neardata/v0/block_chunk/index.md @@ -905,3 +905,10 @@ "refName": "ChunkDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_headers.md b/static/ru/apis/neardata/v0/block_headers.md index a87ac1e..3735527 100644 --- a/static/ru/apis/neardata/v0/block_headers.md +++ b/static/ru/apis/neardata/v0/block_headers.md @@ -250,3 +250,10 @@ "refName": "BlockEnvelope" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_headers/index.md b/static/ru/apis/neardata/v0/block_headers/index.md index a87ac1e..3735527 100644 --- a/static/ru/apis/neardata/v0/block_headers/index.md +++ b/static/ru/apis/neardata/v0/block_headers/index.md @@ -250,3 +250,10 @@ "refName": "BlockEnvelope" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_optimistic.md b/static/ru/apis/neardata/v0/block_optimistic.md index f800964..20dbecb 100644 --- a/static/ru/apis/neardata/v0/block_optimistic.md +++ b/static/ru/apis/neardata/v0/block_optimistic.md @@ -1850,3 +1850,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_optimistic/index.md b/static/ru/apis/neardata/v0/block_optimistic/index.md index f800964..20dbecb 100644 --- a/static/ru/apis/neardata/v0/block_optimistic/index.md +++ b/static/ru/apis/neardata/v0/block_optimistic/index.md @@ -1850,3 +1850,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_shard.md b/static/ru/apis/neardata/v0/block_shard.md index 469f464..b09d32e 100644 --- a/static/ru/apis/neardata/v0/block_shard.md +++ b/static/ru/apis/neardata/v0/block_shard.md @@ -1629,3 +1629,10 @@ "refName": "ShardDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_shard/index.md b/static/ru/apis/neardata/v0/block_shard/index.md index 469f464..b09d32e 100644 --- a/static/ru/apis/neardata/v0/block_shard/index.md +++ b/static/ru/apis/neardata/v0/block_shard/index.md @@ -1629,3 +1629,10 @@ "refName": "ShardDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/first_block.md b/static/ru/apis/neardata/v0/first_block.md index d768d13..de64f25 100644 --- a/static/ru/apis/neardata/v0/first_block.md +++ b/static/ru/apis/neardata/v0/first_block.md @@ -1842,3 +1842,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/first_block/index.md b/static/ru/apis/neardata/v0/first_block/index.md index d768d13..de64f25 100644 --- a/static/ru/apis/neardata/v0/first_block/index.md +++ b/static/ru/apis/neardata/v0/first_block/index.md @@ -1842,3 +1842,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/last_block_final.md b/static/ru/apis/neardata/v0/last_block_final.md index e0acdf6..1608fac 100644 --- a/static/ru/apis/neardata/v0/last_block_final.md +++ b/static/ru/apis/neardata/v0/last_block_final.md @@ -1842,3 +1842,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/last_block_final/index.md b/static/ru/apis/neardata/v0/last_block_final/index.md index e0acdf6..1608fac 100644 --- a/static/ru/apis/neardata/v0/last_block_final/index.md +++ b/static/ru/apis/neardata/v0/last_block_final/index.md @@ -1842,3 +1842,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/last_block_optimistic.md b/static/ru/apis/neardata/v0/last_block_optimistic.md index 8652ea8..e5212e0 100644 --- a/static/ru/apis/neardata/v0/last_block_optimistic.md +++ b/static/ru/apis/neardata/v0/last_block_optimistic.md @@ -1842,3 +1842,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/last_block_optimistic/index.md b/static/ru/apis/neardata/v0/last_block_optimistic/index.md index 8652ea8..e5212e0 100644 --- a/static/ru/apis/neardata/v0/last_block_optimistic/index.md +++ b/static/ru/apis/neardata/v0/last_block_optimistic/index.md @@ -1842,3 +1842,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/account.md b/static/ru/apis/transactions/v0/account.md index f4ac645..55fe2a1 100644 --- a/static/ru/apis/transactions/v0/account.md +++ b/static/ru/apis/transactions/v0/account.md @@ -413,3 +413,10 @@ "refName": "AccountResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/account/index.md b/static/ru/apis/transactions/v0/account/index.md index f4ac645..55fe2a1 100644 --- a/static/ru/apis/transactions/v0/account/index.md +++ b/static/ru/apis/transactions/v0/account/index.md @@ -413,3 +413,10 @@ "refName": "AccountResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/block.md b/static/ru/apis/transactions/v0/block.md index 7c808a1..a26190a 100644 --- a/static/ru/apis/transactions/v0/block.md +++ b/static/ru/apis/transactions/v0/block.md @@ -588,3 +588,10 @@ "refName": "BlockResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/block/index.md b/static/ru/apis/transactions/v0/block/index.md index 7c808a1..a26190a 100644 --- a/static/ru/apis/transactions/v0/block/index.md +++ b/static/ru/apis/transactions/v0/block/index.md @@ -588,3 +588,10 @@ "refName": "BlockResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/blocks.md b/static/ru/apis/transactions/v0/blocks.md index 70d322a..5bf2fc5 100644 --- a/static/ru/apis/transactions/v0/blocks.md +++ b/static/ru/apis/transactions/v0/blocks.md @@ -267,3 +267,10 @@ "refName": "BlocksResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/blocks/index.md b/static/ru/apis/transactions/v0/blocks/index.md index 70d322a..5bf2fc5 100644 --- a/static/ru/apis/transactions/v0/blocks/index.md +++ b/static/ru/apis/transactions/v0/blocks/index.md @@ -267,3 +267,10 @@ "refName": "BlocksResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/receipt.md b/static/ru/apis/transactions/v0/receipt.md index 845e4e4..c20bbe5 100644 --- a/static/ru/apis/transactions/v0/receipt.md +++ b/static/ru/apis/transactions/v0/receipt.md @@ -234,3 +234,10 @@ "refName": "ReceiptResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/receipt/index.md b/static/ru/apis/transactions/v0/receipt/index.md index 845e4e4..c20bbe5 100644 --- a/static/ru/apis/transactions/v0/receipt/index.md +++ b/static/ru/apis/transactions/v0/receipt/index.md @@ -234,3 +234,10 @@ "refName": "ReceiptResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/transactions.md b/static/ru/apis/transactions/v0/transactions.md index 4e409de..3a4a8f6 100644 --- a/static/ru/apis/transactions/v0/transactions.md +++ b/static/ru/apis/transactions/v0/transactions.md @@ -97,3 +97,10 @@ "refName": "TransactionsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/transactions/index.md b/static/ru/apis/transactions/v0/transactions/index.md index 4e409de..3a4a8f6 100644 --- a/static/ru/apis/transactions/v0/transactions/index.md +++ b/static/ru/apis/transactions/v0/transactions/index.md @@ -97,3 +97,10 @@ "refName": "TransactionsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transfers/v0/transfers.md b/static/ru/apis/transfers/v0/transfers.md index a0db763..3cf3886 100644 --- a/static/ru/apis/transfers/v0/transfers.md +++ b/static/ru/apis/transfers/v0/transfers.md @@ -382,3 +382,10 @@ "refName": "TransfersResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transfers/v0/transfers/index.md b/static/ru/apis/transfers/v0/transfers/index.md index a0db763..3cf3886 100644 --- a/static/ru/apis/transfers/v0/transfers/index.md +++ b/static/ru/apis/transfers/v0/transfers/index.md @@ -382,3 +382,10 @@ "refName": "TransfersResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/auth.md b/static/ru/auth.md index 2730547..e81a7a7 100644 --- a/static/ru/auth.md +++ b/static/ru/auth.md @@ -26,3 +26,10 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/auth/index.md b/static/ru/auth/index.md index 2730547..e81a7a7 100644 --- a/static/ru/auth/index.md +++ b/static/ru/auth/index.md @@ -26,3 +26,10 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv.md b/static/ru/fastdata/kv.md index bfec141..0df2f0a 100644 --- a/static/ru/fastdata/kv.md +++ b/static/ru/fastdata/kv.md @@ -107,3 +107,10 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY ### Мне нужны продуктовые балансы аккаунта, а не сырые строки «ключ–значение» Переходите на [FastNear API](https://docs.fastnear.com/ru/api). +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/all-by-predecessor.md b/static/ru/fastdata/kv/all-by-predecessor.md index 28bbce2..413bb30 100644 --- a/static/ru/fastdata/kv/all-by-predecessor.md +++ b/static/ru/fastdata/kv/all-by-predecessor.md @@ -207,3 +207,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/all-by-predecessor/index.md b/static/ru/fastdata/kv/all-by-predecessor/index.md index 28bbce2..413bb30 100644 --- a/static/ru/fastdata/kv/all-by-predecessor/index.md +++ b/static/ru/fastdata/kv/all-by-predecessor/index.md @@ -207,3 +207,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index e5990e4..d04b82f 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -49,3 +49,10 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KE - [FastNear API](https://docs.fastnear.com/ru/api) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index e5990e4..d04b82f 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -49,3 +49,10 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KE - [FastNear API](https://docs.fastnear.com/ru/api) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/get-history-key.md b/static/ru/fastdata/kv/get-history-key.md index 2879de1..28551aa 100644 --- a/static/ru/fastdata/kv/get-history-key.md +++ b/static/ru/fastdata/kv/get-history-key.md @@ -164,3 +164,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/get-history-key/index.md b/static/ru/fastdata/kv/get-history-key/index.md index 2879de1..28551aa 100644 --- a/static/ru/fastdata/kv/get-history-key/index.md +++ b/static/ru/fastdata/kv/get-history-key/index.md @@ -164,3 +164,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/get-latest-key.md b/static/ru/fastdata/kv/get-latest-key.md index 7608b13..d0f1bf5 100644 --- a/static/ru/fastdata/kv/get-latest-key.md +++ b/static/ru/fastdata/kv/get-latest-key.md @@ -164,3 +164,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/get-latest-key/index.md b/static/ru/fastdata/kv/get-latest-key/index.md index 7608b13..d0f1bf5 100644 --- a/static/ru/fastdata/kv/get-latest-key/index.md +++ b/static/ru/fastdata/kv/get-latest-key/index.md @@ -164,3 +164,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/history-by-account.md b/static/ru/fastdata/kv/history-by-account.md index 1e2fb55..9fa8470 100644 --- a/static/ru/fastdata/kv/history-by-account.md +++ b/static/ru/fastdata/kv/history-by-account.md @@ -239,3 +239,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/history-by-account/index.md b/static/ru/fastdata/kv/history-by-account/index.md index 1e2fb55..9fa8470 100644 --- a/static/ru/fastdata/kv/history-by-account/index.md +++ b/static/ru/fastdata/kv/history-by-account/index.md @@ -239,3 +239,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/history-by-key.md b/static/ru/fastdata/kv/history-by-key.md index c8c7b66..ebf2e18 100644 --- a/static/ru/fastdata/kv/history-by-key.md +++ b/static/ru/fastdata/kv/history-by-key.md @@ -225,3 +225,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/history-by-key/index.md b/static/ru/fastdata/kv/history-by-key/index.md index c8c7b66..ebf2e18 100644 --- a/static/ru/fastdata/kv/history-by-key/index.md +++ b/static/ru/fastdata/kv/history-by-key/index.md @@ -225,3 +225,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/history-by-predecessor.md b/static/ru/fastdata/kv/history-by-predecessor.md index dd78adc..af7abcf 100644 --- a/static/ru/fastdata/kv/history-by-predecessor.md +++ b/static/ru/fastdata/kv/history-by-predecessor.md @@ -242,3 +242,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/history-by-predecessor/index.md b/static/ru/fastdata/kv/history-by-predecessor/index.md index dd78adc..af7abcf 100644 --- a/static/ru/fastdata/kv/history-by-predecessor/index.md +++ b/static/ru/fastdata/kv/history-by-predecessor/index.md @@ -242,3 +242,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/index.md b/static/ru/fastdata/kv/index.md index bfec141..0df2f0a 100644 --- a/static/ru/fastdata/kv/index.md +++ b/static/ru/fastdata/kv/index.md @@ -107,3 +107,10 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY ### Мне нужны продуктовые балансы аккаунта, а не сырые строки «ключ–значение» Переходите на [FastNear API](https://docs.fastnear.com/ru/api). +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/latest-by-account.md b/static/ru/fastdata/kv/latest-by-account.md index 994407d..9b3842c 100644 --- a/static/ru/fastdata/kv/latest-by-account.md +++ b/static/ru/fastdata/kv/latest-by-account.md @@ -227,3 +227,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/latest-by-account/index.md b/static/ru/fastdata/kv/latest-by-account/index.md index 994407d..9b3842c 100644 --- a/static/ru/fastdata/kv/latest-by-account/index.md +++ b/static/ru/fastdata/kv/latest-by-account/index.md @@ -227,3 +227,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/latest-by-predecessor.md b/static/ru/fastdata/kv/latest-by-predecessor.md index f28cd37..2356664 100644 --- a/static/ru/fastdata/kv/latest-by-predecessor.md +++ b/static/ru/fastdata/kv/latest-by-predecessor.md @@ -230,3 +230,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/latest-by-predecessor/index.md b/static/ru/fastdata/kv/latest-by-predecessor/index.md index f28cd37..2356664 100644 --- a/static/ru/fastdata/kv/latest-by-predecessor/index.md +++ b/static/ru/fastdata/kv/latest-by-predecessor/index.md @@ -230,3 +230,10 @@ "refName": "ListResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/multi.md b/static/ru/fastdata/kv/multi.md index d9adb52..9dd1028 100644 --- a/static/ru/fastdata/kv/multi.md +++ b/static/ru/fastdata/kv/multi.md @@ -198,3 +198,10 @@ "refName": "MultiResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/multi/index.md b/static/ru/fastdata/kv/multi/index.md index d9adb52..9dd1028 100644 --- a/static/ru/fastdata/kv/multi/index.md +++ b/static/ru/fastdata/kv/multi/index.md @@ -198,3 +198,10 @@ "refName": "MultiResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/index.md b/static/ru/index.md index 85536f8..9c26d95 100644 --- a/static/ru/index.md +++ b/static/ru/index.md @@ -115,3 +115,10 @@ [Открыть хаб агентов](https://docs.fastnear.com/ru/agents) [Как выбрать поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/internationalization.md b/static/ru/internationalization.md index 5cda7fa..41647ea 100644 --- a/static/ru/internationalization.md +++ b/static/ru/internationalization.md @@ -252,3 +252,10 @@ Playwright, relevance scoring и более тяжёлые редакционн 9. Добавьте браузерные smoke-checks только там, где локаль вносит новое поведение рантайма. Если идти по этому списку, следующие локали будут в основном редакционной задачей поверх уже готового фреймворка. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/internationalization/index.md b/static/ru/internationalization/index.md index 5cda7fa..41647ea 100644 --- a/static/ru/internationalization/index.md +++ b/static/ru/internationalization/index.md @@ -252,3 +252,10 @@ Playwright, relevance scoring и более тяжёлые редакционн 9. Добавьте браузерные smoke-checks только там, где локаль вносит новое поведение рантайма. Если идти по этому списку, следующие локали будут в основном редакционной задачей поверх уже готового фреймворка. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index d92b2a7..45654fa 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -996,6 +996,26 @@ https://test.api.fastnear.com ## Примеры +### Свести один аккаунт за один вызов + +`/v1/account/{id}/full` — это агрегатор аккаунтов в FastNear API: один вызов собирает NEAR-состояние аккаунта, каждый FT-контракт, которого он касался, каждую NFT-коллекцию, которую он получил, и каждый валидаторский пул, в который делегировал. Если у вас уже есть `account_id`, это самый быстрый ответ на вопрос «что это за аккаунт?». + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq '{ + account_id, + near_balance_yocto: .state.balance, + ft_contracts: (.tokens | length), + nft_contracts: (.nfts | length), + staking_pool_contracts: (.pools | length) + }' +``` + +Для `mike.near`: 40 FT-контрактов в списке, 40 NFT-коллекций, 5 валидаторских пулов. Одни только счётчики контрактов говорят, что это активный mainnet-аккаунт. Все примеры ниже погружаются в какую-то одну из этих поверхностей — начинайте отсюда, когда на руках только ID аккаунта. + ### Определить аккаунт по публичному ключу и сразу получить сводку Найдите, какому аккаунту принадлежит ключ, и прочитайте его активы за один следующий запрос. @@ -1016,6 +1036,98 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Если `matched` больше 1, переключайтесь на [V1 Public Key Lookup All](https://docs.fastnear.com/ru/api/v1/public-key-all) и пройдитесь по каждому найденному аккаунту. +### Сколько NEAR на этом аккаунте реально доступно к переводу? + +Состояние аккаунта NEAR делится на три ведра, которые UI кошельков обычно сливает в одно: `balance` — это свободная часть (не в стейкинге), `locked` — NEAR, привязанный к валидаторскому стейку или lockup-контракту, а `storage_bytes` подразумевает ещё отдельную долю, пришпиленную к trie по текущей ставке 10^19 yoctoNEAR за байт. Один pipeline над `/full` разводит их по полкам. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq ' + (.state.balance | tonumber) as $amount + | (.state.locked | tonumber) as $locked + | (.state.storage_bytes * 10000000000000000000) as $pinned + | 1e24 as $ynear + | { + account_id, + near: { + total_owned: (($amount + $locked) / $ynear), + unstaked: ($amount / $ynear), + stake_or_lockup: ($locked / $ynear), + pinned_to_storage: ($pinned / $ynear), + spendable: (($amount - $pinned) / $ynear) + } + }' +``` + +Для `mike.near`: ~2613.49 NEAR всего, всё в свободной части, ~5.58 NEAR закреплено за 558 КБ on-chain-состояния, ~2607.91 NEAR доступно к переводу. Новым аккаунтам это особенно заметно — свежесозданный именованный аккаунт ~182 байта «съедает» ~0.00182 NEAR под storage, и именно поэтому CLI-утилиты не дают отправить полный баланс. + +Наведите тот же pipeline на валидаторский пул вроде `astro-stakers.poolv1.near`, и пропорции перевернутся: ~730 тыс. свободных, ~27.68 млн в `locked`. Этот `locked` — собственный протокольный валидаторский стейк пула, а не средства делегатов (те учитываются внутри состояния контракта пула). Одно и то же поле означает разное на разных типах аккаунтов. + +jq считает в IEEE-754 double, поэтому NEAR-значения выше — только для отображения; для точной бухгалтерии сохраняйте сами yocto-строки. + +### Когда в этом аккаунте что-либо последний раз менялось? + +У каждой записи в массивах `tokens`, `nfts` и `pools` внутри `/full` есть собственное `last_update_block_height` — блок, в котором индексер последний раз видел изменение этой строки для этого аккаунта. Максимум по всем трём массивам даёт дешёвый сигнал «последняя активность» без похода в Transactions API. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ + | jq ' + [ + (.tokens // [])[].last_update_block_height, + (.nfts // [])[].last_update_block_height, + (.pools // [])[].last_update_block_height + ] as $heights + | ($heights | map(select(. != null))) as $tracked + | { + account_id, + total_entries: ($heights | length), + tracked_entries: ($tracked | length), + most_recent_block: ($tracked | max), + oldest_tracked_block: ($tracked | min) + }' +``` + +Для `mike.near` относительно текущего tip это возвращает 85 записей по FT-, NFT- и pool-контрактам, 34 с отслеживаемым блоком и самый свежий блок `194711866` — примерно 125 тыс. блоков назад, или около 35 часов при темпе NEAR ~1 блок/сек. Для `root.near`: 254 записи, 158 отслеживаемых. + +Это правильный вопрос для «заброшен ли этот кошелёк?» или «двигалось ли что-то после блока X?» — дёшево, один запрос, без истории транзакций. Чтобы достать саму транзакцию, вызвавшую последнее изменение, расширяйте поверхность до [Transactions API](https://docs.fastnear.com/ru/tx). Записи с `last_update_block_height: null` относятся ко времени до per-row-отслеживания индексером (обычно старые airdrops) и здесь игнорируются, а не считаются свежими. + +### Показать NFT-коллекции этого кошелька от конкретного издателя + +Имена аккаунтов на NEAR кодируют иерархию: `mint.sharddog.near` — это подаккаунт `sharddog.near`, который, в свою очередь, — подаккаунт `near`. Издатели, выпускающие несколько NFT-коллекций, обычно разворачивают каждую как отдельный подаккаунт, поэтому один фильтр по суффиксу над NFT-списком аккаунта вытаскивает всё опубликованное под одним деревом — без внешнего реестра коллекций. + +```bash +API_BASE_URL=https://api.fastnear.com +ACCOUNT_ID=mike.near +PUBLISHER=sharddog.near + +curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ + | jq --arg publisher "$PUBLISHER" ' + ("." + $publisher) as $suffix + | { + account_id: .account_id, + publisher: $publisher, + collections: [ + .tokens[] + | select(.contract_id | endswith($suffix)) + | { + contract_id, + last_update_block_height, + status: (if .last_update_block_height == null then "dormant" else "active" end) + } + ] | sort_by(.last_update_block_height // 0) + }' +``` + +Для `mike.near` и `sharddog.near` это возвращает четыре контракта-подаккаунта: `comic`, `mintv2`, `mint` и `claim`. Два с ненулевым `last_update_block_height` (`mint` на `115715361` и `claim` на `119718026`) — те, где позиция кошелька действительно менялась. Два других — спящие, типично для одноразовых drop-контрактов, в которые аккаунт что-то получил и больше не возвращался. + +Поменяйте `PUBLISHER` на любой аккаунт, чтобы сфокусировать фильтр на другом дереве издателя. + ### Показывает ли кошелёк прямой стейкинг, liquid staking-токены или оба варианта? Прямые позиции в пулах лежат на `/staking`; liquid staking-токены (stNEAR, LiNEAR и т. п.) лежат на `/ft` как обычные FT. Прочитайте оба эндпоинта и классифицируйте кошелёк — `root.near` оказывается `mixed`. @@ -1671,118 +1783,127 @@ https://testnet.neardata.xyz ## Примеры -Каждый гидратированный документ блока NEAR Data несёт транзакции, receipts, результаты исполнения и state changes с разбивкой по shard. Три сценария ниже используют один `bash`-помощник, который сворачивает эти четыре сигнала в одну сводку с полями для перехода дальше. Определите его один раз и прогоняйте блоки через него: +NEAR Data возвращает каждый блок полностью гидратированным одним JSON-документом — header плюс per-shard chunks, receipts, результаты исполнения и state changes, — так что один `curl` уже даёт всё необходимое, чтобы отфильтровать нужный контракт без второго запроса. + +### На каком блоке NEAR сейчас? + +`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Прежде чем фильтровать по конкретному контракту, полезно увидеть, как выглядит один блок на уровне протокола: транзакции приходят с разбивкой по shard, поэтому общее число транзакций в блоке — это сумма по shards, а не одно поле верхнего уровня. ```bash -contract_touch_summary() { - jq -r --arg contract "$1" ' - [ .shards[] | { - shard_id, - direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length), - sample_tx_hash: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash] | .[0]), - sample_receipt_id: ( - [ .chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id ] + - [ .receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract) | .execution_outcome.id ] + - [ .state_changes[]? | select(.change.account_id? == $contract) | (.cause.receipt_hash? // empty) ] - | .[0] - ) - } - | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) - ] as $rows - | { - height: .block.header.height, - hash: .block.header.hash, - contract: $contract, - touched: (($rows | length) > 0), - shards: ($rows | map(.shard_id)), - evidence: { - direct_txs: (($rows | map(.direct_txs) | add) // 0), - incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), - execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), - state_changes: (($rows | map(.state_changes) | add) // 0) - }, - sample_tx_hash: ([ $rows[] | .sample_tx_hash | select(.) ] | .[0]), - sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) - }' -} +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ + | jq '{ + height: .block.header.height, + timestamp_nanosec: .block.header.timestamp_nanosec, + txs_per_shard: [.shards[] | {shard_id, tx_count: (.chunk.transactions | length)}], + total_txs: ([.shards[].chunk.transactions[]?] | length) + }' ``` +Живой блок показывает 9 shards и горсть транзакций, распределённых по ним — большинство shards в любом блоке пустые, а активность концентрируется на тех shards, где живут загруженные контракты. `timestamp_nanosec` — это Unix-время в наносекундах (делите на 1e9, чтобы получить секунды). С этим одним вызовом у вас уже есть всё необходимое для дальнейшего копания — примеры фильтрации ниже просто применяют jq к тому же ответу. + ### Был ли мой контракт затронут в последнем финализированном блоке? -`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок; пройдите по нему и направьте результат сразу в помощник. +`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Контракт может проявиться либо в `transactions` chunk (когда он `receiver_id`), либо в `receipts` (когда прилетает cross-shard-вызов), поэтому один проход jq по shards покрывает оба случая. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ - | contract_touch_summary "$TARGET_CONTRACT" + | jq --arg contract "$TARGET_CONTRACT" '{ + height: .block.header.height, + contract: $contract, + touched_shards: [ + .shards[] | { + shard_id, + txs: [.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash], + receipts: [.chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id] + } | select((.txs | length) + (.receipts | length) > 0) + ] + }' ``` -Читайте `touched: false` как полный и однозначный ответ для тихого блока. При `true` поля перехода (`sample_tx_hash`, `sample_receipt_id`) сразу ведут вас в [/tx/examples](https://docs.fastnear.com/ru/tx/examples) за человекочитаемой историей. Один запрос заменяет ручной просмотр chunks — и учтите: `touched: true` с `state_changes: 0` — это реальная форма: receipt может попасть в chunk, не вызвав в том же блоке мутации состояния. +`touched_shards: []` — полный ответ для тихого блока. Непустой список называет shards, где контракт появился, и отдаёт конкретные `tx`-хеши или `receipt_id` — передавайте хеш в [Transactions API](https://docs.fastnear.com/ru/tx), когда нужна человекочитаемая история. Receipts без парных `txs` — это нормально: cross-contract-вызов приходит как incoming receipt в этом блоке, даже если исходная транзакция была раньше. -### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? +### Увидел ли я активность в optimistic-режиме, и догнала ли её finality? -Optimistic-блоки живут по адресу `/v0/block_opt/{height}`; как только finality догоняет (обычно в пределах одного блока, ~1 с на mainnet), та же высота становится доступна и по `/v0/block/{height}`. Прогоните помощник на обеих и сравните. +Optimistic-блоки ходят по `/v0/block_opt/{height}` примерно на секунду впереди `/v0/block/{height}`. Цикл мониторинга может действовать по optimistic-сигналу и ожидать, что тот же ответ придёт на финализированный эндпоинт через один блок — если только стресс сети не расширит разрыв, и тогда финализированный fetch вернёт `null`, а вы подождёте. ```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + +count_touches() { + jq --arg contract "$1" ' + [.shards[] + | ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length) + + ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length)] + | add // 0' +} + OPT_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" OPT_HEIGHT="${OPT_LOCATION##*/}" -echo "Optimistic view at $OPT_HEIGHT:" -curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | contract_touch_summary "$TARGET_CONTRACT" - -echo "Finalized view at $OPT_HEIGHT:" +echo "optimistic @ $OPT_HEIGHT: $(curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | count_touches "$TARGET_CONTRACT") touches" FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" -if [ "$(echo "$FINAL" | jq 'type')" = '"null"' ]; then - echo "finality has not caught up to $OPT_HEIGHT yet" +if [ "$(printf '%s' "$FINAL" | jq 'type')" = '"null"' ]; then + echo "finalized @ $OPT_HEIGHT: not caught up yet" else - echo "$FINAL" | contract_touch_summary "$TARGET_CONTRACT" + echo "finalized @ $OPT_HEIGHT: $(printf '%s' "$FINAL" | count_touches "$TARGET_CONTRACT") touches" fi ``` -На здоровой сети обе сводки совпадают сразу; ценность — в самом шаблоне, а не в драматичной разнице. Цикл мониторинга, который реагирует на optimistic-сигнал, знает: тот же ответ — на один блок от надёжного. Ветку `finality has not caught up` используйте, когда действительно нужно отличить «увидено optimistically» от «подтверждено» — во время стресса сети этот разрыв расширяется. +На здоровом mainnet оба счётчика совпадают в пределах секунды. Ценность — в самом шаблоне: optimistic-поток даёт ответ, на который можно реагировать сразу, а финализированный приходит через блок как надёжное подтверждение. -### Какой shard действительно изменил мой контракт в этом блоке? +### Какой shard действительно изменил состояние моего контракта? -Блоки тонкие — в большинстве финализированных блоков нет мутаций состояния ни для одного конкретного контракта. Идите назад от финализированной головы, пока помощник не покажет `state_changes > 0`, затем откройте «победивший» shard через `/v0/block/{height}/shard/{shard_id}` ради самого payload мутации. +В большинстве финализированных блоков нет мутации состояния ни для одного конкретного контракта — активность разрежена и привязана к shard. Идите назад от финализированной головы, пока состояние контракта реально не изменится, и откройте этот shard для payload мутации. Вызов уровня блока говорит, *на каком* shard это случилось; вызов уровня shard — *как*. ```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" -TARGET_HEIGHT="" -WINNING_SHARD="" +FOUND_HEIGHT="" +FOUND_SHARD="" -for OFFSET in 0 1 2 3 4 5 6 7 8 9; do +for OFFSET in $(seq 0 15); do H=$((HEAD - OFFSET)) - SUMMARY="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" | contract_touch_summary "$TARGET_CONTRACT")" - if [ "$(echo "$SUMMARY" | jq '.evidence.state_changes')" -gt 0 ]; then - TARGET_HEIGHT=$H - WINNING_SHARD="$(echo "$SUMMARY" | jq -r '.shards[0]')" - echo "$SUMMARY" + SHARD="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" \ + | jq -r --arg contract "$TARGET_CONTRACT" ' + .shards[] + | select([.state_changes[]? | select(.change.account_id? == $contract)] | length > 0) + | .shard_id + ' | head -1)" + if [ -n "$SHARD" ]; then + FOUND_HEIGHT=$H; FOUND_SHARD=$SHARD break fi done -curl -s "$NEARDATA_BASE_URL/v0/block/$TARGET_HEIGHT/shard/$WINNING_SHARD" \ - | jq --arg contract "$TARGET_CONTRACT" '{ - shard_id, - chunk_hash: .chunk.header.chunk_hash, - matching_state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause_type: (.cause | keys[0]), account_id: .change.account_id}][0:3], - matching_execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0]), predecessor_id: .receipt.predecessor_id}][0:3] - }' +if [ -z "$FOUND_HEIGHT" ]; then + echo "no state mutation for $TARGET_CONTRACT in the last 16 finalized blocks" +else + curl -s "$NEARDATA_BASE_URL/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + | jq --arg contract "$TARGET_CONTRACT" --argjson height "$FOUND_HEIGHT" --argjson shard_id "$FOUND_SHARD" '{ + height: $height, + shard_id: $shard_id, + state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause: (.cause | keys[0])}][0:3], + execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0])}][0:3] + }' +fi ``` -На mainnet `intents.near` стабильно выполняется на shard 7, поэтому обход назад обычно попадает в цель за несколько блоков. Payload shard затем называет конкретные типы state-change (`account_update`, `data_update` и т. п.) и результаты исполнения receipt, которые их породили, — shard-локальное доказательство без догадок. Для менее активных контрактов расширьте диапазон `OFFSET`. +На mainnet `intents.near` живёт на shard 7, поэтому обход назад обычно попадает в цель за несколько блоков. Payload shard называет конкретные типы state-change (`account_update`, `data_update` и т. п.) и результаты receipt, которые их породили, — shard-локальное доказательство без догадок. Для менее активных контрактов расширьте диапазон `OFFSET`. ## Когда расширить поверхность -- Используйте [Transactions API](https://docs.fastnear.com/ru/tx), когда у вас уже есть `tx_hash` и нужен человекочитаемый рассказ о транзакции. +- Используйте [Transactions API](https://docs.fastnear.com/ru/tx), когда у вас уже есть `tx_hash` и нужна человекочитаемая история транзакции. - Используйте [RPC Reference](https://docs.fastnear.com/ru/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. - Используйте [Block Headers](https://docs.fastnear.com/ru/neardata/block-headers), когда нужна только динамика head/finality, а не проверка contract-touch. @@ -1947,6 +2068,27 @@ https://archival-rpc.testnet.fastnear.com Начинайте с RPC-метода, который отвечает на вопрос. Используйте `tx`, чтобы отследить включение и финальность по хешу транзакции, и расширяйте поверхность только когда нужны дерево receipts, сырой state или трассировка на уровне shard. +## Состояние аккаунта + +### Показать баланс и storage аккаунта на finality + +`view_account` — канонический RPC-запрос для текущего состояния аккаунта. Один вызов возвращает свободный баланс, сумму, заблокированную в валидаторском стейке или lockup-контракте, использованное storage и блок, на котором было сделано чтение. `finality: "final"` гарантирует, что вы читаете стабильное состояние, а не optimistic-представление. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_account",account_id:$account_id,finality:"final"} + }')" \ + | jq '.result | {amount, locked, storage_usage, block_height, block_hash}' +``` + +Для `mike.near` это возвращает `amount` (yoctoNEAR в свободной части), `locked: "0"` (ничего в валидаторском стейке или lockup-контракте) и `storage_usage: 558441` — 558 КБ on-chain-состояния. Пара `block_height`/`block_hash` фиксирует точку чтения; чтобы прочитать несколько аккаунтов *на одном и том же* блоке, переиспользуйте возвращённый `block_hash` как `block_id` в последующих запросах. + ## Включение транзакции и финальность ### Отследить транзакцию от хеша до финальности @@ -1983,7 +2125,7 @@ curl -s "$RPC_URL" \ ### Описать первый action первой транзакции на текущем tip -Пройдите `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. +Блок NEAR — это header поверх N shard chunks, а не плоский список транзакций. `block` возвращает headers chunks; сами транзакции лежат уровнем ниже, внутри `chunk`. Шортката `block → tx` нет — блок не несёт хешей транзакций, поэтому `tx` (которому нужен hash) в этой цепочке не участвует. Канонический проход — `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. ```bash RPC_URL=https://rpc.mainnet.fastnear.com @@ -2029,9 +2171,16 @@ fi ## Механика аккаунтов и ключей -### Аудит старых function-call-ключей Near Social +### Определить function-call-ключи, которые стоит удалить + +Каждый кошелёк, шлюз и dapp-сессия, в которую вы заходите, обычно оставляет за собой function-call-ключ. Большинством из них вы больше никогда не воспользуетесь. `view_access_key_list` возвращает все ключи аккаунта; структура nonce показывает, какие из них устарели. + +Новые ключи стартуют с `block_height * 10^6`, и значение инкрементируется на единицу за каждую транзакцию, которую ключ подписывает, поэтому: -У создателей накапливаются Social function-call-ключи от каждого кошелька и каждого BOS-шлюза, которым они пользовались. `view_access_key_list` возвращает их все; один фильтр сужает до `social.near`, а **младшие шесть цифр nonce** заодно служат счётчиком использования — новые ключи стартуют с `block_height * 10^6` и инкрементируются на единицу за каждую транзакцию. +- `nonce / 10^6` → блок, в котором ключ был добавлен +- `nonce % 10^6` → сколько раз ключ был использован + +Любой ключ с `tx_count: 0` был создан и ни разу не использовался — самый очевидный кандидат на очистку. Следующий по порядку — ключи, заскоупленные на контракт, с которым вы больше не работаете. Фильтр ниже сужает до `social.near`, но чтобы аудитировать другой контракт, меняется только строка `RECEIVER_ID`. ```bash RPC_URL=https://rpc.mainnet.fastnear.com @@ -2047,13 +2196,13 @@ curl -s "$RPC_URL" \ | jq --arg receiver "$RECEIVER_ID" ' { total_keys: (.result.keys | length), - social_fcks: [ + fcks_for_receiver: [ .result.keys[] | select((.access_key.permission | type) == "object") | select(.access_key.permission.FunctionCall.receiver_id == $receiver) | { public_key, - created_near_block: (.access_key.nonce / 1000000 | floor), + added_at_block: (.access_key.nonce / 1000000 | floor), tx_count: (.access_key.nonce % 1000000), method_names: (.access_key.permission.FunctionCall.method_names | if . == [] then "ANY" else . end), allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") @@ -2062,119 +2211,17 @@ curl -s "$RPC_URL" \ }' ``` -Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на очистку. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность конкретного dapp. - -Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом — function-call-ключ не может авторизовать `DeleteKey` — и отправьте через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Повторный запуск того же списка подтверждает удаление. Само подписание — стандартная near-api-js-история и не самая интересная часть аудита. - -### Какая транзакция добавила этот `social.near` function-call-ключ и кто её авторизовал? - -Тот же nonce, что считает использование, заодно якорит `AddKey` во времени блоков: новые ключи стартуют примерно с `block_height * 10^6`, так что деление текущего nonce на миллион даёт плотное окно поиска. Один раз гидратируйте кандидатов — и ответ уже несёт достаточно, чтобы отличить прямой `AddKey` от делегированной (meta-tx) авторизации, то есть показать, *какой ключ подписал решение*, а не только какой аккаунт оплатил gas. - -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=mike.near -TARGET_PUBLIC_KEY=ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs - -CURRENT_NONCE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg public_key "$TARGET_PUBLIC_KEY" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"view_access_key",account_id:$account_id,public_key:$public_key,finality:"final"} - }')" \ - | jq -r '.result.nonce')" - -ADD_KEY_BLOCK=$((CURRENT_NONCE / 1000000)) +Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на удаление. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность одного dapp. -TX_HASHES="$(curl -s "$TX_BASE_URL/v0/account" -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" \ - --argjson from $((ADD_KEY_BLOCK - 20)) --argjson to $((ADD_KEY_BLOCK + 5)) '{ - account_id: $account_id, is_real_signer: true, - from_tx_block_height: $from, to_tx_block_height: $to, desc: false, limit: 50 - }')" \ - | jq -c '[.account_txs[].transaction_hash]')" - -curl -s "$TX_BASE_URL/v0/transactions" -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES" '{tx_hashes: $tx_hashes}')" \ - | jq --arg target "$TARGET_PUBLIC_KEY" ' - [ .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? | .AddKey? | select(.public_key == $target) - | {mode: "direct", authorizing_public_key: $tx.transaction.public_key, permission: .access_key.permission}), - ($tx.transaction.actions[]? | .Delegate? | .delegate_action as $d - | $d.actions[]? | .AddKey? | select(.public_key == $target) - | {mode: "delegated", authorizing_public_key: $d.public_key, permission: .access_key.permission}) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - signer_id: $tx.transaction.signer_id, - receiver_id: $tx.transaction.receiver_id, - add_key_receipt: ([$tx.receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]?; .AddKey.public_key? == $target)) - | {receipt_id: .receipt.receipt_id, receipt_block: .execution_outcome.block_height}][0]) - } + . - ]' -``` - -Для ключа `ed25519:7GZg…` аккаунта `mike.near` (первый `social.near` FCK из аудита выше) это разрешается в транзакцию `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` на внешнем блоке tx `112057390`. Внешний signer — `app.herewallet.near`, это relayer HERE Wallet, и `mode: "delegated"` рассказывает остальную историю: relayer оплатил gas, но *авторизующий* ключ внутри Delegate — `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu`, full-access-ключ `mike.near`, который подписал сам `AddKey`. Это та разница meta-tx, которую верхнеуровневый `signer_id` в одиночку скрыл бы. - -`add_key_receipt` замыкает картину: `AddKey` выполнился в блоке `112057392`, через два блока после внешней tx, потому что Delegate прыгает из shard relayer в shard целевого аккаунта. Расширьте окно `-20/+5`, если ключом с момента создания пользовались активно. - -### Зарегистрировать FT-хранилище при необходимости и затем перевести токены - -Токены NEP-141 требуют, чтобы каждый получатель предварительно зарегистрировал storage на контракте, прежде чем сможет держать баланс. Два view-вызова авторитетно отвечают на вопрос регистрации *до* отправки — пропуск этой проверки и есть причина, по которой `ft_transfer` в итоге тихо возвращается отправителю. - -```bash -RPC_URL=https://rpc.testnet.fastnear.com -TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -RECEIVER_ACCOUNT_ID=mike.testnet - -ACCOUNT_ARGS_B64="$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" - -REGISTERED="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_of",args_base64:$args,finality:"final"} - }')" \ - | jq '(.result.result | implode | fromjson) != null')" - -MIN_DEPOSIT="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_bounds",args_base64:"e30=",finality:"final"} - }')" \ - | jq -r '.result.result | implode | fromjson | .min')" - -jq -n --argjson registered "$REGISTERED" --arg min "$MIN_DEPOSIT" '{ - registered: $registered, - min_storage_deposit_yocto: $min -}' -``` - -Для зафиксированного testnet-контракта `storage_balance_of({account_id: "mike.testnet"})` возвращает `null` (не зарегистрирован), а `storage_balance_bounds` возвращает `{min: "1250000000000000000000", max: "1250000000000000000000"}` — плоскую комиссию регистрации 0.00125 NEAR. Это собственный ответ контракта, и большего на read-стороне до записи не нужно. - -Write-сторона — это две подписанных function call (near-api-js `transactions.functionCall` или любая NEAR-библиотека подписи работает одинаково): - -- `storage_deposit({account_id: "", registration_only: true})` с депозитом `` yocto и 100 Tgas — пропустите, если `registered: true`. -- `ft_transfer({receiver_id: "", amount: "", memo: "..."})` с депозитом 1 yocto (требует NEP-141) и 100 Tgas. - -Отправьте каждую подписанную транзакцию через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) с `wait_until: "FINAL"`. После этого подтвердите через собственный view-метод контракта — индексированная история не нужна, чтобы доказать, что перевод закрепился: - -```bash -curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"ft_balance_of",args_base64:$args,finality:"final"} - }')" \ - | jq '{receiver_balance: (.result.result | implode | fromjson)}' -``` +Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом (function-call-ключ не может авторизовать `DeleteKey`) и отправьте через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Повторный запуск того же запроса подтвердит, что ключа больше нет. ## Чтение контрактов и сырой state -### Как прочитать сырое storage контракта напрямую? +### Прочитать storage контракта, не запуская его + +View-метод вроде `get_num` всё равно заставляет узел загрузить wasm-контракта и выполнить его. Если ключ storage уже известен, `view_state` возвращает сырые сериализованные байты напрямую — без исполнения и без зависимости от того, выставил ли контракт getter для этого поля вообще. -Два RPC-метода отвечают на один и тот же вопрос о counter с разных слоёв: `view_state` достаёт сырые байты trie без запуска кода, а `call_function` запускает собственный view-метод контракта. Когда они совпадают, вы доказали, что view-метод контракта соответствует его сохранённому состоянию. +Контракты на `near-sdk-rs` хранят верхнеуровневую `#[near_bindgen]`-структуру под ключом `STATE`. Передайте `STATE` как `prefix_base64` (`U1RBVEU=` — это base64 тех же четырёх ASCII-байт), и узел вернёт сериализованное значение. ```bash RPC_URL=https://rpc.testnet.fastnear.com @@ -2187,26 +2234,14 @@ RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ }')" \ | jq -r '.result.values[0].value')" -RAW_I8="$(python3 -c "import base64,sys;print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" - -METHOD_VALUE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"get_num",args_base64:"e30=",finality:"final"} - }')" \ - | jq -r '.result.result | implode | fromjson')" +DECODED_I8="$(python3 -c "import base64; print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" -jq -n --arg raw_b64 "$RAW_B64" --argjson raw_i8 "$RAW_I8" --argjson method "$METHOD_VALUE" '{ - raw_state_b64: $raw_b64, - raw_state_decoded: $raw_i8, - view_method_value: $method, - agree: ($raw_i8 == $method) -}' +jq -n --arg raw "$RAW_B64" --argjson val "$DECODED_I8" '{raw_bytes_base64: $raw, decoded_i8: $val}' ``` -Для живого counter `view_state` по ключу `STATE` (base64 `U1RBVEU=`) возвращает `"CQ=="` — один байт `0x09`, декодируется как signed i8 в `9`; `get_num` тоже возвращает `9`. Они совпадают, потому что контракт хранит `val: i8` по этому ключу. `signed=True` важен: отрицательный counter выглядел бы как `"/w=="` (байт `0xff` → i8 `-1`, а не u8 `255`). +Для живого counter это возвращает `"CQ=="` — один байт `0x09`, декодируется как signed i8 в `9`. Это то же число, которое вернул бы `get_num`, только прочитанное прямо из trie без запуска кода контракта. `signed=True` важен: отрицательный counter сериализовался бы как `"/w=="` (байт `0xff` → i8 `-1`, а не u8 `255`). -`view_state` — правильный инструмент, когда у контракта нет view-метода для нужных данных, когда нужно сверить view-метод с реальным storage или когда нужна семья ключей, которую контракт не раскрывает публично. Для всего остального `call_function` требует меньше церемоний. Если следующий вопрос становится историческим, а не текущим, расширяйте поверхность до [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). +Тянитесь к `view_state`, когда контракт не выставляет view-метод для нужных данных или когда нужна семья ключей, которую контракт не публикует. Для большинства чтений `call_function` всё равно требует меньше церемоний. Если вопрос становится историческим, а не текущим, расширяйте поверхность до [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). ## NEAR Social и точные чтения BOS @@ -3023,48 +3058,9 @@ curl -s "$RPC_URL" \ `UNKNOWN_ACCOUNT` — это и есть доказательство. Если бы `CreateAccount` закрепился, `view_account` вернул бы результат; раз нет — предыдущие `Transfer` и `AddKey` из того же batched-receipt тоже не закрепились. -### Почему этот вызов контракта выглядел успешным, но потом receipt упал? - -Одна транзакция может закончиться тем, что внешний handoff рапортует `SuccessReceiptId`, а дочерний receipt при этом тихо падает — это и есть async-модель NEAR, и `/v0/transactions` выдаёт весь timeline за один запрос. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - tx_handoff: .transactions[0].execution_outcome.outcome.status, - outer_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, - descendant_failures: [ - .transactions[0].receipts[] - | select(.execution_outcome.outcome.status.Failure != null) - | { - receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - block_height: .execution_outcome.block_height, - failure: .execution_outcome.outcome.status.Failure - } - ], - receipt_timeline: [ - .transactions[0].receipts[] - | { - receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - status_class: (.execution_outcome.outcome.status | keys[0]) - } - ] - }' -``` - -Для зафиксированной транзакции mainnet `tx_handoff` — `SuccessReceiptId`: транзакция чисто запустила свой первый receipt. Если смотреть только сюда, можно назвать это победой. `descendant_failures` рассказывают вторую историю: `ft_on_transfer` на `v2.ref-finance.near` упал с `E51: contract paused` — DEX был на паузе во время этого свопа и не мог принять wrapped NEAR. А `receipt_timeline` показывает, как история разрешилась: callback `ft_resolve_transfer` на `wrap.near` всё равно отработал и вывел лог `Refund`, вернув wrapped NEAR отправителю. +### Когда транзакция выглядит успешной — что на самом деле произошло? -Успех receipt не транзитивен. Протокол может чисто отдать handoff и при этом увидеть, как отцеплённая работа провалится позже. Если ваше приложение «выглядело успешным», но деньги всё равно вернулись, пройдите этот же timeline — разделение видно на индексированном ответе без отдельного RPC status-запроса. Чтобы отдельно проверить, что ваш callback отработал, см. [Отработал ли мой callback?](#отработал-ли-мой-callback). - -### Отработал ли мой callback? - -Кросс-контрактные вызовы NEAR возвращаются через callback-receipt на исходном контракте. Отработал ли этот callback — это одна строка с `any(...)` против индексированного списка receipts; а полная история refund выпадает из того же ответа. +Внешний `execution_outcome.outcome.status` рапортует `SuccessReceiptId`, как только сработал handoff первого receipt, — и ничего не говорит о том, успешны ли дочерние receipts и отработал ли callback на исходном контракте. Один pipeline над `/v0/transactions` отвечает сразу на все три вопроса. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -3076,30 +3072,38 @@ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ - top_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, - callback_ran: any( - .transactions[0].receipts[]; - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback - ), - receipt_chain: [ + outer: { + method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + tx_handoff: (.transactions[0].execution_outcome.outcome.status | keys[0]) + }, + callback: { + expected_on: $origin, + method: $callback, + ran: any( + .transactions[0].receipts[]; + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback + ) + }, + descendant_failures: [ .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) | { receiver_id: .receipt.receiver_id, method: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - block: .execution_outcome.block_height, - status: (.execution_outcome.outcome.status | keys[0]), - logs: .execution_outcome.outcome.logs + cause: .execution_outcome.outcome.status.Failure } ] }' ``` -Для зафиксированной транзакции `ft_transfer_call` на `wrap.near` передаёт управление в `ft_on_transfer` на `v2.ref-finance.near`, который **падает**. Callback `ft_resolve_transfer` всё равно выполняется на `wrap.near` и логирует `Refund 7278020378457059679767103 from v2.ref-finance.near to …` обратно отправителю — поэтому `callback_ran: true`, несмотря на сбой дочернего receipt. Сбой ниже по цепочке не мешает исходному контракту увидеть свой callback; так async-обработка ошибок NEAR остаётся восстанавливаемой. Строки с `method: "system"` — это рантайм-возвраты газа, а не логика контракта. Чтобы привязать один из этих логов к породившему его receipt, см. [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). +Для зафиксированной транзакции `outer.method` — `ft_transfer_call`, а `outer.tx_handoff` — `SuccessReceiptId`: транзакция чисто запустила свой первый receipt, и если смотреть только сюда, можно назвать это победой. `descendant_failures` рассказывают вторую историю: `ft_on_transfer` на `v2.ref-finance.near` упал с `E51: contract paused` — DEX был на паузе во время этого свопа и не мог принять wrapped NEAR. `callback.ran: true` — третью: callback `ft_resolve_transfer` на `wrap.near` всё равно отработал. Сбой ниже по цепочке никогда не мешает callback исходного контракта — именно так NEP-141 возвращает отправителю средства, когда получатель их отклонил. + +Успех receipt не транзитивен. Протокол может чисто отдать handoff и при этом увидеть, как отцеплённая работа провалится позже; callback исходного контракта отработает в любом случае. Прочитайте эти три поля вместе — и async-история становится читаемой без ручного обхода цепочки receipts. Чтобы вытянуть сам лог `Refund`, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). ### Сопоставить запрос OutLayer с его TEE-разрешением -[OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь вызывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, и позже `worker.outlayer.near` вызывает `submit_execution_output_and_resolve` с результатом. Один batch на `/v0/transactions` с обоими хешами возвращает всё сопоставление — метод, корреляционные идентификаторы и TEE-отпечаток — без отдельного worker-запроса. +[OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь подписывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, затем `worker.outlayer.near` присылает результат через `submit_execution_output_and_resolve`. Обе половины несут один и тот же `request_id` — передайте оба tx-хеша в один запрос `/v0/transactions` и извлеките это поле с каждой стороны, чтобы подтвердить пару. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -3112,30 +3116,25 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | jq '[ .transactions[] | { + role: (if .transaction.actions[0].FunctionCall.method_name == "request_execution" + then "request" else "worker" end), hash: .transaction.hash, signer: .transaction.signer_id, method: .transaction.actions[0].FunctionCall.method_name, block: .execution_outcome.block_height, - evidence: ( + request_id: ( if .transaction.actions[0].FunctionCall.method_name == "request_execution" - then (.receipts[0].execution_outcome.outcome.logs - | map(select(startswith("EVENT_JSON"))) | .[0] - | sub("EVENT_JSON:"; "") | fromjson | .data[0] as $e - | ($e.request_data | fromjson) as $r - | {request_id: $r.request_id, sender_id: $r.sender_id, project_id: $r.project_id, - data_id: $e.data_id, code_hash: $r.code_source.WasmUrl.hash, - input_preview: ($r.input_data | .[0:80] + "…")}) + then (.receipts[0].execution_outcome.outcome.logs[] | select(startswith("EVENT_JSON")) + | sub("EVENT_JSON:"; "") | fromjson | .data[0].request_data | fromjson | .request_id) else (.receipts[0].receipt.receipt.Action.actions[0].FunctionCall.args - | @base64d | fromjson - | {request_id, success: .output.Json.success, encrypted_bytes: (.output.Json.encrypted_data | length), - instructions: .resources_used.instructions, time_ms: .resources_used.time_ms}) + | @base64d | fromjson | .request_id) end ) } ]' ``` -Обе строки несут `request_id: 1868`. Half-запрос, подписанный `retrorn.near` в блоке `194832281`, вызывает проект `zavodil.near/near-email` с действием `delete_email` и 32-байтным `data_id` — это идентификатор payload yield/resume NEAR, который контракт использует внутри, чтобы приостановить on-chain-обещание, пока worker выполняется. Half-worker попадает через 11 блоков и рапортует `success: true`, `instructions: 53075053`, `time_ms: 2401` и 21 568-байтный зашифрованный результат; эти цифры `resources_used` — тот самый TEE-отпечаток, который можно сверить с лимитами из запроса. `/v0/transactions` отдаёт исторические пары бессрочно, поэтому archival RPC для самой трассировки не нужен — тянитесь к [archival RPC](https://archival-rpc.mainnet.fastnear.com) только когда нужно сверять состояние контракта на высоте блока запроса. +Обе строки несут `request_id: 1868`, подтверждая пару. Половина-запрос, подписанная `retrorn.near` в блоке `194832281`, лежит в логе `EVENT_JSON:` её receipt (это yield/resume-паттерн NEAR — on-chain-обещание приостанавливается, пока TDX-worker выполняется). Половина-worker приходит через 11 блоков с `submit_execution_output_and_resolve`, подписанной `worker.outlayer.near`, и её `request_id` достаётся прямо из base64-обёрнутых `FunctionCall.args`. Те же два payload несут и более богатый отпечаток — `sender_id`, `project_id`, `code_hash`, `resources_used.instructions`, `resources_used.time_ms`, размер зашифрованного результата в байтах — если нужно проверить, что именно исполнилось; этот минимальный pipeline лишь подтверждает, что половины принадлежат друг другу. `/v0/transactions` отдаёт исторические пары бессрочно, поэтому archival RPC для самой трассировки не нужен даже через недели. ## Частые ошибки @@ -33138,3 +33137,10 @@ curl -s "$TX_BASE_URL/v0/transactions" \ "refName": "TransfersResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata.md b/static/ru/neardata.md index fb1afdf..ff6be0e 100644 --- a/static/ru/neardata.md +++ b/static/ru/neardata.md @@ -56,3 +56,10 @@ https://testnet.neardata.xyz ### Нужна потоковая передача, а не опрос Эта поверхность предназначена для чтения через опрос почти в реальном времени. Не позиционируйте её как продукт на основе WebSocket или вебхуков. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-chunk.md b/static/ru/neardata/block-chunk.md index e882ae5..7bdeefe 100644 --- a/static/ru/neardata/block-chunk.md +++ b/static/ru/neardata/block-chunk.md @@ -906,3 +906,10 @@ "refName": "ChunkDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-chunk/index.md b/static/ru/neardata/block-chunk/index.md index e882ae5..7bdeefe 100644 --- a/static/ru/neardata/block-chunk/index.md +++ b/static/ru/neardata/block-chunk/index.md @@ -906,3 +906,10 @@ "refName": "ChunkDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-headers.md b/static/ru/neardata/block-headers.md index 48e7aba..263e9f9 100644 --- a/static/ru/neardata/block-headers.md +++ b/static/ru/neardata/block-headers.md @@ -251,3 +251,10 @@ "refName": "BlockEnvelope" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-headers/index.md b/static/ru/neardata/block-headers/index.md index 48e7aba..263e9f9 100644 --- a/static/ru/neardata/block-headers/index.md +++ b/static/ru/neardata/block-headers/index.md @@ -251,3 +251,10 @@ "refName": "BlockEnvelope" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-optimistic.md b/static/ru/neardata/block-optimistic.md index 1930041..f9aae3a 100644 --- a/static/ru/neardata/block-optimistic.md +++ b/static/ru/neardata/block-optimistic.md @@ -1851,3 +1851,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-optimistic/index.md b/static/ru/neardata/block-optimistic/index.md index 1930041..f9aae3a 100644 --- a/static/ru/neardata/block-optimistic/index.md +++ b/static/ru/neardata/block-optimistic/index.md @@ -1851,3 +1851,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-shard.md b/static/ru/neardata/block-shard.md index 9610aec..9577cb8 100644 --- a/static/ru/neardata/block-shard.md +++ b/static/ru/neardata/block-shard.md @@ -1630,3 +1630,10 @@ "refName": "ShardDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-shard/index.md b/static/ru/neardata/block-shard/index.md index 9610aec..9577cb8 100644 --- a/static/ru/neardata/block-shard/index.md +++ b/static/ru/neardata/block-shard/index.md @@ -1630,3 +1630,10 @@ "refName": "ShardDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block.md b/static/ru/neardata/block.md index 8cdba8d..aac69f1 100644 --- a/static/ru/neardata/block.md +++ b/static/ru/neardata/block.md @@ -1851,3 +1851,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block/index.md b/static/ru/neardata/block/index.md index 8cdba8d..aac69f1 100644 --- a/static/ru/neardata/block/index.md +++ b/static/ru/neardata/block/index.md @@ -1851,3 +1851,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index 80bcf7e..cb244e0 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -2,117 +2,133 @@ ## Примеры -Каждый гидратированный документ блока NEAR Data несёт транзакции, receipts, результаты исполнения и state changes с разбивкой по shard. Три сценария ниже используют один `bash`-помощник, который сворачивает эти четыре сигнала в одну сводку с полями для перехода дальше. Определите его один раз и прогоняйте блоки через него: +NEAR Data возвращает каждый блок полностью гидратированным одним JSON-документом — header плюс per-shard chunks, receipts, результаты исполнения и state changes, — так что один `curl` уже даёт всё необходимое, чтобы отфильтровать нужный контракт без второго запроса. + +### На каком блоке NEAR сейчас? + +`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Прежде чем фильтровать по конкретному контракту, полезно увидеть, как выглядит один блок на уровне протокола: транзакции приходят с разбивкой по shard, поэтому общее число транзакций в блоке — это сумма по shards, а не одно поле верхнего уровня. ```bash -contract_touch_summary() { - jq -r --arg contract "$1" ' - [ .shards[] | { - shard_id, - direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length), - sample_tx_hash: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash] | .[0]), - sample_receipt_id: ( - [ .chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id ] + - [ .receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract) | .execution_outcome.id ] + - [ .state_changes[]? | select(.change.account_id? == $contract) | (.cause.receipt_hash? // empty) ] - | .[0] - ) - } - | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) - ] as $rows - | { - height: .block.header.height, - hash: .block.header.hash, - contract: $contract, - touched: (($rows | length) > 0), - shards: ($rows | map(.shard_id)), - evidence: { - direct_txs: (($rows | map(.direct_txs) | add) // 0), - incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), - execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), - state_changes: (($rows | map(.state_changes) | add) // 0) - }, - sample_tx_hash: ([ $rows[] | .sample_tx_hash | select(.) ] | .[0]), - sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) - }' -} +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ + | jq '{ + height: .block.header.height, + timestamp_nanosec: .block.header.timestamp_nanosec, + txs_per_shard: [.shards[] | {shard_id, tx_count: (.chunk.transactions | length)}], + total_txs: ([.shards[].chunk.transactions[]?] | length) + }' ``` +Живой блок показывает 9 shards и горсть транзакций, распределённых по ним — большинство shards в любом блоке пустые, а активность концентрируется на тех shards, где живут загруженные контракты. `timestamp_nanosec` — это Unix-время в наносекундах (делите на 1e9, чтобы получить секунды). С этим одним вызовом у вас уже есть всё необходимое для дальнейшего копания — примеры фильтрации ниже просто применяют jq к тому же ответу. + ### Был ли мой контракт затронут в последнем финализированном блоке? -`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок; пройдите по нему и направьте результат сразу в помощник. +`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Контракт может проявиться либо в `transactions` chunk (когда он `receiver_id`), либо в `receipts` (когда прилетает cross-shard-вызов), поэтому один проход jq по shards покрывает оба случая. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ - | contract_touch_summary "$TARGET_CONTRACT" + | jq --arg contract "$TARGET_CONTRACT" '{ + height: .block.header.height, + contract: $contract, + touched_shards: [ + .shards[] | { + shard_id, + txs: [.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash], + receipts: [.chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id] + } | select((.txs | length) + (.receipts | length) > 0) + ] + }' ``` -Читайте `touched: false` как полный и однозначный ответ для тихого блока. При `true` поля перехода (`sample_tx_hash`, `sample_receipt_id`) сразу ведут вас в [/tx/examples](https://docs.fastnear.com/ru/tx/examples) за человекочитаемой историей. Один запрос заменяет ручной просмотр chunks — и учтите: `touched: true` с `state_changes: 0` — это реальная форма: receipt может попасть в chunk, не вызвав в том же блоке мутации состояния. +`touched_shards: []` — полный ответ для тихого блока. Непустой список называет shards, где контракт появился, и отдаёт конкретные `tx`-хеши или `receipt_id` — передавайте хеш в [Transactions API](https://docs.fastnear.com/ru/tx), когда нужна человекочитаемая история. Receipts без парных `txs` — это нормально: cross-contract-вызов приходит как incoming receipt в этом блоке, даже если исходная транзакция была раньше. -### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? +### Увидел ли я активность в optimistic-режиме, и догнала ли её finality? -Optimistic-блоки живут по адресу `/v0/block_opt/{height}`; как только finality догоняет (обычно в пределах одного блока, ~1 с на mainnet), та же высота становится доступна и по `/v0/block/{height}`. Прогоните помощник на обеих и сравните. +Optimistic-блоки ходят по `/v0/block_opt/{height}` примерно на секунду впереди `/v0/block/{height}`. Цикл мониторинга может действовать по optimistic-сигналу и ожидать, что тот же ответ придёт на финализированный эндпоинт через один блок — если только стресс сети не расширит разрыв, и тогда финализированный fetch вернёт `null`, а вы подождёте. ```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + +count_touches() { + jq --arg contract "$1" ' + [.shards[] + | ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length) + + ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length)] + | add // 0' +} + OPT_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" OPT_HEIGHT="${OPT_LOCATION##*/}" -echo "Optimistic view at $OPT_HEIGHT:" -curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | contract_touch_summary "$TARGET_CONTRACT" - -echo "Finalized view at $OPT_HEIGHT:" +echo "optimistic @ $OPT_HEIGHT: $(curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | count_touches "$TARGET_CONTRACT") touches" FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" -if [ "$(echo "$FINAL" | jq 'type')" = '"null"' ]; then - echo "finality has not caught up to $OPT_HEIGHT yet" +if [ "$(printf '%s' "$FINAL" | jq 'type')" = '"null"' ]; then + echo "finalized @ $OPT_HEIGHT: not caught up yet" else - echo "$FINAL" | contract_touch_summary "$TARGET_CONTRACT" + echo "finalized @ $OPT_HEIGHT: $(printf '%s' "$FINAL" | count_touches "$TARGET_CONTRACT") touches" fi ``` -На здоровой сети обе сводки совпадают сразу; ценность — в самом шаблоне, а не в драматичной разнице. Цикл мониторинга, который реагирует на optimistic-сигнал, знает: тот же ответ — на один блок от надёжного. Ветку `finality has not caught up` используйте, когда действительно нужно отличить «увидено optimistically» от «подтверждено» — во время стресса сети этот разрыв расширяется. +На здоровом mainnet оба счётчика совпадают в пределах секунды. Ценность — в самом шаблоне: optimistic-поток даёт ответ, на который можно реагировать сразу, а финализированный приходит через блок как надёжное подтверждение. -### Какой shard действительно изменил мой контракт в этом блоке? +### Какой shard действительно изменил состояние моего контракта? -Блоки тонкие — в большинстве финализированных блоков нет мутаций состояния ни для одного конкретного контракта. Идите назад от финализированной головы, пока помощник не покажет `state_changes > 0`, затем откройте «победивший» shard через `/v0/block/{height}/shard/{shard_id}` ради самого payload мутации. +В большинстве финализированных блоков нет мутации состояния ни для одного конкретного контракта — активность разрежена и привязана к shard. Идите назад от финализированной головы, пока состояние контракта реально не изменится, и откройте этот shard для payload мутации. Вызов уровня блока говорит, *на каком* shard это случилось; вызов уровня shard — *как*. ```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" -TARGET_HEIGHT="" -WINNING_SHARD="" +FOUND_HEIGHT="" +FOUND_SHARD="" -for OFFSET in 0 1 2 3 4 5 6 7 8 9; do +for OFFSET in $(seq 0 15); do H=$((HEAD - OFFSET)) - SUMMARY="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" | contract_touch_summary "$TARGET_CONTRACT")" - if [ "$(echo "$SUMMARY" | jq '.evidence.state_changes')" -gt 0 ]; then - TARGET_HEIGHT=$H - WINNING_SHARD="$(echo "$SUMMARY" | jq -r '.shards[0]')" - echo "$SUMMARY" + SHARD="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" \ + | jq -r --arg contract "$TARGET_CONTRACT" ' + .shards[] + | select([.state_changes[]? | select(.change.account_id? == $contract)] | length > 0) + | .shard_id + ' | head -1)" + if [ -n "$SHARD" ]; then + FOUND_HEIGHT=$H; FOUND_SHARD=$SHARD break fi done -curl -s "$NEARDATA_BASE_URL/v0/block/$TARGET_HEIGHT/shard/$WINNING_SHARD" \ - | jq --arg contract "$TARGET_CONTRACT" '{ - shard_id, - chunk_hash: .chunk.header.chunk_hash, - matching_state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause_type: (.cause | keys[0]), account_id: .change.account_id}][0:3], - matching_execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0]), predecessor_id: .receipt.predecessor_id}][0:3] - }' +if [ -z "$FOUND_HEIGHT" ]; then + echo "no state mutation for $TARGET_CONTRACT in the last 16 finalized blocks" +else + curl -s "$NEARDATA_BASE_URL/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + | jq --arg contract "$TARGET_CONTRACT" --argjson height "$FOUND_HEIGHT" --argjson shard_id "$FOUND_SHARD" '{ + height: $height, + shard_id: $shard_id, + state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause: (.cause | keys[0])}][0:3], + execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0])}][0:3] + }' +fi ``` -На mainnet `intents.near` стабильно выполняется на shard 7, поэтому обход назад обычно попадает в цель за несколько блоков. Payload shard затем называет конкретные типы state-change (`account_update`, `data_update` и т. п.) и результаты исполнения receipt, которые их породили, — shard-локальное доказательство без догадок. Для менее активных контрактов расширьте диапазон `OFFSET`. +На mainnet `intents.near` живёт на shard 7, поэтому обход назад обычно попадает в цель за несколько блоков. Payload shard называет конкретные типы state-change (`account_update`, `data_update` и т. п.) и результаты receipt, которые их породили, — shard-локальное доказательство без догадок. Для менее активных контрактов расширьте диапазон `OFFSET`. ## Когда расширить поверхность -- Используйте [Transactions API](https://docs.fastnear.com/ru/tx), когда у вас уже есть `tx_hash` и нужен человекочитаемый рассказ о транзакции. +- Используйте [Transactions API](https://docs.fastnear.com/ru/tx), когда у вас уже есть `tx_hash` и нужна человекочитаемая история транзакции. - Используйте [RPC Reference](https://docs.fastnear.com/ru/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. - Используйте [Block Headers](https://docs.fastnear.com/ru/neardata/block-headers), когда нужна только динамика head/finality, а не проверка contract-touch. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index 80bcf7e..cb244e0 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -2,117 +2,133 @@ ## Примеры -Каждый гидратированный документ блока NEAR Data несёт транзакции, receipts, результаты исполнения и state changes с разбивкой по shard. Три сценария ниже используют один `bash`-помощник, который сворачивает эти четыре сигнала в одну сводку с полями для перехода дальше. Определите его один раз и прогоняйте блоки через него: +NEAR Data возвращает каждый блок полностью гидратированным одним JSON-документом — header плюс per-shard chunks, receipts, результаты исполнения и state changes, — так что один `curl` уже даёт всё необходимое, чтобы отфильтровать нужный контракт без второго запроса. + +### На каком блоке NEAR сейчас? + +`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Прежде чем фильтровать по конкретному контракту, полезно увидеть, как выглядит один блок на уровне протокола: транзакции приходят с разбивкой по shard, поэтому общее число транзакций в блоке — это сумма по shards, а не одно поле верхнего уровня. ```bash -contract_touch_summary() { - jq -r --arg contract "$1" ' - [ .shards[] | { - shard_id, - direct_txs: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length), - incoming_receipts: ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length), - execution_outcomes: ([.receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract)] | length), - state_changes: ([.state_changes[]? | select(.change.account_id? == $contract)] | length), - sample_tx_hash: ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash] | .[0]), - sample_receipt_id: ( - [ .chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id ] + - [ .receipt_execution_outcomes[]? | select(.execution_outcome.outcome.executor_id == $contract) | .execution_outcome.id ] + - [ .state_changes[]? | select(.change.account_id? == $contract) | (.cause.receipt_hash? // empty) ] - | .[0] - ) - } - | select(.direct_txs + .incoming_receipts + .execution_outcomes + .state_changes > 0) - ] as $rows - | { - height: .block.header.height, - hash: .block.header.hash, - contract: $contract, - touched: (($rows | length) > 0), - shards: ($rows | map(.shard_id)), - evidence: { - direct_txs: (($rows | map(.direct_txs) | add) // 0), - incoming_receipts: (($rows | map(.incoming_receipts) | add) // 0), - execution_outcomes: (($rows | map(.execution_outcomes) | add) // 0), - state_changes: (($rows | map(.state_changes) | add) // 0) - }, - sample_tx_hash: ([ $rows[] | .sample_tx_hash | select(.) ] | .[0]), - sample_receipt_id: ([ $rows[] | .sample_receipt_id | select(.) ] | .[0]) - }' -} +NEARDATA_BASE_URL=https://mainnet.neardata.xyz + +curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ + | jq '{ + height: .block.header.height, + timestamp_nanosec: .block.header.timestamp_nanosec, + txs_per_shard: [.shards[] | {shard_id, tx_count: (.chunk.transactions | length)}], + total_txs: ([.shards[].chunk.transactions[]?] | length) + }' ``` +Живой блок показывает 9 shards и горсть транзакций, распределённых по ним — большинство shards в любом блоке пустые, а активность концентрируется на тех shards, где живут загруженные контракты. `timestamp_nanosec` — это Unix-время в наносекундах (делите на 1e9, чтобы получить секунды). С этим одним вызовом у вас уже есть всё необходимое для дальнейшего копания — примеры фильтрации ниже просто применяют jq к тому же ответу. + ### Был ли мой контракт затронут в последнем финализированном блоке? -`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок; пройдите по нему и направьте результат сразу в помощник. +`/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Контракт может проявиться либо в `transactions` chunk (когда он `receiver_id`), либо в `receipts` (когда прилетает cross-shard-вызов), поэтому один проход jq по shards покрывает оба случая. ```bash NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ - | contract_touch_summary "$TARGET_CONTRACT" + | jq --arg contract "$TARGET_CONTRACT" '{ + height: .block.header.height, + contract: $contract, + touched_shards: [ + .shards[] | { + shard_id, + txs: [.chunk.transactions[]? | select(.transaction.receiver_id == $contract) | .transaction.hash], + receipts: [.chunk.receipts[]? | select(.receiver_id == $contract) | .receipt_id] + } | select((.txs | length) + (.receipts | length) > 0) + ] + }' ``` -Читайте `touched: false` как полный и однозначный ответ для тихого блока. При `true` поля перехода (`sample_tx_hash`, `sample_receipt_id`) сразу ведут вас в [/tx/examples](https://docs.fastnear.com/ru/tx/examples) за человекочитаемой историей. Один запрос заменяет ручной просмотр chunks — и учтите: `touched: true` с `state_changes: 0` — это реальная форма: receipt может попасть в chunk, не вызвав в том же блоке мутации состояния. +`touched_shards: []` — полный ответ для тихого блока. Непустой список называет shards, где контракт появился, и отдаёт конкретные `tx`-хеши или `receipt_id` — передавайте хеш в [Transactions API](https://docs.fastnear.com/ru/tx), когда нужна человекочитаемая история. Receipts без парных `txs` — это нормально: cross-contract-вызов приходит как incoming receipt в этом блоке, даже если исходная транзакция была раньше. -### Увидел ли я активность в optimistic-режиме, и пережила ли она finality? +### Увидел ли я активность в optimistic-режиме, и догнала ли её finality? -Optimistic-блоки живут по адресу `/v0/block_opt/{height}`; как только finality догоняет (обычно в пределах одного блока, ~1 с на mainnet), та же высота становится доступна и по `/v0/block/{height}`. Прогоните помощник на обеих и сравните. +Optimistic-блоки ходят по `/v0/block_opt/{height}` примерно на секунду впереди `/v0/block/{height}`. Цикл мониторинга может действовать по optimistic-сигналу и ожидать, что тот же ответ придёт на финализированный эндпоинт через один блок — если только стресс сети не расширит разрыв, и тогда финализированный fetch вернёт `null`, а вы подождёте. ```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + +count_touches() { + jq --arg contract "$1" ' + [.shards[] + | ([.chunk.transactions[]? | select(.transaction.receiver_id == $contract)] | length) + + ([.chunk.receipts[]? | select(.receiver_id == $contract)] | length)] + | add // 0' +} + OPT_LOCATION="$( curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" OPT_HEIGHT="${OPT_LOCATION##*/}" -echo "Optimistic view at $OPT_HEIGHT:" -curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | contract_touch_summary "$TARGET_CONTRACT" - -echo "Finalized view at $OPT_HEIGHT:" +echo "optimistic @ $OPT_HEIGHT: $(curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | count_touches "$TARGET_CONTRACT") touches" FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" -if [ "$(echo "$FINAL" | jq 'type')" = '"null"' ]; then - echo "finality has not caught up to $OPT_HEIGHT yet" +if [ "$(printf '%s' "$FINAL" | jq 'type')" = '"null"' ]; then + echo "finalized @ $OPT_HEIGHT: not caught up yet" else - echo "$FINAL" | contract_touch_summary "$TARGET_CONTRACT" + echo "finalized @ $OPT_HEIGHT: $(printf '%s' "$FINAL" | count_touches "$TARGET_CONTRACT") touches" fi ``` -На здоровой сети обе сводки совпадают сразу; ценность — в самом шаблоне, а не в драматичной разнице. Цикл мониторинга, который реагирует на optimistic-сигнал, знает: тот же ответ — на один блок от надёжного. Ветку `finality has not caught up` используйте, когда действительно нужно отличить «увидено optimistically» от «подтверждено» — во время стресса сети этот разрыв расширяется. +На здоровом mainnet оба счётчика совпадают в пределах секунды. Ценность — в самом шаблоне: optimistic-поток даёт ответ, на который можно реагировать сразу, а финализированный приходит через блок как надёжное подтверждение. -### Какой shard действительно изменил мой контракт в этом блоке? +### Какой shard действительно изменил состояние моего контракта? -Блоки тонкие — в большинстве финализированных блоков нет мутаций состояния ни для одного конкретного контракта. Идите назад от финализированной головы, пока помощник не покажет `state_changes > 0`, затем откройте «победивший» shard через `/v0/block/{height}/shard/{shard_id}` ради самого payload мутации. +В большинстве финализированных блоков нет мутации состояния ни для одного конкретного контракта — активность разрежена и привязана к shard. Идите назад от финализированной головы, пока состояние контракта реально не изменится, и откройте этот shard для payload мутации. Вызов уровня блока говорит, *на каком* shard это случилось; вызов уровня shard — *как*. ```bash +NEARDATA_BASE_URL=https://mainnet.neardata.xyz +TARGET_CONTRACT=intents.near + HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" -TARGET_HEIGHT="" -WINNING_SHARD="" +FOUND_HEIGHT="" +FOUND_SHARD="" -for OFFSET in 0 1 2 3 4 5 6 7 8 9; do +for OFFSET in $(seq 0 15); do H=$((HEAD - OFFSET)) - SUMMARY="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" | contract_touch_summary "$TARGET_CONTRACT")" - if [ "$(echo "$SUMMARY" | jq '.evidence.state_changes')" -gt 0 ]; then - TARGET_HEIGHT=$H - WINNING_SHARD="$(echo "$SUMMARY" | jq -r '.shards[0]')" - echo "$SUMMARY" + SHARD="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" \ + | jq -r --arg contract "$TARGET_CONTRACT" ' + .shards[] + | select([.state_changes[]? | select(.change.account_id? == $contract)] | length > 0) + | .shard_id + ' | head -1)" + if [ -n "$SHARD" ]; then + FOUND_HEIGHT=$H; FOUND_SHARD=$SHARD break fi done -curl -s "$NEARDATA_BASE_URL/v0/block/$TARGET_HEIGHT/shard/$WINNING_SHARD" \ - | jq --arg contract "$TARGET_CONTRACT" '{ - shard_id, - chunk_hash: .chunk.header.chunk_hash, - matching_state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause_type: (.cause | keys[0]), account_id: .change.account_id}][0:3], - matching_execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0]), predecessor_id: .receipt.predecessor_id}][0:3] - }' +if [ -z "$FOUND_HEIGHT" ]; then + echo "no state mutation for $TARGET_CONTRACT in the last 16 finalized blocks" +else + curl -s "$NEARDATA_BASE_URL/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + | jq --arg contract "$TARGET_CONTRACT" --argjson height "$FOUND_HEIGHT" --argjson shard_id "$FOUND_SHARD" '{ + height: $height, + shard_id: $shard_id, + state_changes: [.state_changes[] | select(.change.account_id? == $contract) | {type, cause: (.cause | keys[0])}][0:3], + execution_outcomes: [.receipt_execution_outcomes[] | select(.execution_outcome.outcome.executor_id == $contract) | {receipt_id: .execution_outcome.id, status: (.execution_outcome.outcome.status | keys[0])}][0:3] + }' +fi ``` -На mainnet `intents.near` стабильно выполняется на shard 7, поэтому обход назад обычно попадает в цель за несколько блоков. Payload shard затем называет конкретные типы state-change (`account_update`, `data_update` и т. п.) и результаты исполнения receipt, которые их породили, — shard-локальное доказательство без догадок. Для менее активных контрактов расширьте диапазон `OFFSET`. +На mainnet `intents.near` живёт на shard 7, поэтому обход назад обычно попадает в цель за несколько блоков. Payload shard называет конкретные типы state-change (`account_update`, `data_update` и т. п.) и результаты receipt, которые их породили, — shard-локальное доказательство без догадок. Для менее активных контрактов расширьте диапазон `OFFSET`. ## Когда расширить поверхность -- Используйте [Transactions API](https://docs.fastnear.com/ru/tx), когда у вас уже есть `tx_hash` и нужен человекочитаемый рассказ о транзакции. +- Используйте [Transactions API](https://docs.fastnear.com/ru/tx), когда у вас уже есть `tx_hash` и нужна человекочитаемая история транзакции. - Используйте [RPC Reference](https://docs.fastnear.com/ru/rpc), когда следующий вопрос касается точной протокольной семантики receipt или блока. - Используйте [Block Headers](https://docs.fastnear.com/ru/neardata/block-headers), когда нужна только динамика head/finality, а не проверка contract-touch. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/first-block.md b/static/ru/neardata/first-block.md index dc1c366..1c67c57 100644 --- a/static/ru/neardata/first-block.md +++ b/static/ru/neardata/first-block.md @@ -1843,3 +1843,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/first-block/index.md b/static/ru/neardata/first-block/index.md index dc1c366..1c67c57 100644 --- a/static/ru/neardata/first-block/index.md +++ b/static/ru/neardata/first-block/index.md @@ -1843,3 +1843,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/index.md b/static/ru/neardata/index.md index fb1afdf..ff6be0e 100644 --- a/static/ru/neardata/index.md +++ b/static/ru/neardata/index.md @@ -56,3 +56,10 @@ https://testnet.neardata.xyz ### Нужна потоковая передача, а не опрос Эта поверхность предназначена для чтения через опрос почти в реальном времени. Не позиционируйте её как продукт на основе WebSocket или вебхуков. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/last-block-final.md b/static/ru/neardata/last-block-final.md index ed854bf..c28cdf6 100644 --- a/static/ru/neardata/last-block-final.md +++ b/static/ru/neardata/last-block-final.md @@ -1843,3 +1843,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/last-block-final/index.md b/static/ru/neardata/last-block-final/index.md index ed854bf..c28cdf6 100644 --- a/static/ru/neardata/last-block-final/index.md +++ b/static/ru/neardata/last-block-final/index.md @@ -1843,3 +1843,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/last-block-optimistic.md b/static/ru/neardata/last-block-optimistic.md index 81ca60c..daf1b55 100644 --- a/static/ru/neardata/last-block-optimistic.md +++ b/static/ru/neardata/last-block-optimistic.md @@ -1843,3 +1843,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/last-block-optimistic/index.md b/static/ru/neardata/last-block-optimistic/index.md index 81ca60c..daf1b55 100644 --- a/static/ru/neardata/last-block-optimistic/index.md +++ b/static/ru/neardata/last-block-optimistic/index.md @@ -1843,3 +1843,10 @@ "refName": "BlockDocument" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/system/health.md b/static/ru/neardata/system/health.md index e565e6c..84b2878 100644 --- a/static/ru/neardata/system/health.md +++ b/static/ru/neardata/system/health.md @@ -59,3 +59,10 @@ "refName": "HealthResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/system/health/index.md b/static/ru/neardata/system/health/index.md index e565e6c..84b2878 100644 --- a/static/ru/neardata/system/health/index.md +++ b/static/ru/neardata/system/health/index.md @@ -59,3 +59,10 @@ "refName": "HealthResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/redocly-config.md b/static/ru/redocly-config.md index de9f065..5b71076 100644 --- a/static/ru/redocly-config.md +++ b/static/ru/redocly-config.md @@ -48,3 +48,10 @@ Bearer-токены по-прежнему используют: - `mike-docs/README.md` - `mike-docs/INTEGRATION_GUIDE.md` - `builder-docs/CLAUDE.md` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/redocly-config/index.md b/static/ru/redocly-config/index.md index de9f065..5b71076 100644 --- a/static/ru/redocly-config/index.md +++ b/static/ru/redocly-config/index.md @@ -48,3 +48,10 @@ Bearer-токены по-прежнему используют: - `mike-docs/README.md` - `mike-docs/INTEGRATION_GUIDE.md` - `builder-docs/CLAUDE.md` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc.md b/static/ru/rpc.md index b04a1cf..36a16ca 100644 --- a/static/ru/rpc.md +++ b/static/ru/rpc.md @@ -80,3 +80,10 @@ https://archival-rpc.testnet.fastnear.com ### Мне нужен более простой ответ, чем даёт JSON-RPC Обычно это означает, что нужно индексированное REST-семейство, а не сырой RPC. Воспользуйтесь страницей выбора поверхности и подберите более высокий уровень абстракции. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/account/view-access-key-list.md b/static/ru/rpc/account/view-access-key-list.md index e801e5f..8f9a730 100644 --- a/static/ru/rpc/account/view-access-key-list.md +++ b/static/ru/rpc/account/view-access-key-list.md @@ -237,3 +237,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/account/view-access-key-list/index.md b/static/ru/rpc/account/view-access-key-list/index.md index e801e5f..8f9a730 100644 --- a/static/ru/rpc/account/view-access-key-list/index.md +++ b/static/ru/rpc/account/view-access-key-list/index.md @@ -237,3 +237,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/account/view-access-key.md b/static/ru/rpc/account/view-access-key.md index 3ac1bee..95c28ff 100644 --- a/static/ru/rpc/account/view-access-key.md +++ b/static/ru/rpc/account/view-access-key.md @@ -255,3 +255,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/account/view-access-key/index.md b/static/ru/rpc/account/view-access-key/index.md index 3ac1bee..95c28ff 100644 --- a/static/ru/rpc/account/view-access-key/index.md +++ b/static/ru/rpc/account/view-access-key/index.md @@ -255,3 +255,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/account/view-account.md b/static/ru/rpc/account/view-account.md index c3b9b91..5086c79 100644 --- a/static/ru/rpc/account/view-account.md +++ b/static/ru/rpc/account/view-account.md @@ -289,3 +289,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/account/view-account/index.md b/static/ru/rpc/account/view-account/index.md index c3b9b91..5086c79 100644 --- a/static/ru/rpc/account/view-account/index.md +++ b/static/ru/rpc/account/view-account/index.md @@ -289,3 +289,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/block/block-by-height.md b/static/ru/rpc/block/block-by-height.md index 6cd45c7..da8318a 100644 --- a/static/ru/rpc/block/block-by-height.md +++ b/static/ru/rpc/block/block-by-height.md @@ -191,3 +191,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/block/block-by-height/index.md b/static/ru/rpc/block/block-by-height/index.md index 6cd45c7..da8318a 100644 --- a/static/ru/rpc/block/block-by-height/index.md +++ b/static/ru/rpc/block/block-by-height/index.md @@ -191,3 +191,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/block/block-by-id.md b/static/ru/rpc/block/block-by-id.md index f8ac3ed..f88118c 100644 --- a/static/ru/rpc/block/block-by-id.md +++ b/static/ru/rpc/block/block-by-id.md @@ -191,3 +191,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/block/block-by-id/index.md b/static/ru/rpc/block/block-by-id/index.md index f8ac3ed..f88118c 100644 --- a/static/ru/rpc/block/block-by-id/index.md +++ b/static/ru/rpc/block/block-by-id/index.md @@ -191,3 +191,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/block/block-effects.md b/static/ru/rpc/block/block-effects.md index ba4fbad..039b7ae 100644 --- a/static/ru/rpc/block/block-effects.md +++ b/static/ru/rpc/block/block-effects.md @@ -225,3 +225,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/block/block-effects/index.md b/static/ru/rpc/block/block-effects/index.md index ba4fbad..039b7ae 100644 --- a/static/ru/rpc/block/block-effects/index.md +++ b/static/ru/rpc/block/block-effects/index.md @@ -225,3 +225,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/call-function.md b/static/ru/rpc/contract/call-function.md index 566fdc7..135c2d1 100644 --- a/static/ru/rpc/contract/call-function.md +++ b/static/ru/rpc/contract/call-function.md @@ -272,3 +272,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/call-function/index.md b/static/ru/rpc/contract/call-function/index.md index 566fdc7..135c2d1 100644 --- a/static/ru/rpc/contract/call-function/index.md +++ b/static/ru/rpc/contract/call-function/index.md @@ -272,3 +272,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-code.md b/static/ru/rpc/contract/view-code.md index cc81306..3c6532f 100644 --- a/static/ru/rpc/contract/view-code.md +++ b/static/ru/rpc/contract/view-code.md @@ -242,3 +242,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-code/index.md b/static/ru/rpc/contract/view-code/index.md index cc81306..3c6532f 100644 --- a/static/ru/rpc/contract/view-code/index.md +++ b/static/ru/rpc/contract/view-code/index.md @@ -242,3 +242,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-global-contract-code-by-account-id.md b/static/ru/rpc/contract/view-global-contract-code-by-account-id.md index bc45bc2..1142445 100644 --- a/static/ru/rpc/contract/view-global-contract-code-by-account-id.md +++ b/static/ru/rpc/contract/view-global-contract-code-by-account-id.md @@ -242,3 +242,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-global-contract-code-by-account-id/index.md b/static/ru/rpc/contract/view-global-contract-code-by-account-id/index.md index bc45bc2..1142445 100644 --- a/static/ru/rpc/contract/view-global-contract-code-by-account-id/index.md +++ b/static/ru/rpc/contract/view-global-contract-code-by-account-id/index.md @@ -242,3 +242,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-global-contract-code.md b/static/ru/rpc/contract/view-global-contract-code.md index 78908f1..e25f203 100644 --- a/static/ru/rpc/contract/view-global-contract-code.md +++ b/static/ru/rpc/contract/view-global-contract-code.md @@ -242,3 +242,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-global-contract-code/index.md b/static/ru/rpc/contract/view-global-contract-code/index.md index 78908f1..e25f203 100644 --- a/static/ru/rpc/contract/view-global-contract-code/index.md +++ b/static/ru/rpc/contract/view-global-contract-code/index.md @@ -242,3 +242,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-state.md b/static/ru/rpc/contract/view-state.md index 4731913..8d0625d 100644 --- a/static/ru/rpc/contract/view-state.md +++ b/static/ru/rpc/contract/view-state.md @@ -268,3 +268,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-state/index.md b/static/ru/rpc/contract/view-state/index.md index 4731913..8d0625d 100644 --- a/static/ru/rpc/contract/view-state/index.md +++ b/static/ru/rpc/contract/view-state/index.md @@ -268,3 +268,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index 1784303..caf180d 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -4,6 +4,27 @@ Начинайте с RPC-метода, который отвечает на вопрос. Используйте `tx`, чтобы отследить включение и финальность по хешу транзакции, и расширяйте поверхность только когда нужны дерево receipts, сырой state или трассировка на уровне shard. +## Состояние аккаунта + +### Показать баланс и storage аккаунта на finality + +`view_account` — канонический RPC-запрос для текущего состояния аккаунта. Один вызов возвращает свободный баланс, сумму, заблокированную в валидаторском стейке или lockup-контракте, использованное storage и блок, на котором было сделано чтение. `finality: "final"` гарантирует, что вы читаете стабильное состояние, а не optimistic-представление. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_account",account_id:$account_id,finality:"final"} + }')" \ + | jq '.result | {amount, locked, storage_usage, block_height, block_hash}' +``` + +Для `mike.near` это возвращает `amount` (yoctoNEAR в свободной части), `locked: "0"` (ничего в валидаторском стейке или lockup-контракте) и `storage_usage: 558441` — 558 КБ on-chain-состояния. Пара `block_height`/`block_hash` фиксирует точку чтения; чтобы прочитать несколько аккаунтов *на одном и том же* блоке, переиспользуйте возвращённый `block_hash` как `block_id` в последующих запросах. + ## Включение транзакции и финальность ### Отследить транзакцию от хеша до финальности @@ -40,7 +61,7 @@ curl -s "$RPC_URL" \ ### Описать первый action первой транзакции на текущем tip -Пройдите `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. +Блок NEAR — это header поверх N shard chunks, а не плоский список транзакций. `block` возвращает headers chunks; сами транзакции лежат уровнем ниже, внутри `chunk`. Шортката `block → tx` нет — блок не несёт хешей транзакций, поэтому `tx` (которому нужен hash) в этой цепочке не участвует. Канонический проход — `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. ```bash RPC_URL=https://rpc.mainnet.fastnear.com @@ -86,9 +107,16 @@ fi ## Механика аккаунтов и ключей -### Аудит старых function-call-ключей Near Social +### Определить function-call-ключи, которые стоит удалить + +Каждый кошелёк, шлюз и dapp-сессия, в которую вы заходите, обычно оставляет за собой function-call-ключ. Большинством из них вы больше никогда не воспользуетесь. `view_access_key_list` возвращает все ключи аккаунта; структура nonce показывает, какие из них устарели. -У создателей накапливаются Social function-call-ключи от каждого кошелька и каждого BOS-шлюза, которым они пользовались. `view_access_key_list` возвращает их все; один фильтр сужает до `social.near`, а **младшие шесть цифр nonce** заодно служат счётчиком использования — новые ключи стартуют с `block_height * 10^6` и инкрементируются на единицу за каждую транзакцию. +Новые ключи стартуют с `block_height * 10^6`, и значение инкрементируется на единицу за каждую транзакцию, которую ключ подписывает, поэтому: + +- `nonce / 10^6` → блок, в котором ключ был добавлен +- `nonce % 10^6` → сколько раз ключ был использован + +Любой ключ с `tx_count: 0` был создан и ни разу не использовался — самый очевидный кандидат на очистку. Следующий по порядку — ключи, заскоупленные на контракт, с которым вы больше не работаете. Фильтр ниже сужает до `social.near`, но чтобы аудитировать другой контракт, меняется только строка `RECEIVER_ID`. ```bash RPC_URL=https://rpc.mainnet.fastnear.com @@ -104,13 +132,13 @@ curl -s "$RPC_URL" \ | jq --arg receiver "$RECEIVER_ID" ' { total_keys: (.result.keys | length), - social_fcks: [ + fcks_for_receiver: [ .result.keys[] | select((.access_key.permission | type) == "object") | select(.access_key.permission.FunctionCall.receiver_id == $receiver) | { public_key, - created_near_block: (.access_key.nonce / 1000000 | floor), + added_at_block: (.access_key.nonce / 1000000 | floor), tx_count: (.access_key.nonce % 1000000), method_names: (.access_key.permission.FunctionCall.method_names | if . == [] then "ANY" else . end), allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") @@ -119,119 +147,17 @@ curl -s "$RPC_URL" \ }' ``` -Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на очистку. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность конкретного dapp. - -Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом — function-call-ключ не может авторизовать `DeleteKey` — и отправьте через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Повторный запуск того же списка подтверждает удаление. Само подписание — стандартная near-api-js-история и не самая интересная часть аудита. - -### Какая транзакция добавила этот `social.near` function-call-ключ и кто её авторизовал? - -Тот же nonce, что считает использование, заодно якорит `AddKey` во времени блоков: новые ключи стартуют примерно с `block_height * 10^6`, так что деление текущего nonce на миллион даёт плотное окно поиска. Один раз гидратируйте кандидатов — и ответ уже несёт достаточно, чтобы отличить прямой `AddKey` от делегированной (meta-tx) авторизации, то есть показать, *какой ключ подписал решение*, а не только какой аккаунт оплатил gas. - -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=mike.near -TARGET_PUBLIC_KEY=ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs - -CURRENT_NONCE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg public_key "$TARGET_PUBLIC_KEY" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"view_access_key",account_id:$account_id,public_key:$public_key,finality:"final"} - }')" \ - | jq -r '.result.nonce')" - -ADD_KEY_BLOCK=$((CURRENT_NONCE / 1000000)) - -TX_HASHES="$(curl -s "$TX_BASE_URL/v0/account" -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" \ - --argjson from $((ADD_KEY_BLOCK - 20)) --argjson to $((ADD_KEY_BLOCK + 5)) '{ - account_id: $account_id, is_real_signer: true, - from_tx_block_height: $from, to_tx_block_height: $to, desc: false, limit: 50 - }')" \ - | jq -c '[.account_txs[].transaction_hash]')" - -curl -s "$TX_BASE_URL/v0/transactions" -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES" '{tx_hashes: $tx_hashes}')" \ - | jq --arg target "$TARGET_PUBLIC_KEY" ' - [ .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? | .AddKey? | select(.public_key == $target) - | {mode: "direct", authorizing_public_key: $tx.transaction.public_key, permission: .access_key.permission}), - ($tx.transaction.actions[]? | .Delegate? | .delegate_action as $d - | $d.actions[]? | .AddKey? | select(.public_key == $target) - | {mode: "delegated", authorizing_public_key: $d.public_key, permission: .access_key.permission}) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - signer_id: $tx.transaction.signer_id, - receiver_id: $tx.transaction.receiver_id, - add_key_receipt: ([$tx.receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]?; .AddKey.public_key? == $target)) - | {receipt_id: .receipt.receipt_id, receipt_block: .execution_outcome.block_height}][0]) - } + . - ]' -``` - -Для ключа `ed25519:7GZg…` аккаунта `mike.near` (первый `social.near` FCK из аудита выше) это разрешается в транзакцию `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` на внешнем блоке tx `112057390`. Внешний signer — `app.herewallet.near`, это relayer HERE Wallet, и `mode: "delegated"` рассказывает остальную историю: relayer оплатил gas, но *авторизующий* ключ внутри Delegate — `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu`, full-access-ключ `mike.near`, который подписал сам `AddKey`. Это та разница meta-tx, которую верхнеуровневый `signer_id` в одиночку скрыл бы. - -`add_key_receipt` замыкает картину: `AddKey` выполнился в блоке `112057392`, через два блока после внешней tx, потому что Delegate прыгает из shard relayer в shard целевого аккаунта. Расширьте окно `-20/+5`, если ключом с момента создания пользовались активно. - -### Зарегистрировать FT-хранилище при необходимости и затем перевести токены - -Токены NEP-141 требуют, чтобы каждый получатель предварительно зарегистрировал storage на контракте, прежде чем сможет держать баланс. Два view-вызова авторитетно отвечают на вопрос регистрации *до* отправки — пропуск этой проверки и есть причина, по которой `ft_transfer` в итоге тихо возвращается отправителю. - -```bash -RPC_URL=https://rpc.testnet.fastnear.com -TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -RECEIVER_ACCOUNT_ID=mike.testnet - -ACCOUNT_ARGS_B64="$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" - -REGISTERED="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_of",args_base64:$args,finality:"final"} - }')" \ - | jq '(.result.result | implode | fromjson) != null')" - -MIN_DEPOSIT="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_bounds",args_base64:"e30=",finality:"final"} - }')" \ - | jq -r '.result.result | implode | fromjson | .min')" +Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на удаление. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность одного dapp. -jq -n --argjson registered "$REGISTERED" --arg min "$MIN_DEPOSIT" '{ - registered: $registered, - min_storage_deposit_yocto: $min -}' -``` - -Для зафиксированного testnet-контракта `storage_balance_of({account_id: "mike.testnet"})` возвращает `null` (не зарегистрирован), а `storage_balance_bounds` возвращает `{min: "1250000000000000000000", max: "1250000000000000000000"}` — плоскую комиссию регистрации 0.00125 NEAR. Это собственный ответ контракта, и большего на read-стороне до записи не нужно. - -Write-сторона — это две подписанных function call (near-api-js `transactions.functionCall` или любая NEAR-библиотека подписи работает одинаково): - -- `storage_deposit({account_id: "", registration_only: true})` с депозитом `` yocto и 100 Tgas — пропустите, если `registered: true`. -- `ft_transfer({receiver_id: "", amount: "", memo: "..."})` с депозитом 1 yocto (требует NEP-141) и 100 Tgas. - -Отправьте каждую подписанную транзакцию через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) с `wait_until: "FINAL"`. После этого подтвердите через собственный view-метод контракта — индексированная история не нужна, чтобы доказать, что перевод закрепился: - -```bash -curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"ft_balance_of",args_base64:$args,finality:"final"} - }')" \ - | jq '{receiver_balance: (.result.result | implode | fromjson)}' -``` +Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом (function-call-ключ не может авторизовать `DeleteKey`) и отправьте через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Повторный запуск того же запроса подтвердит, что ключа больше нет. ## Чтение контрактов и сырой state -### Как прочитать сырое storage контракта напрямую? +### Прочитать storage контракта, не запуская его -Два RPC-метода отвечают на один и тот же вопрос о counter с разных слоёв: `view_state` достаёт сырые байты trie без запуска кода, а `call_function` запускает собственный view-метод контракта. Когда они совпадают, вы доказали, что view-метод контракта соответствует его сохранённому состоянию. +View-метод вроде `get_num` всё равно заставляет узел загрузить wasm-контракта и выполнить его. Если ключ storage уже известен, `view_state` возвращает сырые сериализованные байты напрямую — без исполнения и без зависимости от того, выставил ли контракт getter для этого поля вообще. + +Контракты на `near-sdk-rs` хранят верхнеуровневую `#[near_bindgen]`-структуру под ключом `STATE`. Передайте `STATE` как `prefix_base64` (`U1RBVEU=` — это base64 тех же четырёх ASCII-байт), и узел вернёт сериализованное значение. ```bash RPC_URL=https://rpc.testnet.fastnear.com @@ -244,26 +170,14 @@ RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ }')" \ | jq -r '.result.values[0].value')" -RAW_I8="$(python3 -c "import base64,sys;print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" +DECODED_I8="$(python3 -c "import base64; print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" -METHOD_VALUE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"get_num",args_base64:"e30=",finality:"final"} - }')" \ - | jq -r '.result.result | implode | fromjson')" - -jq -n --arg raw_b64 "$RAW_B64" --argjson raw_i8 "$RAW_I8" --argjson method "$METHOD_VALUE" '{ - raw_state_b64: $raw_b64, - raw_state_decoded: $raw_i8, - view_method_value: $method, - agree: ($raw_i8 == $method) -}' +jq -n --arg raw "$RAW_B64" --argjson val "$DECODED_I8" '{raw_bytes_base64: $raw, decoded_i8: $val}' ``` -Для живого counter `view_state` по ключу `STATE` (base64 `U1RBVEU=`) возвращает `"CQ=="` — один байт `0x09`, декодируется как signed i8 в `9`; `get_num` тоже возвращает `9`. Они совпадают, потому что контракт хранит `val: i8` по этому ключу. `signed=True` важен: отрицательный counter выглядел бы как `"/w=="` (байт `0xff` → i8 `-1`, а не u8 `255`). +Для живого counter это возвращает `"CQ=="` — один байт `0x09`, декодируется как signed i8 в `9`. Это то же число, которое вернул бы `get_num`, только прочитанное прямо из trie без запуска кода контракта. `signed=True` важен: отрицательный counter сериализовался бы как `"/w=="` (байт `0xff` → i8 `-1`, а не u8 `255`). -`view_state` — правильный инструмент, когда у контракта нет view-метода для нужных данных, когда нужно сверить view-метод с реальным storage или когда нужна семья ключей, которую контракт не раскрывает публично. Для всего остального `call_function` требует меньше церемоний. Если следующий вопрос становится историческим, а не текущим, расширяйте поверхность до [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). +Тянитесь к `view_state`, когда контракт не выставляет view-метод для нужных данных или когда нужна семья ключей, которую контракт не публикует. Для большинства чтений `call_function` всё равно требует меньше церемоний. Если вопрос становится историческим, а не текущим, расширяйте поверхность до [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). ## NEAR Social и точные чтения BOS @@ -368,3 +282,10 @@ curl -s "$RPC_URL" -H 'content-type: application/json' \ - [Transactions API](https://docs.fastnear.com/ru/tx) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index 1784303..caf180d 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -4,6 +4,27 @@ Начинайте с RPC-метода, который отвечает на вопрос. Используйте `tx`, чтобы отследить включение и финальность по хешу транзакции, и расширяйте поверхность только когда нужны дерево receipts, сырой state или трассировка на уровне shard. +## Состояние аккаунта + +### Показать баланс и storage аккаунта на finality + +`view_account` — канонический RPC-запрос для текущего состояния аккаунта. Один вызов возвращает свободный баланс, сумму, заблокированную в валидаторском стейке или lockup-контракте, использованное storage и блок, на котором было сделано чтение. `finality: "final"` гарантирует, что вы читаете стабильное состояние, а не optimistic-представление. + +```bash +RPC_URL=https://rpc.mainnet.fastnear.com +ACCOUNT_ID=mike.near + +curl -s "$RPC_URL" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + jsonrpc:"2.0",id:"fastnear",method:"query", + params:{request_type:"view_account",account_id:$account_id,finality:"final"} + }')" \ + | jq '.result | {amount, locked, storage_usage, block_height, block_hash}' +``` + +Для `mike.near` это возвращает `amount` (yoctoNEAR в свободной части), `locked: "0"` (ничего в валидаторском стейке или lockup-контракте) и `storage_usage: 558441` — 558 КБ on-chain-состояния. Пара `block_height`/`block_hash` фиксирует точку чтения; чтобы прочитать несколько аккаунтов *на одном и том же* блоке, переиспользуйте возвращённый `block_hash` как `block_id` в последующих запросах. + ## Включение транзакции и финальность ### Отследить транзакцию от хеша до финальности @@ -40,7 +61,7 @@ curl -s "$RPC_URL" \ ### Описать первый action первой транзакции на текущем tip -Пройдите `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. +Блок NEAR — это header поверх N shard chunks, а не плоский список транзакций. `block` возвращает headers chunks; сами транзакции лежат уровнем ниже, внутри `chunk`. Шортката `block → tx` нет — блок не несёт хешей транзакций, поэтому `tx` (которому нужен hash) в этой цепочке не участвует. Канонический проход — `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. ```bash RPC_URL=https://rpc.mainnet.fastnear.com @@ -86,9 +107,16 @@ fi ## Механика аккаунтов и ключей -### Аудит старых function-call-ключей Near Social +### Определить function-call-ключи, которые стоит удалить + +Каждый кошелёк, шлюз и dapp-сессия, в которую вы заходите, обычно оставляет за собой function-call-ключ. Большинством из них вы больше никогда не воспользуетесь. `view_access_key_list` возвращает все ключи аккаунта; структура nonce показывает, какие из них устарели. -У создателей накапливаются Social function-call-ключи от каждого кошелька и каждого BOS-шлюза, которым они пользовались. `view_access_key_list` возвращает их все; один фильтр сужает до `social.near`, а **младшие шесть цифр nonce** заодно служат счётчиком использования — новые ключи стартуют с `block_height * 10^6` и инкрементируются на единицу за каждую транзакцию. +Новые ключи стартуют с `block_height * 10^6`, и значение инкрементируется на единицу за каждую транзакцию, которую ключ подписывает, поэтому: + +- `nonce / 10^6` → блок, в котором ключ был добавлен +- `nonce % 10^6` → сколько раз ключ был использован + +Любой ключ с `tx_count: 0` был создан и ни разу не использовался — самый очевидный кандидат на очистку. Следующий по порядку — ключи, заскоупленные на контракт, с которым вы больше не работаете. Фильтр ниже сужает до `social.near`, но чтобы аудитировать другой контракт, меняется только строка `RECEIVER_ID`. ```bash RPC_URL=https://rpc.mainnet.fastnear.com @@ -104,13 +132,13 @@ curl -s "$RPC_URL" \ | jq --arg receiver "$RECEIVER_ID" ' { total_keys: (.result.keys | length), - social_fcks: [ + fcks_for_receiver: [ .result.keys[] | select((.access_key.permission | type) == "object") | select(.access_key.permission.FunctionCall.receiver_id == $receiver) | { public_key, - created_near_block: (.access_key.nonce / 1000000 | floor), + added_at_block: (.access_key.nonce / 1000000 | floor), tx_count: (.access_key.nonce % 1000000), method_names: (.access_key.permission.FunctionCall.method_names | if . == [] then "ANY" else . end), allowance: (.access_key.permission.FunctionCall.allowance // "unlimited") @@ -119,119 +147,17 @@ curl -s "$RPC_URL" \ }' ``` -Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на очистку. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность конкретного dapp. - -Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом — function-call-ключ не может авторизовать `DeleteKey` — и отправьте через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Повторный запуск того же списка подтверждает удаление. Само подписание — стандартная near-api-js-история и не самая интересная часть аудита. - -### Какая транзакция добавила этот `social.near` function-call-ключ и кто её авторизовал? - -Тот же nonce, что считает использование, заодно якорит `AddKey` во времени блоков: новые ключи стартуют примерно с `block_height * 10^6`, так что деление текущего nonce на миллион даёт плотное окно поиска. Один раз гидратируйте кандидатов — и ответ уже несёт достаточно, чтобы отличить прямой `AddKey` от делегированной (meta-tx) авторизации, то есть показать, *какой ключ подписал решение*, а не только какой аккаунт оплатил gas. - -```bash -RPC_URL=https://rpc.mainnet.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=mike.near -TARGET_PUBLIC_KEY=ed25519:7GZgXkMPEyGXqRhxaLvHxWn6fVfeyuQGMqnLVQAh7bs - -CURRENT_NONCE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg public_key "$TARGET_PUBLIC_KEY" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"view_access_key",account_id:$account_id,public_key:$public_key,finality:"final"} - }')" \ - | jq -r '.result.nonce')" - -ADD_KEY_BLOCK=$((CURRENT_NONCE / 1000000)) - -TX_HASHES="$(curl -s "$TX_BASE_URL/v0/account" -H 'content-type: application/json' \ - --data "$(jq -nc --arg account_id "$ACCOUNT_ID" \ - --argjson from $((ADD_KEY_BLOCK - 20)) --argjson to $((ADD_KEY_BLOCK + 5)) '{ - account_id: $account_id, is_real_signer: true, - from_tx_block_height: $from, to_tx_block_height: $to, desc: false, limit: 50 - }')" \ - | jq -c '[.account_txs[].transaction_hash]')" - -curl -s "$TX_BASE_URL/v0/transactions" -H 'content-type: application/json' \ - --data "$(jq -nc --argjson tx_hashes "$TX_HASHES" '{tx_hashes: $tx_hashes}')" \ - | jq --arg target "$TARGET_PUBLIC_KEY" ' - [ .transactions[] - | . as $tx - | ( - ($tx.transaction.actions[]? | .AddKey? | select(.public_key == $target) - | {mode: "direct", authorizing_public_key: $tx.transaction.public_key, permission: .access_key.permission}), - ($tx.transaction.actions[]? | .Delegate? | .delegate_action as $d - | $d.actions[]? | .AddKey? | select(.public_key == $target) - | {mode: "delegated", authorizing_public_key: $d.public_key, permission: .access_key.permission}) - ) - | { - transaction_hash: $tx.transaction.hash, - tx_block_height: $tx.execution_outcome.block_height, - signer_id: $tx.transaction.signer_id, - receiver_id: $tx.transaction.receiver_id, - add_key_receipt: ([$tx.receipts[] - | select(any((.receipt.receipt.Action.actions // [])[]?; .AddKey.public_key? == $target)) - | {receipt_id: .receipt.receipt_id, receipt_block: .execution_outcome.block_height}][0]) - } + . - ]' -``` - -Для ключа `ed25519:7GZg…` аккаунта `mike.near` (первый `social.near` FCK из аудита выше) это разрешается в транзакцию `6ZT8UGPRC6L3NGs2qHnECPVexKWNQ5LWLK9w95tgj3tV` на внешнем блоке tx `112057390`. Внешний signer — `app.herewallet.near`, это relayer HERE Wallet, и `mode: "delegated"` рассказывает остальную историю: relayer оплатил gas, но *авторизующий* ключ внутри Delegate — `ed25519:GaYgzN1eZUgwA7t8a5pYxFGqtF4kon9dQaDMjPDejsiu`, full-access-ключ `mike.near`, который подписал сам `AddKey`. Это та разница meta-tx, которую верхнеуровневый `signer_id` в одиночку скрыл бы. - -`add_key_receipt` замыкает картину: `AddKey` выполнился в блоке `112057392`, через два блока после внешней tx, потому что Delegate прыгает из shard relayer в shard целевого аккаунта. Расширьте окно `-20/+5`, если ключом с момента создания пользовались активно. - -### Зарегистрировать FT-хранилище при необходимости и затем перевести токены - -Токены NEP-141 требуют, чтобы каждый получатель предварительно зарегистрировал storage на контракте, прежде чем сможет держать баланс. Два view-вызова авторитетно отвечают на вопрос регистрации *до* отправки — пропуск этой проверки и есть причина, по которой `ft_transfer` в итоге тихо возвращается отправителю. - -```bash -RPC_URL=https://rpc.testnet.fastnear.com -TOKEN_CONTRACT_ID=ft.predeployed.examples.testnet -RECEIVER_ACCOUNT_ID=mike.testnet - -ACCOUNT_ARGS_B64="$(jq -nc --arg account_id "$RECEIVER_ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" - -REGISTERED="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_of",args_base64:$args,finality:"final"} - }')" \ - | jq '(.result.result | implode | fromjson) != null')" - -MIN_DEPOSIT="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"storage_balance_bounds",args_base64:"e30=",finality:"final"} - }')" \ - | jq -r '.result.result | implode | fromjson | .min')" +Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на удаление. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность одного dapp. -jq -n --argjson registered "$REGISTERED" --arg min "$MIN_DEPOSIT" '{ - registered: $registered, - min_storage_deposit_yocto: $min -}' -``` - -Для зафиксированного testnet-контракта `storage_balance_of({account_id: "mike.testnet"})` возвращает `null` (не зарегистрирован), а `storage_balance_bounds` возвращает `{min: "1250000000000000000000", max: "1250000000000000000000"}` — плоскую комиссию регистрации 0.00125 NEAR. Это собственный ответ контракта, и большего на read-стороне до записи не нужно. - -Write-сторона — это две подписанных function call (near-api-js `transactions.functionCall` или любая NEAR-библиотека подписи работает одинаково): - -- `storage_deposit({account_id: "", registration_only: true})` с депозитом `` yocto и 100 Tgas — пропустите, если `registered: true`. -- `ft_transfer({receiver_id: "", amount: "", memo: "..."})` с депозитом 1 yocto (требует NEP-141) и 100 Tgas. - -Отправьте каждую подписанную транзакцию через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx) с `wait_until: "FINAL"`. После этого подтвердите через собственный view-метод контракта — индексированная история не нужна, чтобы доказать, что перевод закрепился: - -```bash -curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$TOKEN_CONTRACT_ID" --arg args "$ACCOUNT_ARGS_B64" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"ft_balance_of",args_base64:$args,finality:"final"} - }')" \ - | jq '{receiver_balance: (.result.result | implode | fromjson)}' -``` +Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом (function-call-ключ не может авторизовать `DeleteKey`) и отправьте через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Повторный запуск того же запроса подтвердит, что ключа больше нет. ## Чтение контрактов и сырой state -### Как прочитать сырое storage контракта напрямую? +### Прочитать storage контракта, не запуская его -Два RPC-метода отвечают на один и тот же вопрос о counter с разных слоёв: `view_state` достаёт сырые байты trie без запуска кода, а `call_function` запускает собственный view-метод контракта. Когда они совпадают, вы доказали, что view-метод контракта соответствует его сохранённому состоянию. +View-метод вроде `get_num` всё равно заставляет узел загрузить wasm-контракта и выполнить его. Если ключ storage уже известен, `view_state` возвращает сырые сериализованные байты напрямую — без исполнения и без зависимости от того, выставил ли контракт getter для этого поля вообще. + +Контракты на `near-sdk-rs` хранят верхнеуровневую `#[near_bindgen]`-структуру под ключом `STATE`. Передайте `STATE` как `prefix_base64` (`U1RBVEU=` — это base64 тех же четырёх ASCII-байт), и узел вернёт сериализованное значение. ```bash RPC_URL=https://rpc.testnet.fastnear.com @@ -244,26 +170,14 @@ RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ }')" \ | jq -r '.result.values[0].value')" -RAW_I8="$(python3 -c "import base64,sys;print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" +DECODED_I8="$(python3 -c "import base64; print(int.from_bytes(base64.b64decode('$RAW_B64'),'little',signed=True))")" -METHOD_VALUE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ - --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ - jsonrpc:"2.0",id:"fastnear",method:"query", - params:{request_type:"call_function",account_id:$contract,method_name:"get_num",args_base64:"e30=",finality:"final"} - }')" \ - | jq -r '.result.result | implode | fromjson')" - -jq -n --arg raw_b64 "$RAW_B64" --argjson raw_i8 "$RAW_I8" --argjson method "$METHOD_VALUE" '{ - raw_state_b64: $raw_b64, - raw_state_decoded: $raw_i8, - view_method_value: $method, - agree: ($raw_i8 == $method) -}' +jq -n --arg raw "$RAW_B64" --argjson val "$DECODED_I8" '{raw_bytes_base64: $raw, decoded_i8: $val}' ``` -Для живого counter `view_state` по ключу `STATE` (base64 `U1RBVEU=`) возвращает `"CQ=="` — один байт `0x09`, декодируется как signed i8 в `9`; `get_num` тоже возвращает `9`. Они совпадают, потому что контракт хранит `val: i8` по этому ключу. `signed=True` важен: отрицательный counter выглядел бы как `"/w=="` (байт `0xff` → i8 `-1`, а не u8 `255`). +Для живого counter это возвращает `"CQ=="` — один байт `0x09`, декодируется как signed i8 в `9`. Это то же число, которое вернул бы `get_num`, только прочитанное прямо из trie без запуска кода контракта. `signed=True` важен: отрицательный counter сериализовался бы как `"/w=="` (байт `0xff` → i8 `-1`, а не u8 `255`). -`view_state` — правильный инструмент, когда у контракта нет view-метода для нужных данных, когда нужно сверить view-метод с реальным storage или когда нужна семья ключей, которую контракт не раскрывает публично. Для всего остального `call_function` требует меньше церемоний. Если следующий вопрос становится историческим, а не текущим, расширяйте поверхность до [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). +Тянитесь к `view_state`, когда контракт не выставляет view-метод для нужных данных или когда нужна семья ключей, которую контракт не публикует. Для большинства чтений `call_function` всё равно требует меньше церемоний. Если вопрос становится историческим, а не текущим, расширяйте поверхность до [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). ## NEAR Social и точные чтения BOS @@ -368,3 +282,10 @@ curl -s "$RPC_URL" -H 'content-type: application/json' \ - [Transactions API](https://docs.fastnear.com/ru/tx) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/index.md b/static/ru/rpc/index.md index b04a1cf..36a16ca 100644 --- a/static/ru/rpc/index.md +++ b/static/ru/rpc/index.md @@ -80,3 +80,10 @@ https://archival-rpc.testnet.fastnear.com ### Мне нужен более простой ответ, чем даёт JSON-RPC Обычно это означает, что нужно индексированное REST-семейство, а не сырой RPC. Воспользуйтесь страницей выбора поверхности и подберите более высокий уровень абстракции. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/changes.md b/static/ru/rpc/protocol/changes.md index ae95691..79bf155 100644 --- a/static/ru/rpc/protocol/changes.md +++ b/static/ru/rpc/protocol/changes.md @@ -222,3 +222,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/changes/index.md b/static/ru/rpc/protocol/changes/index.md index ae95691..79bf155 100644 --- a/static/ru/rpc/protocol/changes/index.md +++ b/static/ru/rpc/protocol/changes/index.md @@ -222,3 +222,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/chunk-by-block-shard.md b/static/ru/rpc/protocol/chunk-by-block-shard.md index 36f3242..666b593 100644 --- a/static/ru/rpc/protocol/chunk-by-block-shard.md +++ b/static/ru/rpc/protocol/chunk-by-block-shard.md @@ -442,3 +442,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/chunk-by-block-shard/index.md b/static/ru/rpc/protocol/chunk-by-block-shard/index.md index 36f3242..666b593 100644 --- a/static/ru/rpc/protocol/chunk-by-block-shard/index.md +++ b/static/ru/rpc/protocol/chunk-by-block-shard/index.md @@ -442,3 +442,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/chunk-by-hash.md b/static/ru/rpc/protocol/chunk-by-hash.md index 62c5b03..0a6f714 100644 --- a/static/ru/rpc/protocol/chunk-by-hash.md +++ b/static/ru/rpc/protocol/chunk-by-hash.md @@ -421,3 +421,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/chunk-by-hash/index.md b/static/ru/rpc/protocol/chunk-by-hash/index.md index 62c5b03..0a6f714 100644 --- a/static/ru/rpc/protocol/chunk-by-hash/index.md +++ b/static/ru/rpc/protocol/chunk-by-hash/index.md @@ -421,3 +421,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/client-config.md b/static/ru/rpc/protocol/client-config.md index 3973433..5426d54 100644 --- a/static/ru/rpc/protocol/client-config.md +++ b/static/ru/rpc/protocol/client-config.md @@ -1028,3 +1028,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/client-config/index.md b/static/ru/rpc/protocol/client-config/index.md index 3973433..5426d54 100644 --- a/static/ru/rpc/protocol/client-config/index.md +++ b/static/ru/rpc/protocol/client-config/index.md @@ -1028,3 +1028,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-congestion-level.md b/static/ru/rpc/protocol/experimental-congestion-level.md index 38442dd..0fbe787 100644 --- a/static/ru/rpc/protocol/experimental-congestion-level.md +++ b/static/ru/rpc/protocol/experimental-congestion-level.md @@ -226,3 +226,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-congestion-level/index.md b/static/ru/rpc/protocol/experimental-congestion-level/index.md index 38442dd..0fbe787 100644 --- a/static/ru/rpc/protocol/experimental-congestion-level/index.md +++ b/static/ru/rpc/protocol/experimental-congestion-level/index.md @@ -226,3 +226,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-light-client-block-proof.md b/static/ru/rpc/protocol/experimental-light-client-block-proof.md index 9d6159c..778c5c6 100644 --- a/static/ru/rpc/protocol/experimental-light-client-block-proof.md +++ b/static/ru/rpc/protocol/experimental-light-client-block-proof.md @@ -257,3 +257,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-light-client-block-proof/index.md b/static/ru/rpc/protocol/experimental-light-client-block-proof/index.md index 9d6159c..778c5c6 100644 --- a/static/ru/rpc/protocol/experimental-light-client-block-proof/index.md +++ b/static/ru/rpc/protocol/experimental-light-client-block-proof/index.md @@ -257,3 +257,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-light-client-proof.md b/static/ru/rpc/protocol/experimental-light-client-proof.md index 43e4556..34280a7 100644 --- a/static/ru/rpc/protocol/experimental-light-client-proof.md +++ b/static/ru/rpc/protocol/experimental-light-client-proof.md @@ -359,3 +359,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-light-client-proof/index.md b/static/ru/rpc/protocol/experimental-light-client-proof/index.md index 43e4556..34280a7 100644 --- a/static/ru/rpc/protocol/experimental-light-client-proof/index.md +++ b/static/ru/rpc/protocol/experimental-light-client-proof/index.md @@ -359,3 +359,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-protocol-config.md b/static/ru/rpc/protocol/experimental-protocol-config.md index 5436e52..a7bebf7 100644 --- a/static/ru/rpc/protocol/experimental-protocol-config.md +++ b/static/ru/rpc/protocol/experimental-protocol-config.md @@ -543,3 +543,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-protocol-config/index.md b/static/ru/rpc/protocol/experimental-protocol-config/index.md index 5436e52..a7bebf7 100644 --- a/static/ru/rpc/protocol/experimental-protocol-config/index.md +++ b/static/ru/rpc/protocol/experimental-protocol-config/index.md @@ -543,3 +543,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-split-storage-info.md b/static/ru/rpc/protocol/experimental-split-storage-info.md index 20da6d8..ec72637 100644 --- a/static/ru/rpc/protocol/experimental-split-storage-info.md +++ b/static/ru/rpc/protocol/experimental-split-storage-info.md @@ -212,3 +212,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-split-storage-info/index.md b/static/ru/rpc/protocol/experimental-split-storage-info/index.md index 20da6d8..ec72637 100644 --- a/static/ru/rpc/protocol/experimental-split-storage-info/index.md +++ b/static/ru/rpc/protocol/experimental-split-storage-info/index.md @@ -212,3 +212,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/gas-price-by-block.md b/static/ru/rpc/protocol/gas-price-by-block.md index f428b09..a406fb9 100644 --- a/static/ru/rpc/protocol/gas-price-by-block.md +++ b/static/ru/rpc/protocol/gas-price-by-block.md @@ -213,3 +213,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/gas-price-by-block/index.md b/static/ru/rpc/protocol/gas-price-by-block/index.md index f428b09..a406fb9 100644 --- a/static/ru/rpc/protocol/gas-price-by-block/index.md +++ b/static/ru/rpc/protocol/gas-price-by-block/index.md @@ -213,3 +213,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/gas-price.md b/static/ru/rpc/protocol/gas-price.md index cac921d..fc7e998 100644 --- a/static/ru/rpc/protocol/gas-price.md +++ b/static/ru/rpc/protocol/gas-price.md @@ -195,3 +195,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/gas-price/index.md b/static/ru/rpc/protocol/gas-price/index.md index cac921d..fc7e998 100644 --- a/static/ru/rpc/protocol/gas-price/index.md +++ b/static/ru/rpc/protocol/gas-price/index.md @@ -195,3 +195,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/genesis-config.md b/static/ru/rpc/protocol/genesis-config.md index 752af01..63abf19 100644 --- a/static/ru/rpc/protocol/genesis-config.md +++ b/static/ru/rpc/protocol/genesis-config.md @@ -596,3 +596,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/genesis-config/index.md b/static/ru/rpc/protocol/genesis-config/index.md index 752af01..63abf19 100644 --- a/static/ru/rpc/protocol/genesis-config/index.md +++ b/static/ru/rpc/protocol/genesis-config/index.md @@ -596,3 +596,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/health.md b/static/ru/rpc/protocol/health.md index 35ea5a5..07e9805 100644 --- a/static/ru/rpc/protocol/health.md +++ b/static/ru/rpc/protocol/health.md @@ -175,3 +175,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/health/index.md b/static/ru/rpc/protocol/health/index.md index 35ea5a5..07e9805 100644 --- a/static/ru/rpc/protocol/health/index.md +++ b/static/ru/rpc/protocol/health/index.md @@ -175,3 +175,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/latest-block.md b/static/ru/rpc/protocol/latest-block.md index 240ce98..9376f6a 100644 --- a/static/ru/rpc/protocol/latest-block.md +++ b/static/ru/rpc/protocol/latest-block.md @@ -196,3 +196,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/latest-block/index.md b/static/ru/rpc/protocol/latest-block/index.md index 240ce98..9376f6a 100644 --- a/static/ru/rpc/protocol/latest-block/index.md +++ b/static/ru/rpc/protocol/latest-block/index.md @@ -196,3 +196,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/light-client-proof.md b/static/ru/rpc/protocol/light-client-proof.md index f7b54a4..19d9c06 100644 --- a/static/ru/rpc/protocol/light-client-proof.md +++ b/static/ru/rpc/protocol/light-client-proof.md @@ -359,3 +359,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/light-client-proof/index.md b/static/ru/rpc/protocol/light-client-proof/index.md index f7b54a4..19d9c06 100644 --- a/static/ru/rpc/protocol/light-client-proof/index.md +++ b/static/ru/rpc/protocol/light-client-proof/index.md @@ -359,3 +359,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/maintenance-windows.md b/static/ru/rpc/protocol/maintenance-windows.md index 695dbca..aad205d 100644 --- a/static/ru/rpc/protocol/maintenance-windows.md +++ b/static/ru/rpc/protocol/maintenance-windows.md @@ -216,3 +216,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/maintenance-windows/index.md b/static/ru/rpc/protocol/maintenance-windows/index.md index 695dbca..aad205d 100644 --- a/static/ru/rpc/protocol/maintenance-windows/index.md +++ b/static/ru/rpc/protocol/maintenance-windows/index.md @@ -216,3 +216,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/metrics.md b/static/ru/rpc/protocol/metrics.md index 4f910f2..e93c0ad 100644 --- a/static/ru/rpc/protocol/metrics.md +++ b/static/ru/rpc/protocol/metrics.md @@ -43,3 +43,10 @@ "example": "# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.\n# TYPE process_cpu_seconds_total counter\nprocess_cpu_seconds_total 12.34" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/metrics/index.md b/static/ru/rpc/protocol/metrics/index.md index 4f910f2..e93c0ad 100644 --- a/static/ru/rpc/protocol/metrics/index.md +++ b/static/ru/rpc/protocol/metrics/index.md @@ -43,3 +43,10 @@ "example": "# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.\n# TYPE process_cpu_seconds_total counter\nprocess_cpu_seconds_total 12.34" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/network-info.md b/static/ru/rpc/protocol/network-info.md index 0a1182f..e882265 100644 --- a/static/ru/rpc/protocol/network-info.md +++ b/static/ru/rpc/protocol/network-info.md @@ -237,3 +237,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/network-info/index.md b/static/ru/rpc/protocol/network-info/index.md index 0a1182f..e882265 100644 --- a/static/ru/rpc/protocol/network-info/index.md +++ b/static/ru/rpc/protocol/network-info/index.md @@ -237,3 +237,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/next-light-client-block.md b/static/ru/rpc/protocol/next-light-client-block.md index 549da18..477df32 100644 --- a/static/ru/rpc/protocol/next-light-client-block.md +++ b/static/ru/rpc/protocol/next-light-client-block.md @@ -334,3 +334,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/next-light-client-block/index.md b/static/ru/rpc/protocol/next-light-client-block/index.md index 549da18..477df32 100644 --- a/static/ru/rpc/protocol/next-light-client-block/index.md +++ b/static/ru/rpc/protocol/next-light-client-block/index.md @@ -334,3 +334,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/status.md b/static/ru/rpc/protocol/status.md index e25b0f9..1df337b 100644 --- a/static/ru/rpc/protocol/status.md +++ b/static/ru/rpc/protocol/status.md @@ -494,3 +494,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/status/index.md b/static/ru/rpc/protocol/status/index.md index e25b0f9..1df337b 100644 --- a/static/ru/rpc/protocol/status/index.md +++ b/static/ru/rpc/protocol/status/index.md @@ -494,3 +494,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/broadcast-tx-async.md b/static/ru/rpc/transaction/broadcast-tx-async.md index ed31a36..c861162 100644 --- a/static/ru/rpc/transaction/broadcast-tx-async.md +++ b/static/ru/rpc/transaction/broadcast-tx-async.md @@ -210,3 +210,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/broadcast-tx-async/index.md b/static/ru/rpc/transaction/broadcast-tx-async/index.md index ed31a36..c861162 100644 --- a/static/ru/rpc/transaction/broadcast-tx-async/index.md +++ b/static/ru/rpc/transaction/broadcast-tx-async/index.md @@ -210,3 +210,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/broadcast-tx-commit.md b/static/ru/rpc/transaction/broadcast-tx-commit.md index a3dade9..6d0b207 100644 --- a/static/ru/rpc/transaction/broadcast-tx-commit.md +++ b/static/ru/rpc/transaction/broadcast-tx-commit.md @@ -276,3 +276,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/broadcast-tx-commit/index.md b/static/ru/rpc/transaction/broadcast-tx-commit/index.md index a3dade9..6d0b207 100644 --- a/static/ru/rpc/transaction/broadcast-tx-commit/index.md +++ b/static/ru/rpc/transaction/broadcast-tx-commit/index.md @@ -276,3 +276,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/experimental-receipt.md b/static/ru/rpc/transaction/experimental-receipt.md index ebbe988..6a66204 100644 --- a/static/ru/rpc/transaction/experimental-receipt.md +++ b/static/ru/rpc/transaction/experimental-receipt.md @@ -241,3 +241,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/experimental-receipt/index.md b/static/ru/rpc/transaction/experimental-receipt/index.md index ebbe988..6a66204 100644 --- a/static/ru/rpc/transaction/experimental-receipt/index.md +++ b/static/ru/rpc/transaction/experimental-receipt/index.md @@ -241,3 +241,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/experimental-tx-status.md b/static/ru/rpc/transaction/experimental-tx-status.md index b2bf419..17b68b3 100644 --- a/static/ru/rpc/transaction/experimental-tx-status.md +++ b/static/ru/rpc/transaction/experimental-tx-status.md @@ -295,3 +295,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/experimental-tx-status/index.md b/static/ru/rpc/transaction/experimental-tx-status/index.md index b2bf419..17b68b3 100644 --- a/static/ru/rpc/transaction/experimental-tx-status/index.md +++ b/static/ru/rpc/transaction/experimental-tx-status/index.md @@ -295,3 +295,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/send-tx.md b/static/ru/rpc/transaction/send-tx.md index 190c819..1536d21 100644 --- a/static/ru/rpc/transaction/send-tx.md +++ b/static/ru/rpc/transaction/send-tx.md @@ -278,3 +278,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/send-tx/index.md b/static/ru/rpc/transaction/send-tx/index.md index 190c819..1536d21 100644 --- a/static/ru/rpc/transaction/send-tx/index.md +++ b/static/ru/rpc/transaction/send-tx/index.md @@ -278,3 +278,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/tx-status.md b/static/ru/rpc/transaction/tx-status.md index 42b7be5..b970384 100644 --- a/static/ru/rpc/transaction/tx-status.md +++ b/static/ru/rpc/transaction/tx-status.md @@ -293,3 +293,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/tx-status/index.md b/static/ru/rpc/transaction/tx-status/index.md index 42b7be5..b970384 100644 --- a/static/ru/rpc/transaction/tx-status/index.md +++ b/static/ru/rpc/transaction/tx-status/index.md @@ -293,3 +293,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/validators/experimental-validators-ordered.md b/static/ru/rpc/validators/experimental-validators-ordered.md index 861c031..753b3d9 100644 --- a/static/ru/rpc/validators/experimental-validators-ordered.md +++ b/static/ru/rpc/validators/experimental-validators-ordered.md @@ -204,3 +204,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/validators/experimental-validators-ordered/index.md b/static/ru/rpc/validators/experimental-validators-ordered/index.md index 861c031..753b3d9 100644 --- a/static/ru/rpc/validators/experimental-validators-ordered/index.md +++ b/static/ru/rpc/validators/experimental-validators-ordered/index.md @@ -204,3 +204,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/validators/validators-by-epoch.md b/static/ru/rpc/validators/validators-by-epoch.md index 095c8dd..f66841e 100644 --- a/static/ru/rpc/validators/validators-by-epoch.md +++ b/static/ru/rpc/validators/validators-by-epoch.md @@ -292,3 +292,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/validators/validators-by-epoch/index.md b/static/ru/rpc/validators/validators-by-epoch/index.md index 095c8dd..f66841e 100644 --- a/static/ru/rpc/validators/validators-by-epoch/index.md +++ b/static/ru/rpc/validators/validators-by-epoch/index.md @@ -292,3 +292,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/validators/validators-current.md b/static/ru/rpc/validators/validators-current.md index 37bbde5..2ef9752 100644 --- a/static/ru/rpc/validators/validators-current.md +++ b/static/ru/rpc/validators/validators-current.md @@ -283,3 +283,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/validators/validators-current/index.md b/static/ru/rpc/validators/validators-current/index.md index 37bbde5..2ef9752 100644 --- a/static/ru/rpc/validators/validators-current/index.md +++ b/static/ru/rpc/validators/validators-current/index.md @@ -283,3 +283,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/account/view_access_key.md b/static/ru/rpcs/account/view_access_key.md index a13d86e..b04408b 100644 --- a/static/ru/rpcs/account/view_access_key.md +++ b/static/ru/rpcs/account/view_access_key.md @@ -254,3 +254,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/account/view_access_key/index.md b/static/ru/rpcs/account/view_access_key/index.md index a13d86e..b04408b 100644 --- a/static/ru/rpcs/account/view_access_key/index.md +++ b/static/ru/rpcs/account/view_access_key/index.md @@ -254,3 +254,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/account/view_access_key_list.md b/static/ru/rpcs/account/view_access_key_list.md index 824a410..48df69d 100644 --- a/static/ru/rpcs/account/view_access_key_list.md +++ b/static/ru/rpcs/account/view_access_key_list.md @@ -236,3 +236,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/account/view_access_key_list/index.md b/static/ru/rpcs/account/view_access_key_list/index.md index 824a410..48df69d 100644 --- a/static/ru/rpcs/account/view_access_key_list/index.md +++ b/static/ru/rpcs/account/view_access_key_list/index.md @@ -236,3 +236,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/account/view_account.md b/static/ru/rpcs/account/view_account.md index 5219e10..71daf77 100644 --- a/static/ru/rpcs/account/view_account.md +++ b/static/ru/rpcs/account/view_account.md @@ -288,3 +288,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/account/view_account/index.md b/static/ru/rpcs/account/view_account/index.md index 5219e10..71daf77 100644 --- a/static/ru/rpcs/account/view_account/index.md +++ b/static/ru/rpcs/account/view_account/index.md @@ -288,3 +288,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/block/block_by_height.md b/static/ru/rpcs/block/block_by_height.md index 851677e..288d440 100644 --- a/static/ru/rpcs/block/block_by_height.md +++ b/static/ru/rpcs/block/block_by_height.md @@ -190,3 +190,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/block/block_by_height/index.md b/static/ru/rpcs/block/block_by_height/index.md index 851677e..288d440 100644 --- a/static/ru/rpcs/block/block_by_height/index.md +++ b/static/ru/rpcs/block/block_by_height/index.md @@ -190,3 +190,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/block/block_by_id.md b/static/ru/rpcs/block/block_by_id.md index 752e98b..9b84d3b 100644 --- a/static/ru/rpcs/block/block_by_id.md +++ b/static/ru/rpcs/block/block_by_id.md @@ -190,3 +190,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/block/block_by_id/index.md b/static/ru/rpcs/block/block_by_id/index.md index 752e98b..9b84d3b 100644 --- a/static/ru/rpcs/block/block_by_id/index.md +++ b/static/ru/rpcs/block/block_by_id/index.md @@ -190,3 +190,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/block/block_effects.md b/static/ru/rpcs/block/block_effects.md index b42d69e..2c64769 100644 --- a/static/ru/rpcs/block/block_effects.md +++ b/static/ru/rpcs/block/block_effects.md @@ -224,3 +224,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/block/block_effects/index.md b/static/ru/rpcs/block/block_effects/index.md index b42d69e..2c64769 100644 --- a/static/ru/rpcs/block/block_effects/index.md +++ b/static/ru/rpcs/block/block_effects/index.md @@ -224,3 +224,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/call.md b/static/ru/rpcs/contract/call.md index f350ca1..efbc072 100644 --- a/static/ru/rpcs/contract/call.md +++ b/static/ru/rpcs/contract/call.md @@ -271,3 +271,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/call/index.md b/static/ru/rpcs/contract/call/index.md index f350ca1..efbc072 100644 --- a/static/ru/rpcs/contract/call/index.md +++ b/static/ru/rpcs/contract/call/index.md @@ -271,3 +271,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_code.md b/static/ru/rpcs/contract/view_code.md index 705c4ad..20a52a8 100644 --- a/static/ru/rpcs/contract/view_code.md +++ b/static/ru/rpcs/contract/view_code.md @@ -241,3 +241,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_code/index.md b/static/ru/rpcs/contract/view_code/index.md index 705c4ad..20a52a8 100644 --- a/static/ru/rpcs/contract/view_code/index.md +++ b/static/ru/rpcs/contract/view_code/index.md @@ -241,3 +241,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_global_contract_code.md b/static/ru/rpcs/contract/view_global_contract_code.md index 7b0835b..03ff28b 100644 --- a/static/ru/rpcs/contract/view_global_contract_code.md +++ b/static/ru/rpcs/contract/view_global_contract_code.md @@ -241,3 +241,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_global_contract_code/index.md b/static/ru/rpcs/contract/view_global_contract_code/index.md index 7b0835b..03ff28b 100644 --- a/static/ru/rpcs/contract/view_global_contract_code/index.md +++ b/static/ru/rpcs/contract/view_global_contract_code/index.md @@ -241,3 +241,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_global_contract_code_by_account_id.md b/static/ru/rpcs/contract/view_global_contract_code_by_account_id.md index 79c99c8..433bd3a 100644 --- a/static/ru/rpcs/contract/view_global_contract_code_by_account_id.md +++ b/static/ru/rpcs/contract/view_global_contract_code_by_account_id.md @@ -241,3 +241,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_global_contract_code_by_account_id/index.md b/static/ru/rpcs/contract/view_global_contract_code_by_account_id/index.md index 79c99c8..433bd3a 100644 --- a/static/ru/rpcs/contract/view_global_contract_code_by_account_id/index.md +++ b/static/ru/rpcs/contract/view_global_contract_code_by_account_id/index.md @@ -241,3 +241,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_state.md b/static/ru/rpcs/contract/view_state.md index e816aec..fe5fb6b 100644 --- a/static/ru/rpcs/contract/view_state.md +++ b/static/ru/rpcs/contract/view_state.md @@ -267,3 +267,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_state/index.md b/static/ru/rpcs/contract/view_state/index.md index e816aec..fe5fb6b 100644 --- a/static/ru/rpcs/contract/view_state/index.md +++ b/static/ru/rpcs/contract/view_state/index.md @@ -267,3 +267,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level.md b/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level.md index 7439c23..5927ae3 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level.md @@ -225,3 +225,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level/index.md b/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level/index.md index 7439c23..5927ae3 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level/index.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level/index.md @@ -225,3 +225,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof.md b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof.md index b659e04..e9207c6 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof.md @@ -256,3 +256,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof/index.md b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof/index.md index b659e04..e9207c6 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof/index.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof/index.md @@ -256,3 +256,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof.md b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof.md index a15dc8c..bce7bcc 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof.md @@ -358,3 +358,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof/index.md b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof/index.md index a15dc8c..bce7bcc 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof/index.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof/index.md @@ -358,3 +358,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config.md b/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config.md index eafdcd0..1e6b574 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config.md @@ -542,3 +542,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config/index.md b/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config/index.md index eafdcd0..1e6b574 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config/index.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config/index.md @@ -542,3 +542,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info.md b/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info.md index 0078b86..ce0faa0 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info.md @@ -211,3 +211,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info/index.md b/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info/index.md index 0078b86..ce0faa0 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info/index.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info/index.md @@ -211,3 +211,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/changes.md b/static/ru/rpcs/protocol/changes.md index 1f84715..f7a5ac2 100644 --- a/static/ru/rpcs/protocol/changes.md +++ b/static/ru/rpcs/protocol/changes.md @@ -221,3 +221,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/changes/index.md b/static/ru/rpcs/protocol/changes/index.md index 1f84715..f7a5ac2 100644 --- a/static/ru/rpcs/protocol/changes/index.md +++ b/static/ru/rpcs/protocol/changes/index.md @@ -221,3 +221,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/chunk_by_block_shard.md b/static/ru/rpcs/protocol/chunk_by_block_shard.md index bfbab60..36a28c2 100644 --- a/static/ru/rpcs/protocol/chunk_by_block_shard.md +++ b/static/ru/rpcs/protocol/chunk_by_block_shard.md @@ -441,3 +441,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/chunk_by_block_shard/index.md b/static/ru/rpcs/protocol/chunk_by_block_shard/index.md index bfbab60..36a28c2 100644 --- a/static/ru/rpcs/protocol/chunk_by_block_shard/index.md +++ b/static/ru/rpcs/protocol/chunk_by_block_shard/index.md @@ -441,3 +441,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/chunk_by_hash.md b/static/ru/rpcs/protocol/chunk_by_hash.md index ae2ff4b..33fb043 100644 --- a/static/ru/rpcs/protocol/chunk_by_hash.md +++ b/static/ru/rpcs/protocol/chunk_by_hash.md @@ -420,3 +420,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/chunk_by_hash/index.md b/static/ru/rpcs/protocol/chunk_by_hash/index.md index ae2ff4b..33fb043 100644 --- a/static/ru/rpcs/protocol/chunk_by_hash/index.md +++ b/static/ru/rpcs/protocol/chunk_by_hash/index.md @@ -420,3 +420,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/client_config.md b/static/ru/rpcs/protocol/client_config.md index 88030bc..f4001f0 100644 --- a/static/ru/rpcs/protocol/client_config.md +++ b/static/ru/rpcs/protocol/client_config.md @@ -1027,3 +1027,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/client_config/index.md b/static/ru/rpcs/protocol/client_config/index.md index 88030bc..f4001f0 100644 --- a/static/ru/rpcs/protocol/client_config/index.md +++ b/static/ru/rpcs/protocol/client_config/index.md @@ -1027,3 +1027,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/gas_price.md b/static/ru/rpcs/protocol/gas_price.md index 0f7d36d..d34353a 100644 --- a/static/ru/rpcs/protocol/gas_price.md +++ b/static/ru/rpcs/protocol/gas_price.md @@ -194,3 +194,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/gas_price/index.md b/static/ru/rpcs/protocol/gas_price/index.md index 0f7d36d..d34353a 100644 --- a/static/ru/rpcs/protocol/gas_price/index.md +++ b/static/ru/rpcs/protocol/gas_price/index.md @@ -194,3 +194,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/gas_price_by_block.md b/static/ru/rpcs/protocol/gas_price_by_block.md index 0b8c8be..2de8aad 100644 --- a/static/ru/rpcs/protocol/gas_price_by_block.md +++ b/static/ru/rpcs/protocol/gas_price_by_block.md @@ -212,3 +212,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/gas_price_by_block/index.md b/static/ru/rpcs/protocol/gas_price_by_block/index.md index 0b8c8be..2de8aad 100644 --- a/static/ru/rpcs/protocol/gas_price_by_block/index.md +++ b/static/ru/rpcs/protocol/gas_price_by_block/index.md @@ -212,3 +212,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/genesis_config.md b/static/ru/rpcs/protocol/genesis_config.md index 3808ffe..979a8f7 100644 --- a/static/ru/rpcs/protocol/genesis_config.md +++ b/static/ru/rpcs/protocol/genesis_config.md @@ -595,3 +595,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/genesis_config/index.md b/static/ru/rpcs/protocol/genesis_config/index.md index 3808ffe..979a8f7 100644 --- a/static/ru/rpcs/protocol/genesis_config/index.md +++ b/static/ru/rpcs/protocol/genesis_config/index.md @@ -595,3 +595,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/health.md b/static/ru/rpcs/protocol/health.md index 073c6cb..8dd242a 100644 --- a/static/ru/rpcs/protocol/health.md +++ b/static/ru/rpcs/protocol/health.md @@ -174,3 +174,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/health/index.md b/static/ru/rpcs/protocol/health/index.md index 073c6cb..8dd242a 100644 --- a/static/ru/rpcs/protocol/health/index.md +++ b/static/ru/rpcs/protocol/health/index.md @@ -174,3 +174,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/latest_block.md b/static/ru/rpcs/protocol/latest_block.md index cbc9225..20437fd 100644 --- a/static/ru/rpcs/protocol/latest_block.md +++ b/static/ru/rpcs/protocol/latest_block.md @@ -195,3 +195,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/latest_block/index.md b/static/ru/rpcs/protocol/latest_block/index.md index cbc9225..20437fd 100644 --- a/static/ru/rpcs/protocol/latest_block/index.md +++ b/static/ru/rpcs/protocol/latest_block/index.md @@ -195,3 +195,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/light_client_proof.md b/static/ru/rpcs/protocol/light_client_proof.md index b3335b1..7cacb43 100644 --- a/static/ru/rpcs/protocol/light_client_proof.md +++ b/static/ru/rpcs/protocol/light_client_proof.md @@ -358,3 +358,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/light_client_proof/index.md b/static/ru/rpcs/protocol/light_client_proof/index.md index b3335b1..7cacb43 100644 --- a/static/ru/rpcs/protocol/light_client_proof/index.md +++ b/static/ru/rpcs/protocol/light_client_proof/index.md @@ -358,3 +358,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/maintenance_windows.md b/static/ru/rpcs/protocol/maintenance_windows.md index 744ef7f..04e2d57 100644 --- a/static/ru/rpcs/protocol/maintenance_windows.md +++ b/static/ru/rpcs/protocol/maintenance_windows.md @@ -215,3 +215,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/maintenance_windows/index.md b/static/ru/rpcs/protocol/maintenance_windows/index.md index 744ef7f..04e2d57 100644 --- a/static/ru/rpcs/protocol/maintenance_windows/index.md +++ b/static/ru/rpcs/protocol/maintenance_windows/index.md @@ -215,3 +215,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/metrics.md b/static/ru/rpcs/protocol/metrics.md index 98ea8e9..07d5820 100644 --- a/static/ru/rpcs/protocol/metrics.md +++ b/static/ru/rpcs/protocol/metrics.md @@ -42,3 +42,10 @@ "example": "# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.\n# TYPE process_cpu_seconds_total counter\nprocess_cpu_seconds_total 12.34" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/metrics/index.md b/static/ru/rpcs/protocol/metrics/index.md index 98ea8e9..07d5820 100644 --- a/static/ru/rpcs/protocol/metrics/index.md +++ b/static/ru/rpcs/protocol/metrics/index.md @@ -42,3 +42,10 @@ "example": "# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.\n# TYPE process_cpu_seconds_total counter\nprocess_cpu_seconds_total 12.34" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/network_info.md b/static/ru/rpcs/protocol/network_info.md index a6562f1..eeae82b 100644 --- a/static/ru/rpcs/protocol/network_info.md +++ b/static/ru/rpcs/protocol/network_info.md @@ -236,3 +236,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/network_info/index.md b/static/ru/rpcs/protocol/network_info/index.md index a6562f1..eeae82b 100644 --- a/static/ru/rpcs/protocol/network_info/index.md +++ b/static/ru/rpcs/protocol/network_info/index.md @@ -236,3 +236,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/next_light_client_block.md b/static/ru/rpcs/protocol/next_light_client_block.md index 22f2f14..16757de 100644 --- a/static/ru/rpcs/protocol/next_light_client_block.md +++ b/static/ru/rpcs/protocol/next_light_client_block.md @@ -333,3 +333,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/next_light_client_block/index.md b/static/ru/rpcs/protocol/next_light_client_block/index.md index 22f2f14..16757de 100644 --- a/static/ru/rpcs/protocol/next_light_client_block/index.md +++ b/static/ru/rpcs/protocol/next_light_client_block/index.md @@ -333,3 +333,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/status.md b/static/ru/rpcs/protocol/status.md index e660141..705379e 100644 --- a/static/ru/rpcs/protocol/status.md +++ b/static/ru/rpcs/protocol/status.md @@ -493,3 +493,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/status/index.md b/static/ru/rpcs/protocol/status/index.md index e660141..705379e 100644 --- a/static/ru/rpcs/protocol/status/index.md +++ b/static/ru/rpcs/protocol/status/index.md @@ -493,3 +493,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/EXPERIMENTAL_receipt.md b/static/ru/rpcs/transaction/EXPERIMENTAL_receipt.md index 1edc574..9068d0b 100644 --- a/static/ru/rpcs/transaction/EXPERIMENTAL_receipt.md +++ b/static/ru/rpcs/transaction/EXPERIMENTAL_receipt.md @@ -240,3 +240,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/EXPERIMENTAL_receipt/index.md b/static/ru/rpcs/transaction/EXPERIMENTAL_receipt/index.md index 1edc574..9068d0b 100644 --- a/static/ru/rpcs/transaction/EXPERIMENTAL_receipt/index.md +++ b/static/ru/rpcs/transaction/EXPERIMENTAL_receipt/index.md @@ -240,3 +240,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status.md b/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status.md index 3f5e77a..7ecb843 100644 --- a/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status.md +++ b/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status.md @@ -294,3 +294,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status/index.md b/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status/index.md index 3f5e77a..7ecb843 100644 --- a/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status/index.md +++ b/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status/index.md @@ -294,3 +294,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/broadcast_tx_async.md b/static/ru/rpcs/transaction/broadcast_tx_async.md index 74dfbb5..9754fe9 100644 --- a/static/ru/rpcs/transaction/broadcast_tx_async.md +++ b/static/ru/rpcs/transaction/broadcast_tx_async.md @@ -209,3 +209,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/broadcast_tx_async/index.md b/static/ru/rpcs/transaction/broadcast_tx_async/index.md index 74dfbb5..9754fe9 100644 --- a/static/ru/rpcs/transaction/broadcast_tx_async/index.md +++ b/static/ru/rpcs/transaction/broadcast_tx_async/index.md @@ -209,3 +209,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/broadcast_tx_commit.md b/static/ru/rpcs/transaction/broadcast_tx_commit.md index 9059380..7678eb8 100644 --- a/static/ru/rpcs/transaction/broadcast_tx_commit.md +++ b/static/ru/rpcs/transaction/broadcast_tx_commit.md @@ -275,3 +275,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/broadcast_tx_commit/index.md b/static/ru/rpcs/transaction/broadcast_tx_commit/index.md index 9059380..7678eb8 100644 --- a/static/ru/rpcs/transaction/broadcast_tx_commit/index.md +++ b/static/ru/rpcs/transaction/broadcast_tx_commit/index.md @@ -275,3 +275,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/send_tx.md b/static/ru/rpcs/transaction/send_tx.md index eff9f55..2443e8e 100644 --- a/static/ru/rpcs/transaction/send_tx.md +++ b/static/ru/rpcs/transaction/send_tx.md @@ -277,3 +277,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/send_tx/index.md b/static/ru/rpcs/transaction/send_tx/index.md index eff9f55..2443e8e 100644 --- a/static/ru/rpcs/transaction/send_tx/index.md +++ b/static/ru/rpcs/transaction/send_tx/index.md @@ -277,3 +277,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/tx_status.md b/static/ru/rpcs/transaction/tx_status.md index db41a58..475f730 100644 --- a/static/ru/rpcs/transaction/tx_status.md +++ b/static/ru/rpcs/transaction/tx_status.md @@ -292,3 +292,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/tx_status/index.md b/static/ru/rpcs/transaction/tx_status/index.md index db41a58..475f730 100644 --- a/static/ru/rpcs/transaction/tx_status/index.md +++ b/static/ru/rpcs/transaction/tx_status/index.md @@ -292,3 +292,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered.md b/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered.md index 2e4ea61..d089c7c 100644 --- a/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered.md +++ b/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered.md @@ -203,3 +203,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered/index.md b/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered/index.md index 2e4ea61..d089c7c 100644 --- a/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered/index.md +++ b/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered/index.md @@ -203,3 +203,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/validators/validators_by_epoch.md b/static/ru/rpcs/validators/validators_by_epoch.md index a603445..886c8c7 100644 --- a/static/ru/rpcs/validators/validators_by_epoch.md +++ b/static/ru/rpcs/validators/validators_by_epoch.md @@ -291,3 +291,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/validators/validators_by_epoch/index.md b/static/ru/rpcs/validators/validators_by_epoch/index.md index a603445..886c8c7 100644 --- a/static/ru/rpcs/validators/validators_by_epoch/index.md +++ b/static/ru/rpcs/validators/validators_by_epoch/index.md @@ -291,3 +291,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/validators/validators_current.md b/static/ru/rpcs/validators/validators_current.md index 265ac1e..6742344 100644 --- a/static/ru/rpcs/validators/validators_current.md +++ b/static/ru/rpcs/validators/validators_current.md @@ -282,3 +282,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/validators/validators_current/index.md b/static/ru/rpcs/validators/validators_current/index.md index 265ac1e..6742344 100644 --- a/static/ru/rpcs/validators/validators_current/index.md +++ b/static/ru/rpcs/validators/validators_current/index.md @@ -282,3 +282,10 @@ "refName": "JsonRpcResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots.md b/static/ru/snapshots.md index 895f94a..91e722e 100644 --- a/static/ru/snapshots.md +++ b/static/ru/snapshots.md @@ -55,3 +55,10 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/examples.md b/static/ru/snapshots/examples.md index 15df6af..e651706 100644 --- a/static/ru/snapshots/examples.md +++ b/static/ru/snapshots/examples.md @@ -56,3 +56,10 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) - [RPC Reference](https://docs.fastnear.com/ru/rpc) - [NEAR Data API](https://docs.fastnear.com/ru/neardata) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/examples/index.md b/static/ru/snapshots/examples/index.md index 15df6af..e651706 100644 --- a/static/ru/snapshots/examples/index.md +++ b/static/ru/snapshots/examples/index.md @@ -56,3 +56,10 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) - [RPC Reference](https://docs.fastnear.com/ru/rpc) - [NEAR Data API](https://docs.fastnear.com/ru/neardata) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/index.md b/static/ru/snapshots/index.md index 895f94a..91e722e 100644 --- a/static/ru/snapshots/index.md +++ b/static/ru/snapshots/index.md @@ -55,3 +55,10 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/mainnet.md b/static/ru/snapshots/mainnet.md index 4d95e90..24fe8c5 100644 --- a/static/ru/snapshots/mainnet.md +++ b/static/ru/snapshots/mainnet.md @@ -128,3 +128,10 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ ```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 ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/mainnet/index.md b/static/ru/snapshots/mainnet/index.md index 4d95e90..24fe8c5 100644 --- a/static/ru/snapshots/mainnet/index.md +++ b/static/ru/snapshots/mainnet/index.md @@ -128,3 +128,10 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ ```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 ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/testnet.md b/static/ru/snapshots/testnet.md index a0ce443..068dbe6 100644 --- a/static/ru/snapshots/testnet.md +++ b/static/ru/snapshots/testnet.md @@ -77,3 +77,10 @@ echo "Latest snapshot block: $LATEST" ```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 ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/testnet/index.md b/static/ru/snapshots/testnet/index.md index a0ce443..068dbe6 100644 --- a/static/ru/snapshots/testnet/index.md +++ b/static/ru/snapshots/testnet/index.md @@ -77,3 +77,10 @@ echo "Latest snapshot block: $LATEST" ```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 ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/transfers.md b/static/ru/transfers.md index 4d23ab9..4c2cbf1 100644 --- a/static/ru/transfers.md +++ b/static/ru/transfers.md @@ -71,3 +71,10 @@ https://transfers.main.fastnear.com ### `resume_token` перестал работать Считайте токен непрозрачным и переиспользуйте его только с тем же эндпоинтом и фильтрами, которые его вернули. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/transfers/examples.md b/static/ru/transfers/examples.md index 3195bca..a819880 100644 --- a/static/ru/transfers/examples.md +++ b/static/ru/transfers/examples.md @@ -56,3 +56,10 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - [FastNear API](https://docs.fastnear.com/ru/api) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/transfers/examples/index.md b/static/ru/transfers/examples/index.md index 3195bca..a819880 100644 --- a/static/ru/transfers/examples/index.md +++ b/static/ru/transfers/examples/index.md @@ -56,3 +56,10 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - [FastNear API](https://docs.fastnear.com/ru/api) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/transfers/index.md b/static/ru/transfers/index.md index 4d23ab9..4c2cbf1 100644 --- a/static/ru/transfers/index.md +++ b/static/ru/transfers/index.md @@ -71,3 +71,10 @@ https://transfers.main.fastnear.com ### `resume_token` перестал работать Считайте токен непрозрачным и переиспользуйте его только с тем же эндпоинтом и фильтрами, которые его вернули. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/transfers/query.md b/static/ru/transfers/query.md index 7678bfe..6b93007 100644 --- a/static/ru/transfers/query.md +++ b/static/ru/transfers/query.md @@ -383,3 +383,10 @@ "refName": "TransfersResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/transfers/query/index.md b/static/ru/transfers/query/index.md index 7678bfe..6b93007 100644 --- a/static/ru/transfers/query/index.md +++ b/static/ru/transfers/query/index.md @@ -383,3 +383,10 @@ "refName": "TransfersResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx.md b/static/ru/tx.md index f1aa96e..985bb94 100644 --- a/static/ru/tx.md +++ b/static/ru/tx.md @@ -57,3 +57,10 @@ https://tx.test.fastnear.com ### Мне нужен только один канонический результат статуса транзакции из RPC Используйте сырой RPC вместо индексированного семейства истории. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/account.md b/static/ru/tx/account.md index ab634bb..4d00a72 100644 --- a/static/ru/tx/account.md +++ b/static/ru/tx/account.md @@ -414,3 +414,10 @@ "refName": "AccountResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/account/index.md b/static/ru/tx/account/index.md index ab634bb..4d00a72 100644 --- a/static/ru/tx/account/index.md +++ b/static/ru/tx/account/index.md @@ -414,3 +414,10 @@ "refName": "AccountResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/block.md b/static/ru/tx/block.md index f603d51..9d86fc3 100644 --- a/static/ru/tx/block.md +++ b/static/ru/tx/block.md @@ -589,3 +589,10 @@ "refName": "BlockResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/block/index.md b/static/ru/tx/block/index.md index f603d51..9d86fc3 100644 --- a/static/ru/tx/block/index.md +++ b/static/ru/tx/block/index.md @@ -589,3 +589,10 @@ "refName": "BlockResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/blocks.md b/static/ru/tx/blocks.md index 5764882..c53a163 100644 --- a/static/ru/tx/blocks.md +++ b/static/ru/tx/blocks.md @@ -268,3 +268,10 @@ "refName": "BlocksResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/blocks/index.md b/static/ru/tx/blocks/index.md index 5764882..c53a163 100644 --- a/static/ru/tx/blocks/index.md +++ b/static/ru/tx/blocks/index.md @@ -268,3 +268,10 @@ "refName": "BlocksResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index fc760a0..2f1e464 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -132,48 +132,9 @@ curl -s "$RPC_URL" \ `UNKNOWN_ACCOUNT` — это и есть доказательство. Если бы `CreateAccount` закрепился, `view_account` вернул бы результат; раз нет — предыдущие `Transfer` и `AddKey` из того же batched-receipt тоже не закрепились. -### Почему этот вызов контракта выглядел успешным, но потом receipt упал? +### Когда транзакция выглядит успешной — что на самом деле произошло? -Одна транзакция может закончиться тем, что внешний handoff рапортует `SuccessReceiptId`, а дочерний receipt при этом тихо падает — это и есть async-модель NEAR, и `/v0/transactions` выдаёт весь timeline за один запрос. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - tx_handoff: .transactions[0].execution_outcome.outcome.status, - outer_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, - descendant_failures: [ - .transactions[0].receipts[] - | select(.execution_outcome.outcome.status.Failure != null) - | { - receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - block_height: .execution_outcome.block_height, - failure: .execution_outcome.outcome.status.Failure - } - ], - receipt_timeline: [ - .transactions[0].receipts[] - | { - receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - status_class: (.execution_outcome.outcome.status | keys[0]) - } - ] - }' -``` - -Для зафиксированной транзакции mainnet `tx_handoff` — `SuccessReceiptId`: транзакция чисто запустила свой первый receipt. Если смотреть только сюда, можно назвать это победой. `descendant_failures` рассказывают вторую историю: `ft_on_transfer` на `v2.ref-finance.near` упал с `E51: contract paused` — DEX был на паузе во время этого свопа и не мог принять wrapped NEAR. А `receipt_timeline` показывает, как история разрешилась: callback `ft_resolve_transfer` на `wrap.near` всё равно отработал и вывел лог `Refund`, вернув wrapped NEAR отправителю. - -Успех receipt не транзитивен. Протокол может чисто отдать handoff и при этом увидеть, как отцеплённая работа провалится позже. Если ваше приложение «выглядело успешным», но деньги всё равно вернулись, пройдите этот же timeline — разделение видно на индексированном ответе без отдельного RPC status-запроса. Чтобы отдельно проверить, что ваш callback отработал, см. [Отработал ли мой callback?](#отработал-ли-мой-callback). - -### Отработал ли мой callback? - -Кросс-контрактные вызовы NEAR возвращаются через callback-receipt на исходном контракте. Отработал ли этот callback — это одна строка с `any(...)` против индексированного списка receipts; а полная история refund выпадает из того же ответа. +Внешний `execution_outcome.outcome.status` рапортует `SuccessReceiptId`, как только сработал handoff первого receipt, — и ничего не говорит о том, успешны ли дочерние receipts и отработал ли callback на исходном контракте. Один pipeline над `/v0/transactions` отвечает сразу на все три вопроса. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -185,30 +146,38 @@ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ - top_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, - callback_ran: any( - .transactions[0].receipts[]; - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback - ), - receipt_chain: [ + outer: { + method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + tx_handoff: (.transactions[0].execution_outcome.outcome.status | keys[0]) + }, + callback: { + expected_on: $origin, + method: $callback, + ran: any( + .transactions[0].receipts[]; + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback + ) + }, + descendant_failures: [ .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) | { receiver_id: .receipt.receiver_id, method: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - block: .execution_outcome.block_height, - status: (.execution_outcome.outcome.status | keys[0]), - logs: .execution_outcome.outcome.logs + cause: .execution_outcome.outcome.status.Failure } ] }' ``` -Для зафиксированной транзакции `ft_transfer_call` на `wrap.near` передаёт управление в `ft_on_transfer` на `v2.ref-finance.near`, который **падает**. Callback `ft_resolve_transfer` всё равно выполняется на `wrap.near` и логирует `Refund 7278020378457059679767103 from v2.ref-finance.near to …` обратно отправителю — поэтому `callback_ran: true`, несмотря на сбой дочернего receipt. Сбой ниже по цепочке не мешает исходному контракту увидеть свой callback; так async-обработка ошибок NEAR остаётся восстанавливаемой. Строки с `method: "system"` — это рантайм-возвраты газа, а не логика контракта. Чтобы привязать один из этих логов к породившему его receipt, см. [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). +Для зафиксированной транзакции `outer.method` — `ft_transfer_call`, а `outer.tx_handoff` — `SuccessReceiptId`: транзакция чисто запустила свой первый receipt, и если смотреть только сюда, можно назвать это победой. `descendant_failures` рассказывают вторую историю: `ft_on_transfer` на `v2.ref-finance.near` упал с `E51: contract paused` — DEX был на паузе во время этого свопа и не мог принять wrapped NEAR. `callback.ran: true` — третью: callback `ft_resolve_transfer` на `wrap.near` всё равно отработал. Сбой ниже по цепочке никогда не мешает callback исходного контракта — именно так NEP-141 возвращает отправителю средства, когда получатель их отклонил. + +Успех receipt не транзитивен. Протокол может чисто отдать handoff и при этом увидеть, как отцеплённая работа провалится позже; callback исходного контракта отработает в любом случае. Прочитайте эти три поля вместе — и async-история становится читаемой без ручного обхода цепочки receipts. Чтобы вытянуть сам лог `Refund`, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). ### Сопоставить запрос OutLayer с его TEE-разрешением -[OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь вызывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, и позже `worker.outlayer.near` вызывает `submit_execution_output_and_resolve` с результатом. Один batch на `/v0/transactions` с обоими хешами возвращает всё сопоставление — метод, корреляционные идентификаторы и TEE-отпечаток — без отдельного worker-запроса. +[OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь подписывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, затем `worker.outlayer.near` присылает результат через `submit_execution_output_and_resolve`. Обе половины несут один и тот же `request_id` — передайте оба tx-хеша в один запрос `/v0/transactions` и извлеките это поле с каждой стороны, чтобы подтвердить пару. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -221,30 +190,25 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | jq '[ .transactions[] | { + role: (if .transaction.actions[0].FunctionCall.method_name == "request_execution" + then "request" else "worker" end), hash: .transaction.hash, signer: .transaction.signer_id, method: .transaction.actions[0].FunctionCall.method_name, block: .execution_outcome.block_height, - evidence: ( + request_id: ( if .transaction.actions[0].FunctionCall.method_name == "request_execution" - then (.receipts[0].execution_outcome.outcome.logs - | map(select(startswith("EVENT_JSON"))) | .[0] - | sub("EVENT_JSON:"; "") | fromjson | .data[0] as $e - | ($e.request_data | fromjson) as $r - | {request_id: $r.request_id, sender_id: $r.sender_id, project_id: $r.project_id, - data_id: $e.data_id, code_hash: $r.code_source.WasmUrl.hash, - input_preview: ($r.input_data | .[0:80] + "…")}) + then (.receipts[0].execution_outcome.outcome.logs[] | select(startswith("EVENT_JSON")) + | sub("EVENT_JSON:"; "") | fromjson | .data[0].request_data | fromjson | .request_id) else (.receipts[0].receipt.receipt.Action.actions[0].FunctionCall.args - | @base64d | fromjson - | {request_id, success: .output.Json.success, encrypted_bytes: (.output.Json.encrypted_data | length), - instructions: .resources_used.instructions, time_ms: .resources_used.time_ms}) + | @base64d | fromjson | .request_id) end ) } ]' ``` -Обе строки несут `request_id: 1868`. Half-запрос, подписанный `retrorn.near` в блоке `194832281`, вызывает проект `zavodil.near/near-email` с действием `delete_email` и 32-байтным `data_id` — это идентификатор payload yield/resume NEAR, который контракт использует внутри, чтобы приостановить on-chain-обещание, пока worker выполняется. Half-worker попадает через 11 блоков и рапортует `success: true`, `instructions: 53075053`, `time_ms: 2401` и 21 568-байтный зашифрованный результат; эти цифры `resources_used` — тот самый TEE-отпечаток, который можно сверить с лимитами из запроса. `/v0/transactions` отдаёт исторические пары бессрочно, поэтому archival RPC для самой трассировки не нужен — тянитесь к [archival RPC](https://archival-rpc.mainnet.fastnear.com) только когда нужно сверять состояние контракта на высоте блока запроса. +Обе строки несут `request_id: 1868`, подтверждая пару. Половина-запрос, подписанная `retrorn.near` в блоке `194832281`, лежит в логе `EVENT_JSON:` её receipt (это yield/resume-паттерн NEAR — on-chain-обещание приостанавливается, пока TDX-worker выполняется). Половина-worker приходит через 11 блоков с `submit_execution_output_and_resolve`, подписанной `worker.outlayer.near`, и её `request_id` достаётся прямо из base64-обёрнутых `FunctionCall.args`. Те же два payload несут и более богатый отпечаток — `sender_id`, `project_id`, `code_hash`, `resources_used.instructions`, `resources_used.time_ms`, размер зашифрованного результата в байтах — если нужно проверить, что именно исполнилось; этот минимальный pipeline лишь подтверждает, что половины принадлежат друг другу. `/v0/transactions` отдаёт исторические пары бессрочно, поэтому archival RPC для самой трассировки не нужен даже через недели. ## Частые ошибки @@ -262,3 +226,10 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/examples/berry-club.md b/static/ru/tx/examples/berry-club.md index f20231b..6511907 100644 --- a/static/ru/tx/examples/berry-club.md +++ b/static/ru/tx/examples/berry-club.md @@ -105,3 +105,10 @@ for (const drawTx of drawTransactionsOldestFirst) { - [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) - [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) - [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/examples/berry-club/index.md b/static/ru/tx/examples/berry-club/index.md index f20231b..6511907 100644 --- a/static/ru/tx/examples/berry-club/index.md +++ b/static/ru/tx/examples/berry-club/index.md @@ -105,3 +105,10 @@ for (const drawTx of drawTransactionsOldestFirst) { - [RPC: call_function](https://docs.fastnear.com/ru/rpc/contract/call-function) - [Transactions API: история аккаунта](https://docs.fastnear.com/ru/tx/account) - [Transactions API: транзакции по хешу](https://docs.fastnear.com/ru/tx/transactions) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index fc760a0..2f1e464 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -132,48 +132,9 @@ curl -s "$RPC_URL" \ `UNKNOWN_ACCOUNT` — это и есть доказательство. Если бы `CreateAccount` закрепился, `view_account` вернул бы результат; раз нет — предыдущие `Transfer` и `AddKey` из того же batched-receipt тоже не закрепились. -### Почему этот вызов контракта выглядел успешным, но потом receipt упал? +### Когда транзакция выглядит успешной — что на самом деле произошло? -Одна транзакция может закончиться тем, что внешний handoff рапортует `SuccessReceiptId`, а дочерний receipt при этом тихо падает — это и есть async-модель NEAR, и `/v0/transactions` выдаёт весь timeline за один запрос. - -```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL - -curl -s "$TX_BASE_URL/v0/transactions" \ - -H 'content-type: application/json' \ - --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ - | jq '{ - tx_handoff: .transactions[0].execution_outcome.outcome.status, - outer_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, - descendant_failures: [ - .transactions[0].receipts[] - | select(.execution_outcome.outcome.status.Failure != null) - | { - receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - block_height: .execution_outcome.block_height, - failure: .execution_outcome.outcome.status.Failure - } - ], - receipt_timeline: [ - .transactions[0].receipts[] - | { - receiver_id: .receipt.receiver_id, - method_name: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - status_class: (.execution_outcome.outcome.status | keys[0]) - } - ] - }' -``` - -Для зафиксированной транзакции mainnet `tx_handoff` — `SuccessReceiptId`: транзакция чисто запустила свой первый receipt. Если смотреть только сюда, можно назвать это победой. `descendant_failures` рассказывают вторую историю: `ft_on_transfer` на `v2.ref-finance.near` упал с `E51: contract paused` — DEX был на паузе во время этого свопа и не мог принять wrapped NEAR. А `receipt_timeline` показывает, как история разрешилась: callback `ft_resolve_transfer` на `wrap.near` всё равно отработал и вывел лог `Refund`, вернув wrapped NEAR отправителю. - -Успех receipt не транзитивен. Протокол может чисто отдать handoff и при этом увидеть, как отцеплённая работа провалится позже. Если ваше приложение «выглядело успешным», но деньги всё равно вернулись, пройдите этот же timeline — разделение видно на индексированном ответе без отдельного RPC status-запроса. Чтобы отдельно проверить, что ваш callback отработал, см. [Отработал ли мой callback?](#отработал-ли-мой-callback). - -### Отработал ли мой callback? - -Кросс-контрактные вызовы NEAR возвращаются через callback-receipt на исходном контракте. Отработал ли этот callback — это одна строка с `any(...)` против индексированного списка receipts; а полная история refund выпадает из того же ответа. +Внешний `execution_outcome.outcome.status` рапортует `SuccessReceiptId`, как только сработал handoff первого receipt, — и ничего не говорит о том, успешны ли дочерние receipts и отработал ли callback на исходном контракте. Один pipeline над `/v0/transactions` отвечает сразу на все три вопроса. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -185,30 +146,38 @@ curl -s "$TX_BASE_URL/v0/transactions" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ - top_method: .transactions[0].transaction.actions[0].FunctionCall.method_name, - callback_ran: any( - .transactions[0].receipts[]; - .receipt.receiver_id == $origin - and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback - ), - receipt_chain: [ + outer: { + method: .transactions[0].transaction.actions[0].FunctionCall.method_name, + tx_handoff: (.transactions[0].execution_outcome.outcome.status | keys[0]) + }, + callback: { + expected_on: $origin, + method: $callback, + ran: any( + .transactions[0].receipts[]; + .receipt.receiver_id == $origin + and (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "") == $callback + ) + }, + descendant_failures: [ .transactions[0].receipts[] + | select(.execution_outcome.outcome.status.Failure != null) | { receiver_id: .receipt.receiver_id, method: (.receipt.receipt.Action.actions[0].FunctionCall.method_name // "system"), - block: .execution_outcome.block_height, - status: (.execution_outcome.outcome.status | keys[0]), - logs: .execution_outcome.outcome.logs + cause: .execution_outcome.outcome.status.Failure } ] }' ``` -Для зафиксированной транзакции `ft_transfer_call` на `wrap.near` передаёт управление в `ft_on_transfer` на `v2.ref-finance.near`, который **падает**. Callback `ft_resolve_transfer` всё равно выполняется на `wrap.near` и логирует `Refund 7278020378457059679767103 from v2.ref-finance.near to …` обратно отправителю — поэтому `callback_ran: true`, несмотря на сбой дочернего receipt. Сбой ниже по цепочке не мешает исходному контракту увидеть свой callback; так async-обработка ошибок NEAR остаётся восстанавливаемой. Строки с `method: "system"` — это рантайм-возвраты газа, а не логика контракта. Чтобы привязать один из этих логов к породившему его receipt, см. [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). +Для зафиксированной транзакции `outer.method` — `ft_transfer_call`, а `outer.tx_handoff` — `SuccessReceiptId`: транзакция чисто запустила свой первый receipt, и если смотреть только сюда, можно назвать это победой. `descendant_failures` рассказывают вторую историю: `ft_on_transfer` на `v2.ref-finance.near` упал с `E51: contract paused` — DEX был на паузе во время этого свопа и не мог принять wrapped NEAR. `callback.ran: true` — третью: callback `ft_resolve_transfer` на `wrap.near` всё равно отработал. Сбой ниже по цепочке никогда не мешает callback исходного контракта — именно так NEP-141 возвращает отправителю средства, когда получатель их отклонил. + +Успех receipt не транзитивен. Протокол может чисто отдать handoff и при этом увидеть, как отцеплённая работа провалится позже; callback исходного контракта отработает в любом случае. Прочитайте эти три поля вместе — и async-история становится читаемой без ручного обхода цепочки receipts. Чтобы вытянуть сам лог `Refund`, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие). ### Сопоставить запрос OutLayer с его TEE-разрешением -[OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь вызывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, и позже `worker.outlayer.near` вызывает `submit_execution_output_and_resolve` с результатом. Один batch на `/v0/transactions` с обоими хешами возвращает всё сопоставление — метод, корреляционные идентификаторы и TEE-отпечаток — без отдельного worker-запроса. +[OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь подписывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, затем `worker.outlayer.near` присылает результат через `submit_execution_output_and_resolve`. Обе половины несут один и тот же `request_id` — передайте оба tx-хеша в один запрос `/v0/transactions` и извлеките это поле с каждой стороны, чтобы подтвердить пару. ```bash TX_BASE_URL=https://tx.main.fastnear.com @@ -221,30 +190,25 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | jq '[ .transactions[] | { + role: (if .transaction.actions[0].FunctionCall.method_name == "request_execution" + then "request" else "worker" end), hash: .transaction.hash, signer: .transaction.signer_id, method: .transaction.actions[0].FunctionCall.method_name, block: .execution_outcome.block_height, - evidence: ( + request_id: ( if .transaction.actions[0].FunctionCall.method_name == "request_execution" - then (.receipts[0].execution_outcome.outcome.logs - | map(select(startswith("EVENT_JSON"))) | .[0] - | sub("EVENT_JSON:"; "") | fromjson | .data[0] as $e - | ($e.request_data | fromjson) as $r - | {request_id: $r.request_id, sender_id: $r.sender_id, project_id: $r.project_id, - data_id: $e.data_id, code_hash: $r.code_source.WasmUrl.hash, - input_preview: ($r.input_data | .[0:80] + "…")}) + then (.receipts[0].execution_outcome.outcome.logs[] | select(startswith("EVENT_JSON")) + | sub("EVENT_JSON:"; "") | fromjson | .data[0].request_data | fromjson | .request_id) else (.receipts[0].receipt.receipt.Action.actions[0].FunctionCall.args - | @base64d | fromjson - | {request_id, success: .output.Json.success, encrypted_bytes: (.output.Json.encrypted_data | length), - instructions: .resources_used.instructions, time_ms: .resources_used.time_ms}) + | @base64d | fromjson | .request_id) end ) } ]' ``` -Обе строки несут `request_id: 1868`. Half-запрос, подписанный `retrorn.near` в блоке `194832281`, вызывает проект `zavodil.near/near-email` с действием `delete_email` и 32-байтным `data_id` — это идентификатор payload yield/resume NEAR, который контракт использует внутри, чтобы приостановить on-chain-обещание, пока worker выполняется. Half-worker попадает через 11 блоков и рапортует `success: true`, `instructions: 53075053`, `time_ms: 2401` и 21 568-байтный зашифрованный результат; эти цифры `resources_used` — тот самый TEE-отпечаток, который можно сверить с лимитами из запроса. `/v0/transactions` отдаёт исторические пары бессрочно, поэтому archival RPC для самой трассировки не нужен — тянитесь к [archival RPC](https://archival-rpc.mainnet.fastnear.com) только когда нужно сверять состояние контракта на высоте блока запроса. +Обе строки несут `request_id: 1868`, подтверждая пару. Половина-запрос, подписанная `retrorn.near` в блоке `194832281`, лежит в логе `EVENT_JSON:` её receipt (это yield/resume-паттерн NEAR — on-chain-обещание приостанавливается, пока TDX-worker выполняется). Половина-worker приходит через 11 блоков с `submit_execution_output_and_resolve`, подписанной `worker.outlayer.near`, и её `request_id` достаётся прямо из base64-обёрнутых `FunctionCall.args`. Те же два payload несут и более богатый отпечаток — `sender_id`, `project_id`, `code_hash`, `resources_used.instructions`, `resources_used.time_ms`, размер зашифрованного результата в байтах — если нужно проверить, что именно исполнилось; этот минимальный pipeline лишь подтверждает, что половины принадлежат друг другу. `/v0/transactions` отдаёт исторические пары бессрочно, поэтому archival RPC для самой трассировки не нужен даже через недели. ## Частые ошибки @@ -262,3 +226,10 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - [Расширенный поиск записи SocialDB](https://docs.fastnear.com/ru/tx/socialdb-proofs) - [Choosing the Right Surface](https://docs.fastnear.com/ru/agents/choosing-surfaces) - [Agent Playbooks](https://docs.fastnear.com/ru/agents/playbooks) +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/index.md b/static/ru/tx/index.md index f1aa96e..985bb94 100644 --- a/static/ru/tx/index.md +++ b/static/ru/tx/index.md @@ -57,3 +57,10 @@ https://tx.test.fastnear.com ### Мне нужен только один канонический результат статуса транзакции из RPC Используйте сырой RPC вместо индексированного семейства истории. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/receipt.md b/static/ru/tx/receipt.md index fe7d5a0..0ff738d 100644 --- a/static/ru/tx/receipt.md +++ b/static/ru/tx/receipt.md @@ -235,3 +235,10 @@ "refName": "ReceiptResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/receipt/index.md b/static/ru/tx/receipt/index.md index fe7d5a0..0ff738d 100644 --- a/static/ru/tx/receipt/index.md +++ b/static/ru/tx/receipt/index.md @@ -235,3 +235,10 @@ "refName": "ReceiptResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/socialdb-proofs.md b/static/ru/tx/socialdb-proofs.md index c016569..a03410c 100644 --- a/static/ru/tx/socialdb-proofs.md +++ b/static/ru/tx/socialdb-proofs.md @@ -109,3 +109,10 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - вариант для исходника виджета: `mob.near/widget/Profile`, блок `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` Ключевая идея не меняется: начните с читаемого значения и его write-block, восстановите receipt `*.near -> social.near` из блока, а затем декодируйте payload `social.near set` из исходной транзакции. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/socialdb-proofs/index.md b/static/ru/tx/socialdb-proofs/index.md index c016569..a03410c 100644 --- a/static/ru/tx/socialdb-proofs/index.md +++ b/static/ru/tx/socialdb-proofs/index.md @@ -109,3 +109,10 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - вариант для исходника виджета: `mob.near/widget/Profile`, блок `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` Ключевая идея не меняется: начните с читаемого значения и его write-block, восстановите receipt `*.near -> social.near` из блока, а затем декодируйте payload `social.near set` из исходной транзакции. +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/transactions.md b/static/ru/tx/transactions.md index cee060f..5de5459 100644 --- a/static/ru/tx/transactions.md +++ b/static/ru/tx/transactions.md @@ -98,3 +98,10 @@ "refName": "TransactionsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/transactions/index.md b/static/ru/tx/transactions/index.md index cee060f..5de5459 100644 --- a/static/ru/tx/transactions/index.md +++ b/static/ru/tx/transactions/index.md @@ -98,3 +98,10 @@ "refName": "TransactionsResponse" } ``` +--- +## О FastNear + +- FastNear обрабатывает более 10 млрд запросов в месяц. +- FastNear управляет более чем 100 нодами по всему миру. +- FastNear предлагает щедрые кредиты и бесплатный пробный период. +- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). From c9f7986fedab3c02bb32df631b1f2eaad0bb5822 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Fri, 24 Apr 2026 05:56:52 -0700 Subject: [PATCH 35/35] Align FastNear docs examples for 1.0.2 --- docs/agents/auth-for-agents.mdx | 10 +- docs/agents/index.mdx | 9 +- docs/api/examples.md | 41 +- docs/auth/index.mdx | 14 +- docs/fastdata/kv/examples.md | 24 +- docs/fastdata/kv/index.md | 5 +- docs/index.md | 2 +- docs/neardata/examples.md | 30 +- docs/rpc/examples.md | 45 +- docs/transaction-flow/finality.mdx | 11 +- docs/transaction-flow/reference.mdx | 35 +- docs/transaction-flow/runtime-execution.mdx | 9 +- docs/transfers/examples.md | 28 +- docs/tx/berry-club.mdx | 17 +- docs/tx/examples.md | 38 +- docs/tx/socialdb-proofs.mdx | 49 +- .../current/agents/auth-for-agents.mdx | 10 +- .../current/agents/index.mdx | 9 +- .../current/api/examples.md | 41 +- .../current/auth/index.mdx | 8 +- .../current/fastdata/kv/examples.md | 24 +- .../current/fastdata/kv/index.md | 5 +- .../current/index.md | 4 +- .../current/neardata/examples.md | 30 +- .../current/rpc/examples.md | 45 +- .../current/transaction-flow/finality.mdx | 11 +- .../current/transaction-flow/reference.mdx | 35 +- .../transaction-flow/runtime-execution.mdx | 9 +- .../current/transfers/examples.md | 28 +- .../current/tx/berry-club.mdx | 17 +- .../current/tx/examples.md | 38 +- .../current/tx/socialdb-proofs.mdx | 49 +- src/data/fastnearAiMarkdownFooter.json | 8 +- static/internationalization/index.md | 4 +- .../nearjs/0.9.7/cjs/index.cjs | 25 - .../nearjs/0.9.7/cjs/index.cjs.map | 1 - .../nearjs/0.9.7/cjs/index.d.cts | 233 - .../nearjs/0.9.7/cjs/near.cjs | 553 --- .../nearjs/0.9.7/cjs/near.cjs.map | 1 - .../nearjs/0.9.7/cjs/state.cjs | 207 - .../nearjs/0.9.7/cjs/state.cjs.map | 1 - .../nearjs/0.9.7/esm/index.d.ts | 233 - .../nearjs/0.9.7/esm/index.js | 4 - .../nearjs/0.9.7/esm/index.js.map | 1 - .../nearjs/0.9.7/esm/near.js | 522 -- .../nearjs/0.9.7/esm/near.js.map | 1 - .../nearjs/0.9.7/esm/state.js | 172 - .../nearjs/0.9.7/esm/state.js.map | 1 - .../nearjs/0.9.7/umd/browser.global.js | 4379 ----------------- .../nearjs/0.9.7/umd/browser.global.js.map | 1 - static/ru/agents.md | 17 +- static/ru/agents/auth.md | 18 +- static/ru/agents/auth/index.md | 18 +- static/ru/agents/choosing-surfaces.md | 4 +- static/ru/agents/choosing-surfaces/index.md | 4 +- static/ru/agents/index.md | 17 +- static/ru/agents/playbooks.md | 4 +- static/ru/agents/playbooks/index.md | 4 +- static/ru/api.md | 4 +- static/ru/api/examples.md | 71 +- static/ru/api/examples/index.md | 71 +- static/ru/api/index.md | 4 +- static/ru/api/reference.md | 4 +- static/ru/api/reference/index.md | 4 +- static/ru/api/system/health.md | 4 +- static/ru/api/system/health/index.md | 4 +- static/ru/api/system/status.md | 4 +- static/ru/api/system/status/index.md | 4 +- static/ru/api/v0/account-ft.md | 4 +- static/ru/api/v0/account-ft/index.md | 4 +- static/ru/api/v0/account-nft.md | 4 +- static/ru/api/v0/account-nft/index.md | 4 +- static/ru/api/v0/account-staking.md | 4 +- static/ru/api/v0/account-staking/index.md | 4 +- static/ru/api/v0/public-key-all.md | 4 +- static/ru/api/v0/public-key-all/index.md | 4 +- static/ru/api/v0/public-key.md | 4 +- static/ru/api/v0/public-key/index.md | 4 +- static/ru/api/v1/account-ft.md | 4 +- static/ru/api/v1/account-ft/index.md | 4 +- static/ru/api/v1/account-full.md | 4 +- static/ru/api/v1/account-full/index.md | 4 +- static/ru/api/v1/account-nft.md | 4 +- static/ru/api/v1/account-nft/index.md | 4 +- static/ru/api/v1/account-staking.md | 4 +- static/ru/api/v1/account-staking/index.md | 4 +- static/ru/api/v1/ft-top.md | 4 +- static/ru/api/v1/ft-top/index.md | 4 +- static/ru/api/v1/public-key-all.md | 4 +- static/ru/api/v1/public-key-all/index.md | 4 +- static/ru/api/v1/public-key.md | 4 +- static/ru/api/v1/public-key/index.md | 4 +- static/ru/apis/fastnear/system/health.md | 4 +- .../ru/apis/fastnear/system/health/index.md | 4 +- static/ru/apis/fastnear/system/status.md | 4 +- .../ru/apis/fastnear/system/status/index.md | 4 +- static/ru/apis/fastnear/v0/account_ft.md | 4 +- .../ru/apis/fastnear/v0/account_ft/index.md | 4 +- static/ru/apis/fastnear/v0/account_nft.md | 4 +- .../ru/apis/fastnear/v0/account_nft/index.md | 4 +- static/ru/apis/fastnear/v0/account_staking.md | 4 +- .../apis/fastnear/v0/account_staking/index.md | 4 +- .../ru/apis/fastnear/v0/public_key_lookup.md | 4 +- .../fastnear/v0/public_key_lookup/index.md | 4 +- .../apis/fastnear/v0/public_key_lookup_all.md | 4 +- .../v0/public_key_lookup_all/index.md | 4 +- static/ru/apis/fastnear/v1/account_ft.md | 4 +- .../ru/apis/fastnear/v1/account_ft/index.md | 4 +- static/ru/apis/fastnear/v1/account_full.md | 4 +- .../ru/apis/fastnear/v1/account_full/index.md | 4 +- static/ru/apis/fastnear/v1/account_nft.md | 4 +- .../ru/apis/fastnear/v1/account_nft/index.md | 4 +- static/ru/apis/fastnear/v1/account_staking.md | 4 +- .../apis/fastnear/v1/account_staking/index.md | 4 +- static/ru/apis/fastnear/v1/ft_top.md | 4 +- static/ru/apis/fastnear/v1/ft_top/index.md | 4 +- .../ru/apis/fastnear/v1/public_key_lookup.md | 4 +- .../fastnear/v1/public_key_lookup/index.md | 4 +- .../apis/fastnear/v1/public_key_lookup_all.md | 4 +- .../v1/public_key_lookup_all/index.md | 4 +- .../apis/kv-fastdata/v0/all_by_predecessor.md | 4 +- .../v0/all_by_predecessor/index.md | 4 +- .../ru/apis/kv-fastdata/v0/get_history_key.md | 4 +- .../kv-fastdata/v0/get_history_key/index.md | 4 +- .../ru/apis/kv-fastdata/v0/get_latest_key.md | 4 +- .../kv-fastdata/v0/get_latest_key/index.md | 4 +- .../apis/kv-fastdata/v0/history_by_account.md | 4 +- .../v0/history_by_account/index.md | 4 +- .../ru/apis/kv-fastdata/v0/history_by_key.md | 4 +- .../kv-fastdata/v0/history_by_key/index.md | 4 +- .../kv-fastdata/v0/history_by_predecessor.md | 4 +- .../v0/history_by_predecessor/index.md | 4 +- .../apis/kv-fastdata/v0/latest_by_account.md | 4 +- .../kv-fastdata/v0/latest_by_account/index.md | 4 +- .../kv-fastdata/v0/latest_by_predecessor.md | 4 +- .../v0/latest_by_predecessor/index.md | 4 +- static/ru/apis/kv-fastdata/v0/multi.md | 4 +- static/ru/apis/kv-fastdata/v0/multi/index.md | 4 +- static/ru/apis/neardata/system/health.md | 4 +- .../ru/apis/neardata/system/health/index.md | 4 +- static/ru/apis/neardata/v0/block.md | 4 +- static/ru/apis/neardata/v0/block/index.md | 4 +- static/ru/apis/neardata/v0/block_chunk.md | 4 +- .../ru/apis/neardata/v0/block_chunk/index.md | 4 +- static/ru/apis/neardata/v0/block_headers.md | 4 +- .../apis/neardata/v0/block_headers/index.md | 4 +- .../ru/apis/neardata/v0/block_optimistic.md | 4 +- .../neardata/v0/block_optimistic/index.md | 4 +- static/ru/apis/neardata/v0/block_shard.md | 4 +- .../ru/apis/neardata/v0/block_shard/index.md | 4 +- static/ru/apis/neardata/v0/first_block.md | 4 +- .../ru/apis/neardata/v0/first_block/index.md | 4 +- .../ru/apis/neardata/v0/last_block_final.md | 4 +- .../neardata/v0/last_block_final/index.md | 4 +- .../apis/neardata/v0/last_block_optimistic.md | 4 +- .../v0/last_block_optimistic/index.md | 4 +- static/ru/apis/transactions/v0/account.md | 4 +- .../ru/apis/transactions/v0/account/index.md | 4 +- static/ru/apis/transactions/v0/block.md | 4 +- static/ru/apis/transactions/v0/block/index.md | 4 +- static/ru/apis/transactions/v0/blocks.md | 4 +- .../ru/apis/transactions/v0/blocks/index.md | 4 +- static/ru/apis/transactions/v0/receipt.md | 4 +- .../ru/apis/transactions/v0/receipt/index.md | 4 +- .../ru/apis/transactions/v0/transactions.md | 4 +- .../transactions/v0/transactions/index.md | 4 +- static/ru/apis/transfers/v0/transfers.md | 4 +- .../ru/apis/transfers/v0/transfers/index.md | 4 +- static/ru/auth.md | 16 +- static/ru/auth/index.md | 16 +- static/ru/fastdata/kv.md | 10 +- static/ru/fastdata/kv/all-by-predecessor.md | 4 +- .../fastdata/kv/all-by-predecessor/index.md | 4 +- static/ru/fastdata/kv/examples.md | 61 +- static/ru/fastdata/kv/examples/index.md | 61 +- static/ru/fastdata/kv/get-history-key.md | 4 +- .../ru/fastdata/kv/get-history-key/index.md | 4 +- static/ru/fastdata/kv/get-latest-key.md | 4 +- static/ru/fastdata/kv/get-latest-key/index.md | 4 +- static/ru/fastdata/kv/history-by-account.md | 4 +- .../fastdata/kv/history-by-account/index.md | 4 +- static/ru/fastdata/kv/history-by-key.md | 4 +- static/ru/fastdata/kv/history-by-key/index.md | 4 +- .../ru/fastdata/kv/history-by-predecessor.md | 4 +- .../kv/history-by-predecessor/index.md | 4 +- static/ru/fastdata/kv/index.md | 10 +- static/ru/fastdata/kv/latest-by-account.md | 4 +- .../ru/fastdata/kv/latest-by-account/index.md | 4 +- .../ru/fastdata/kv/latest-by-predecessor.md | 4 +- .../kv/latest-by-predecessor/index.md | 4 +- static/ru/fastdata/kv/multi.md | 4 +- static/ru/fastdata/kv/multi/index.md | 4 +- static/ru/guides/llms.txt | 2 +- static/ru/index.md | 8 +- static/ru/internationalization.md | 4 +- static/ru/internationalization/index.md | 4 +- static/ru/llms-full.txt | 480 +- static/ru/llms.txt | 2 +- static/ru/neardata.md | 4 +- static/ru/neardata/block-chunk.md | 4 +- static/ru/neardata/block-chunk/index.md | 4 +- static/ru/neardata/block-headers.md | 4 +- static/ru/neardata/block-headers/index.md | 4 +- static/ru/neardata/block-optimistic.md | 4 +- static/ru/neardata/block-optimistic/index.md | 4 +- static/ru/neardata/block-shard.md | 4 +- static/ru/neardata/block-shard/index.md | 4 +- static/ru/neardata/block.md | 4 +- static/ru/neardata/block/index.md | 4 +- static/ru/neardata/examples.md | 41 +- static/ru/neardata/examples/index.md | 41 +- static/ru/neardata/first-block.md | 4 +- static/ru/neardata/first-block/index.md | 4 +- static/ru/neardata/index.md | 4 +- static/ru/neardata/last-block-final.md | 4 +- static/ru/neardata/last-block-final/index.md | 4 +- static/ru/neardata/last-block-optimistic.md | 4 +- .../neardata/last-block-optimistic/index.md | 4 +- static/ru/neardata/system/health.md | 4 +- static/ru/neardata/system/health/index.md | 4 +- static/ru/redocly-config.md | 4 +- static/ru/redocly-config/index.md | 4 +- static/ru/rpc.md | 4 +- static/ru/rpc/account/view-access-key-list.md | 4 +- .../rpc/account/view-access-key-list/index.md | 4 +- static/ru/rpc/account/view-access-key.md | 4 +- .../ru/rpc/account/view-access-key/index.md | 4 +- static/ru/rpc/account/view-account.md | 4 +- static/ru/rpc/account/view-account/index.md | 4 +- static/ru/rpc/block/block-by-height.md | 4 +- static/ru/rpc/block/block-by-height/index.md | 4 +- static/ru/rpc/block/block-by-id.md | 4 +- static/ru/rpc/block/block-by-id/index.md | 4 +- static/ru/rpc/block/block-effects.md | 4 +- static/ru/rpc/block/block-effects/index.md | 4 +- static/ru/rpc/contract/call-function.md | 4 +- static/ru/rpc/contract/call-function/index.md | 4 +- static/ru/rpc/contract/view-code.md | 4 +- static/ru/rpc/contract/view-code/index.md | 4 +- ...view-global-contract-code-by-account-id.md | 4 +- .../index.md | 4 +- .../rpc/contract/view-global-contract-code.md | 4 +- .../view-global-contract-code/index.md | 4 +- static/ru/rpc/contract/view-state.md | 4 +- static/ru/rpc/contract/view-state/index.md | 4 +- static/ru/rpc/examples.md | 66 +- static/ru/rpc/examples/index.md | 66 +- static/ru/rpc/index.md | 4 +- static/ru/rpc/protocol/changes.md | 4 +- static/ru/rpc/protocol/changes/index.md | 4 +- .../ru/rpc/protocol/chunk-by-block-shard.md | 4 +- .../protocol/chunk-by-block-shard/index.md | 4 +- static/ru/rpc/protocol/chunk-by-hash.md | 4 +- static/ru/rpc/protocol/chunk-by-hash/index.md | 4 +- static/ru/rpc/protocol/client-config.md | 4 +- static/ru/rpc/protocol/client-config/index.md | 4 +- .../protocol/experimental-congestion-level.md | 4 +- .../experimental-congestion-level/index.md | 4 +- .../experimental-light-client-block-proof.md | 4 +- .../index.md | 4 +- .../experimental-light-client-proof.md | 4 +- .../experimental-light-client-proof/index.md | 4 +- .../protocol/experimental-protocol-config.md | 4 +- .../experimental-protocol-config/index.md | 4 +- .../experimental-split-storage-info.md | 4 +- .../experimental-split-storage-info/index.md | 4 +- static/ru/rpc/protocol/gas-price-by-block.md | 4 +- .../rpc/protocol/gas-price-by-block/index.md | 4 +- static/ru/rpc/protocol/gas-price.md | 4 +- static/ru/rpc/protocol/gas-price/index.md | 4 +- static/ru/rpc/protocol/genesis-config.md | 4 +- .../ru/rpc/protocol/genesis-config/index.md | 4 +- static/ru/rpc/protocol/health.md | 4 +- static/ru/rpc/protocol/health/index.md | 4 +- static/ru/rpc/protocol/latest-block.md | 4 +- static/ru/rpc/protocol/latest-block/index.md | 4 +- static/ru/rpc/protocol/light-client-proof.md | 4 +- .../rpc/protocol/light-client-proof/index.md | 4 +- static/ru/rpc/protocol/maintenance-windows.md | 4 +- .../rpc/protocol/maintenance-windows/index.md | 4 +- static/ru/rpc/protocol/metrics.md | 4 +- static/ru/rpc/protocol/metrics/index.md | 4 +- static/ru/rpc/protocol/network-info.md | 4 +- static/ru/rpc/protocol/network-info/index.md | 4 +- .../rpc/protocol/next-light-client-block.md | 4 +- .../protocol/next-light-client-block/index.md | 4 +- static/ru/rpc/protocol/status.md | 4 +- static/ru/rpc/protocol/status/index.md | 4 +- .../ru/rpc/transaction/broadcast-tx-async.md | 4 +- .../transaction/broadcast-tx-async/index.md | 4 +- .../ru/rpc/transaction/broadcast-tx-commit.md | 4 +- .../transaction/broadcast-tx-commit/index.md | 4 +- .../rpc/transaction/experimental-receipt.md | 4 +- .../transaction/experimental-receipt/index.md | 4 +- .../rpc/transaction/experimental-tx-status.md | 4 +- .../experimental-tx-status/index.md | 4 +- static/ru/rpc/transaction/send-tx.md | 4 +- static/ru/rpc/transaction/send-tx/index.md | 4 +- static/ru/rpc/transaction/tx-status.md | 4 +- static/ru/rpc/transaction/tx-status/index.md | 4 +- .../experimental-validators-ordered.md | 4 +- .../experimental-validators-ordered/index.md | 4 +- .../ru/rpc/validators/validators-by-epoch.md | 4 +- .../validators/validators-by-epoch/index.md | 4 +- .../ru/rpc/validators/validators-current.md | 4 +- .../validators/validators-current/index.md | 4 +- static/ru/rpcs/account/view_access_key.md | 4 +- .../ru/rpcs/account/view_access_key/index.md | 4 +- .../ru/rpcs/account/view_access_key_list.md | 4 +- .../account/view_access_key_list/index.md | 4 +- static/ru/rpcs/account/view_account.md | 4 +- static/ru/rpcs/account/view_account/index.md | 4 +- static/ru/rpcs/block/block_by_height.md | 4 +- static/ru/rpcs/block/block_by_height/index.md | 4 +- static/ru/rpcs/block/block_by_id.md | 4 +- static/ru/rpcs/block/block_by_id/index.md | 4 +- static/ru/rpcs/block/block_effects.md | 4 +- static/ru/rpcs/block/block_effects/index.md | 4 +- static/ru/rpcs/contract/call.md | 4 +- static/ru/rpcs/contract/call/index.md | 4 +- static/ru/rpcs/contract/view_code.md | 4 +- static/ru/rpcs/contract/view_code/index.md | 4 +- .../contract/view_global_contract_code.md | 4 +- .../view_global_contract_code/index.md | 4 +- ...view_global_contract_code_by_account_id.md | 4 +- .../index.md | 4 +- static/ru/rpcs/contract/view_state.md | 4 +- static/ru/rpcs/contract/view_state/index.md | 4 +- .../protocol/EXPERIMENTAL_congestion_level.md | 4 +- .../EXPERIMENTAL_congestion_level/index.md | 4 +- .../EXPERIMENTAL_light_client_block_proof.md | 4 +- .../index.md | 4 +- .../EXPERIMENTAL_light_client_proof.md | 4 +- .../EXPERIMENTAL_light_client_proof/index.md | 4 +- .../protocol/EXPERIMENTAL_protocol_config.md | 4 +- .../EXPERIMENTAL_protocol_config/index.md | 4 +- .../EXPERIMENTAL_split_storage_info.md | 4 +- .../EXPERIMENTAL_split_storage_info/index.md | 4 +- static/ru/rpcs/protocol/changes.md | 4 +- static/ru/rpcs/protocol/changes/index.md | 4 +- .../ru/rpcs/protocol/chunk_by_block_shard.md | 4 +- .../protocol/chunk_by_block_shard/index.md | 4 +- static/ru/rpcs/protocol/chunk_by_hash.md | 4 +- .../ru/rpcs/protocol/chunk_by_hash/index.md | 4 +- static/ru/rpcs/protocol/client_config.md | 4 +- .../ru/rpcs/protocol/client_config/index.md | 4 +- static/ru/rpcs/protocol/gas_price.md | 4 +- static/ru/rpcs/protocol/gas_price/index.md | 4 +- static/ru/rpcs/protocol/gas_price_by_block.md | 4 +- .../rpcs/protocol/gas_price_by_block/index.md | 4 +- static/ru/rpcs/protocol/genesis_config.md | 4 +- .../ru/rpcs/protocol/genesis_config/index.md | 4 +- static/ru/rpcs/protocol/health.md | 4 +- static/ru/rpcs/protocol/health/index.md | 4 +- static/ru/rpcs/protocol/latest_block.md | 4 +- static/ru/rpcs/protocol/latest_block/index.md | 4 +- static/ru/rpcs/protocol/light_client_proof.md | 4 +- .../rpcs/protocol/light_client_proof/index.md | 4 +- .../ru/rpcs/protocol/maintenance_windows.md | 4 +- .../protocol/maintenance_windows/index.md | 4 +- static/ru/rpcs/protocol/metrics.md | 4 +- static/ru/rpcs/protocol/metrics/index.md | 4 +- static/ru/rpcs/protocol/network_info.md | 4 +- static/ru/rpcs/protocol/network_info/index.md | 4 +- .../rpcs/protocol/next_light_client_block.md | 4 +- .../protocol/next_light_client_block/index.md | 4 +- static/ru/rpcs/protocol/status.md | 4 +- static/ru/rpcs/protocol/status/index.md | 4 +- .../rpcs/transaction/EXPERIMENTAL_receipt.md | 4 +- .../transaction/EXPERIMENTAL_receipt/index.md | 4 +- .../transaction/EXPERIMENTAL_tx_status.md | 4 +- .../EXPERIMENTAL_tx_status/index.md | 4 +- .../ru/rpcs/transaction/broadcast_tx_async.md | 4 +- .../transaction/broadcast_tx_async/index.md | 4 +- .../rpcs/transaction/broadcast_tx_commit.md | 4 +- .../transaction/broadcast_tx_commit/index.md | 4 +- static/ru/rpcs/transaction/send_tx.md | 4 +- static/ru/rpcs/transaction/send_tx/index.md | 4 +- static/ru/rpcs/transaction/tx_status.md | 4 +- static/ru/rpcs/transaction/tx_status/index.md | 4 +- .../EXPERIMENTAL_validators_ordered.md | 4 +- .../EXPERIMENTAL_validators_ordered/index.md | 4 +- .../ru/rpcs/validators/validators_by_epoch.md | 4 +- .../validators/validators_by_epoch/index.md | 4 +- .../ru/rpcs/validators/validators_current.md | 4 +- .../validators/validators_current/index.md | 4 +- static/ru/snapshots.md | 4 +- static/ru/snapshots/examples.md | 4 +- static/ru/snapshots/examples/index.md | 4 +- static/ru/snapshots/index.md | 4 +- static/ru/snapshots/mainnet.md | 4 +- static/ru/snapshots/mainnet/index.md | 4 +- static/ru/snapshots/testnet.md | 4 +- static/ru/snapshots/testnet/index.md | 4 +- static/ru/transfers.md | 4 +- static/ru/transfers/examples.md | 61 +- static/ru/transfers/examples/index.md | 61 +- static/ru/transfers/index.md | 4 +- static/ru/transfers/query.md | 4 +- static/ru/transfers/query/index.md | 4 +- static/ru/tx.md | 4 +- static/ru/tx/account.md | 4 +- static/ru/tx/account/index.md | 4 +- static/ru/tx/block.md | 4 +- static/ru/tx/block/index.md | 4 +- static/ru/tx/blocks.md | 4 +- static/ru/tx/blocks/index.md | 4 +- static/ru/tx/examples.md | 60 +- static/ru/tx/examples/berry-club.md | 17 +- static/ru/tx/examples/berry-club/index.md | 17 +- static/ru/tx/examples/index.md | 60 +- static/ru/tx/index.md | 4 +- static/ru/tx/receipt.md | 4 +- static/ru/tx/receipt/index.md | 4 +- static/ru/tx/socialdb-proofs.md | 82 +- static/ru/tx/socialdb-proofs/index.md | 82 +- static/ru/tx/transactions.md | 4 +- static/ru/tx/transactions/index.md | 4 +- 418 files changed, 2277 insertions(+), 7692 deletions(-) delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/cjs/index.cjs delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/cjs/index.cjs.map delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/cjs/index.d.cts delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/cjs/near.cjs delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/cjs/near.cjs.map delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/cjs/state.cjs delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/cjs/state.cjs.map delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/esm/index.d.ts delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/esm/index.js delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/esm/index.js.map delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/esm/near.js delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/esm/near.js.map delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/esm/state.js delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/esm/state.js.map delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/umd/browser.global.js delete mode 100644 static/js-loaded-globally/nearjs/0.9.7/umd/browser.global.js.map diff --git a/docs/agents/auth-for-agents.mdx b/docs/agents/auth-for-agents.mdx index 6089a97..7d32e80 100644 --- a/docs/agents/auth-for-agents.mdx +++ b/docs/agents/auth-for-agents.mdx @@ -12,7 +12,7 @@ page_actions: 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. +One FastNear API key works across the RPC and API endpoints. 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 @@ -68,13 +68,13 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { }); ``` -## When auth is missing +## If the runtime is missing the key -Many public FastNear endpoints are still readable without a key. If the agent can answer the user's question from public traffic, do that. +Agents should normally start with a configured FastNear API key. Some public FastNear reads may still work without one, but that should not be the default operating posture. -When a key is required for higher limits, paid access, or authenticated traffic: +If the configured runtime does not have the key yet: -- tell the user to create or retrieve a key from [dashboard.fastnear.com](https://dashboard.fastnear.com) +- tell the user to create or retrieve a key from [FastNear Dashboard](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 diff --git a/docs/agents/index.mdx b/docs/agents/index.mdx index 15ef0f1..dc300f0 100644 --- a/docs/agents/index.mdx +++ b/docs/agents/index.mdx @@ -25,6 +25,7 @@ This page is the operational starting point for AI agents, crawlers, and automat - Use indexed APIs when the user wants a product-shaped answer such as balances, holdings, account history, or transfer history. - Use [RPC Reference](/rpc) when the user needs canonical protocol-native fields, contract calls, or transaction submission. +- If you are using the hosted JS runtime at [js.fastnear.com](https://js.fastnear.com), start with low-level methods such as `near.view`, `near.queryAccount`, and `near.tx.*`, and use `near.recipes.*` only when a task helper is the shortest path to the answer. - Use [NEAR Data API](/neardata) when the question is about recent optimistic or finalized blocks and explicit polling. - Use [Snapshots](/snapshots/) for operator workflows, not application-level data reads. - One FastNear API key works across the RPC and API endpoints. @@ -98,10 +99,10 @@ Bad pattern: ## 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: +Start with a FastNear API key and reuse it 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" -FASTNEAR_API_KEY= +: "${FASTNEAR_API_KEY:?Set FASTNEAR_API_KEY in your shell before running this example.}" curl "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -110,12 +111,12 @@ curl "https://rpc.mainnet.fastnear.com" \ ``` ```bash title="URL parameter" -FASTNEAR_API_KEY= +: "${FASTNEAR_API_KEY:?Set FASTNEAR_API_KEY in your shell before running this example.}" curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" ``` -Get a key from [dashboard.fastnear.com](https://dashboard.fastnear.com). Operational posture for non-interactive runtimes: [Auth for Agents](/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](/auth). +Get your API key from [FastNear Dashboard](https://dashboard.fastnear.com). Operational posture for non-interactive runtimes: [Auth for Agents](/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](/auth). ## Pull clean docs into a prompt diff --git a/docs/api/examples.md b/docs/api/examples.md index 02ad053..1c6146f 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -10,16 +10,19 @@ page_actions: ## Examples +All shell examples below work on the public FastNear API hosts as-is. If `FASTNEAR_API_KEY` is set in your shell, they add it as a bearer header automatically; if it is unset, they fall back to the public unauthenticated path. + ### Summarize one account in one call `/v1/account/{id}/full` is the FastNear API's account aggregator — one call bundles the account's NEAR state, every FT contract it's touched, every NFT collection it's received, and every validator pool it's delegated to. When you already have the `account_id`, this is the fastest "what does this account look like?" read. ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{ account_id, near_balance_yocto: .state.balance, @@ -37,17 +40,18 @@ Look up which account a key belongs to, then read that account's holdings in one ```bash PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi LOOKUP="$(curl -s "https://api.fastnear.com/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY")" + "${AUTH_HEADER[@]}")" echo "$LOOKUP" | jq '{matched: (.account_ids | length), account_ids}' ACCOUNT_ID="$(echo "$LOOKUP" | jq -r '.account_ids[0]')" curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{account_id, state, tokens: (.tokens|length), nfts: (.nfts|length), pools: (.pools|length)}' ``` @@ -59,10 +63,11 @@ NEAR account state has three buckets that wallet UIs tend to conflate: `balance` ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq ' (.state.balance | tonumber) as $amount | (.state.locked | tonumber) as $locked @@ -92,10 +97,11 @@ Every entry under `/full`'s `tokens`, `nfts`, and `pools` arrays carries its own ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq ' [ (.tokens // [])[].last_update_block_height, @@ -123,10 +129,11 @@ NEAR account names encode a hierarchy: `mint.sharddog.near` is a subaccount of ` ```bash ACCOUNT_ID=root.near PUBLISHER=sharddog.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq --arg publisher "$PUBLISHER" ' ("." + $publisher) as $suffix | { @@ -155,12 +162,13 @@ Direct pool positions live on `/staking`; liquid staking tokens (stNEAR, LiNEAR, ```bash ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi STAKING="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/staking" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY")" + "${AUTH_HEADER[@]}")" FT="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/ft" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY")" + "${AUTH_HEADER[@]}")" jq -n \ --argjson staking "$STAKING" \ @@ -169,11 +177,12 @@ jq -n \ ($staking.pools // []) as $direct | (($ft.tokens // []) | map(select(.contract_id as $id | $providers | index($id)))) as $liquid | { - classification: + classification: ( if ($direct|length)>0 and ($liquid|length)>0 then "mixed" elif ($direct|length)>0 then "direct_only" elif ($liquid|length)>0 then "liquid_only" - else "no_visible_staking_position" end, + else "no_visible_staking_position" end + ), direct_pools: ($direct | map(.pool_id)), liquid_tokens: ($liquid | map({contract_id, balance})) }' diff --git a/docs/auth/index.mdx b/docs/auth/index.mdx index 59b25fc..fe5a6f7 100644 --- a/docs/auth/index.mdx +++ b/docs/auth/index.mdx @@ -25,16 +25,16 @@ import IconExternalLink from '@theme/Icon/ExternalLink'; # Auth & Access -One FastNear API key works across the RPC and API endpoints. Many public reads still work without one, but the auth model stays simple when you do need a key: use the same credential everywhere and send it either as a Bearer header or an `apiKey` query parameter. +One FastNear API key works across the RPC and API endpoints. Keep the auth model simple: use the same credential everywhere and send it either as a Bearer header or an `apiKey` query parameter. -Get a key at dashboard.fastnear.com. +Get a key in FastNear Dashboard. Live example pages also support `Copy example URL` for sharing prefilled requests. Shared example URLs run automatically on load whenever they include operation state, and saved API keys and tokens are never included in those public docs URLs. ## If you only need the rule - One FastNear API key works across RPC and API endpoints. -- Many public reads still work without a key. +- Set `FASTNEAR_API_KEY` in your shell, runtime, or secret manager. - Prefer `Authorization: Bearer ${FASTNEAR_API_KEY}` for production backends. - Use `?apiKey=${FASTNEAR_API_KEY}` when headers are awkward or you are doing quick curl/debug work. - Agents and automations should keep the key in env vars or a secret manager, not in browser storage. @@ -51,7 +51,7 @@ If you control the client, use the header form. ## Authorization header example ```bash -FASTNEAR_API_KEY= +: "${FASTNEAR_API_KEY:?Set FASTNEAR_API_KEY in your shell before running this example.}" curl "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -62,7 +62,7 @@ curl "https://rpc.mainnet.fastnear.com" \ ## `?apiKey=` query parameter example ```bash -FASTNEAR_API_KEY= +: "${FASTNEAR_API_KEY:?Set FASTNEAR_API_KEY in your shell before running this example.}" curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ @@ -72,8 +72,8 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" \ ## Where this applies - [RPC Reference](/rpc) uses the shared FastNear key model on both the regular and archival hosts. -- API families reuse the same key shape even when public reads often work without a key. -- The docs UI can forward an optional FastNear key for supported pages, but that browser storage behavior is a docs convenience, not a production pattern. +- API families reuse the same key shape across the REST surfaces. +- The docs UI can forward a saved FastNear key for supported pages, but that browser storage behavior is a docs convenience, not a production pattern. For agent and automation runtimes, use [Auth for Agents](/agents/auth). diff --git a/docs/fastdata/kv/examples.md b/docs/fastdata/kv/examples.md index 11366ee..239d909 100644 --- a/docs/fastdata/kv/examples.md +++ b/docs/fastdata/kv/examples.md @@ -10,6 +10,8 @@ page_actions: ## Examples +All shell examples below work on the public KV FastData hosts as-is. If `FASTNEAR_API_KEY` is set in your shell, they add it as a bearer header automatically; if it is unset, they fall back to the public unauthenticated path. + ### Check one exact key, then replay its history When you already know the contract, predecessor, and exact key, start narrow. `latest` answers the present-tense question; `history` shows whether that one row changed over time. @@ -18,12 +20,13 @@ When you already know the contract, predecessor, and exact key, start narrow. `l CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" LATEST="$(curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY")" + "${AUTH_HEADER[@]}")" echo "$LATEST" | jq '{ latest: ( @@ -39,7 +42,7 @@ echo "$LATEST" | jq '{ }' curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{writes: [.entries[] | {block_height, value}]}' ``` @@ -51,10 +54,11 @@ For an exact follow-edge style key like this, `latest` tells you the current ind ```bash PREDECESSOR_ID=jemartel.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data '{"include_metadata":true,"limit":10}')" @@ -69,12 +73,20 @@ For `jemartel.near`, the listing mixes an `account_id` identity assertion on `co Lift the most recent row and replay it through `history`: ```bash +PREDECESSOR_ID=jemartel.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data '{"include_metadata":true,"limit":10}')" + CURRENT_ACCOUNT_ID="$(echo "$FIRST" | jq -r '.entries[0].current_account_id')" EXACT_KEY="$(echo "$FIRST" | jq -r '.entries[0].key')" ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{entries: [.entries[] | {block_height, value}]}' ``` diff --git a/docs/fastdata/kv/index.md b/docs/fastdata/kv/index.md index 43c453e..fb75b8d 100644 --- a/docs/fastdata/kv/index.md +++ b/docs/fastdata/kv/index.md @@ -30,12 +30,13 @@ If you already know one exact key, start with the latest indexed row and stop as CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{ latest: ( .entries[0] diff --git a/docs/index.md b/docs/index.md index 329b63d..c3fb25b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -165,7 +165,7 @@ import Link from '@site/src/components/LocalizedLink';
Keys and billing - Dashboard + FastNear Dashboard

Sign in, create keys, and move to higher-limit usage patterns when you need them.

diff --git a/docs/neardata/examples.md b/docs/neardata/examples.md index 43e7fce..4dd9a88 100644 --- a/docs/neardata/examples.md +++ b/docs/neardata/examples.md @@ -12,15 +12,18 @@ page_actions: NEAR Data returns each block fully hydrated as one JSON document — header plus per-shard chunks, receipts, execution outcomes, and state changes — so a single `curl` gives you everything you need to filter for a specific contract without a second call. +All shell examples below work on the public NEAR Data hosts as-is. If `FASTNEAR_API_KEY` is set in your shell, they add it as a bearer header automatically; if it is unset, they fall back to the public unauthenticated path. + ### What block is NEAR on right now? `/v0/last_block/final` 302-redirects to the current finalized block. Before filtering for a specific contract, it's worth seeing what one block looks like at the protocol level: transactions arrive sharded, so the tx count for a block is a sum across shards — not a single top-level number. ```bash -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{ height: .block.header.height, timestamp_nanosec: .block.header.timestamp_nanosec, @@ -37,10 +40,11 @@ A live block shows 9 shards and a handful of transactions scattered across them ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq --arg contract "$TARGET_CONTRACT" '{ height: .block.header.height, contract: $contract, @@ -62,7 +66,8 @@ Optimistic blocks ship at `/v0/block_opt/{height}` about a second ahead of `/v0/ ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi count_touches() { jq --arg contract "$1" ' @@ -73,15 +78,15 @@ count_touches() { } OPT_LOCATION="$( - curl -s -D - -o /dev/null -H "Authorization: Bearer $FASTNEAR_API_KEY" "https://mainnet.neardata.xyz/v0/last_block/optimistic" \ + curl -s -D - -o /dev/null "${AUTH_HEADER[@]}" "https://mainnet.neardata.xyz/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" OPT_HEIGHT="${OPT_LOCATION##*/}" echo "optimistic @ $OPT_HEIGHT: $(curl -s "https://mainnet.neardata.xyz$OPT_LOCATION" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" | count_touches "$TARGET_CONTRACT") touches" + "${AUTH_HEADER[@]}" | count_touches "$TARGET_CONTRACT") touches" FINAL="$(curl -s "https://mainnet.neardata.xyz/v0/block/$OPT_HEIGHT" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY")" + "${AUTH_HEADER[@]}")" if [ "$(printf '%s' "$FINAL" | jq 'type')" = '"null"' ]; then echo "finalized @ $OPT_HEIGHT: not caught up yet" else @@ -97,17 +102,18 @@ Most finalized blocks show no state mutation for any given contract — activity ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi HEAD="$(curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" | jq '.block.header.height')" + "${AUTH_HEADER[@]}" | jq '.block.header.height')" FOUND_HEIGHT="" FOUND_SHARD="" for OFFSET in $(seq 0 15); do H=$((HEAD - OFFSET)) SHARD="$(curl -s "https://mainnet.neardata.xyz/v0/block/$H" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq -r --arg contract "$TARGET_CONTRACT" ' .shards[] | select([.state_changes[]? | select(.change.account_id? == $contract)] | length > 0) @@ -123,7 +129,7 @@ if [ -z "$FOUND_HEIGHT" ]; then echo "no state mutation for $TARGET_CONTRACT in the last 16 finalized blocks" else curl -s "https://mainnet.neardata.xyz/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq --arg contract "$TARGET_CONTRACT" --argjson height "$FOUND_HEIGHT" --argjson shard_id "$FOUND_SHARD" '{ height: $height, shard_id: $shard_id, diff --git a/docs/rpc/examples.md b/docs/rpc/examples.md index bc3c851..3bd401f 100644 --- a/docs/rpc/examples.md +++ b/docs/rpc/examples.md @@ -12,6 +12,8 @@ page_actions: Start with the RPC method that answers the question. Use `tx` to track inclusion and finality from a tx hash, and widen only when you need receipt trees, raw state, or shard-level tracing. +All shell examples below work on the public RPC hosts as-is. If `FASTNEAR_API_KEY` is set in your shell, they add it as a bearer header automatically; if it is unset, they fall back to the public unauthenticated path. + ## Account State ### Show an account's balance and storage at finality @@ -20,10 +22,11 @@ Start with the RPC method that answers the question. Use `tx` to track inclusion ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -43,10 +46,11 @@ Have a tx hash? Poll `tx` with the smallest `wait_until` threshold that answers ```bash TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow SIGNER_ACCOUNT_ID=mike.testnet -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://archival-rpc.testnet.fastnear.com" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" --arg signer_id "$SIGNER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "tx", @@ -75,13 +79,14 @@ A NEAR block is a header over N shard chunks, not a flat list of transactions. ` ```bash EMPTY_TX_ROOT=11111111111111111111111111111111 -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -BLOCK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ +BLOCK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ | jq -r '.result.sync_info.latest_block_hash')" -CHUNK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ +CHUNK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg block_hash "$BLOCK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"block",params:{block_id:$block_hash} }')" \ @@ -91,7 +96,7 @@ CHUNK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bear if [ -z "$CHUNK_HASH" ]; then echo "tip block had no transactions in any chunk — rerun on the next head" else - curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ + curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg chunk_hash "$CHUNK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"chunk",params:{chunk_id:$chunk_hash} }')" \ @@ -131,10 +136,11 @@ Any key with `tx_count: 0` was created and never used — the clearest candidate ```bash ACCOUNT_ID=root.near RECEIVER_ID=social.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -172,9 +178,10 @@ Contracts built with `near-sdk-rs` store the top-level `#[near_bindgen]` struct ```bash CONTRACT_ID=counter.near-examples.testnet -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -RAW_B64="$(curl -s "https://rpc.testnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ +RAW_B64="$(curl -s "https://rpc.testnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"view_state",account_id:$contract,prefix_base64:"U1RBVEU=",finality:"final"} @@ -201,11 +208,12 @@ These stay on exact SocialDB reads and on-chain readiness checks until the quest ```bash ACCOUNT_ID=root.near # account you're writing under SIGNER_ACCOUNT_ID=root.near # account signing the transaction -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -STORAGE="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ +STORAGE="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$STORAGE_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"get_account_storage",args_base64:$args,finality:"final"} @@ -216,7 +224,7 @@ if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then PERMISSION=true else PERM_ARGS_B64="$(jq -nc --arg pred "$SIGNER_ACCOUNT_ID" --arg key "$ACCOUNT_ID" '{predecessor_id:$pred,key:$key}' | base64 | tr -d '\n')" - PERMISSION="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ + PERMISSION="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$PERM_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"is_write_permission_granted",args_base64:$args,finality:"final"} @@ -243,14 +251,15 @@ SocialDB stores BOS widgets as `/widget/` keys on `social.near`. ```bash ACCOUNT_ID=mob.near WIDGET_NAME=Profile -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi KEYS_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" '{ keys: [($account_id + "/widget/*")], options: {return_type: "BlockHeight"} }' | base64 | tr -d '\n')" -curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ +curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$KEYS_ARGS" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"keys",args_base64:$args,finality:"final"} @@ -267,7 +276,7 @@ GET_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" '{ keys: [($account_id + "/widget/" + $widget)] }' | base64 | tr -d '\n')" -curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ +curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$GET_ARGS" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"get",args_base64:$args,finality:"final"} diff --git a/docs/transaction-flow/finality.mdx b/docs/transaction-flow/finality.mdx index 631f784..0044c67 100644 --- a/docs/transaction-flow/finality.mdx +++ b/docs/transaction-flow/finality.mdx @@ -129,21 +129,24 @@ pub enum FinalExecutionStatus { ## Querying Outcomes +These RPC reads work on the public FastNear RPC hosts. If `FASTNEAR_API_KEY` is already set in your shell, the snippets forward it automatically as a bearer header. + ### The `tx` RPC Method ```bash -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -X POST https://archival-rpc.mainnet.fastnear.com \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "1", "method": "tx", "params": { - "tx_hash": "6zgh2u9DqHHiXzdy9ouTP7oGky2T4nugqzqt9wJZwNFm", - "sender_account_id": "sender.near", + "tx_hash": "7ZKnhzt2MqMNmsk13dV8GAjGu3Db8aHzSBHeNeu9MJCq", + "sender_account_id": "root.near", "wait_until": "FINAL" } }' diff --git a/docs/transaction-flow/reference.mdx b/docs/transaction-flow/reference.mdx index 7f8b800..df80bf3 100644 --- a/docs/transaction-flow/reference.mdx +++ b/docs/transaction-flow/reference.mdx @@ -174,14 +174,17 @@ You never "call" another contract. You send them a letter and ask them to send o ### curl Examples +These RPC reads work on the public FastNear RPC hosts. If `FASTNEAR_API_KEY` is already set in your shell, the snippets forward it automatically as a bearer header. + #### Submit Transaction (Async) ```bash # Submit and get hash immediately -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -X POST https://rpc.mainnet.fastnear.com \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", @@ -197,10 +200,11 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Submit Transaction (Wait for Final) ```bash -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -X POST https://rpc.mainnet.fastnear.com \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", @@ -213,18 +217,19 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Check Transaction Status ```bash -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -X POST https://archival-rpc.mainnet.fastnear.com \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "1", "method": "tx", "params": { - "tx_hash": "6zgh2u9DqHHiXzdy9ouTP7oGky2T4nugqzqt9wJZwNFm", - "sender_account_id": "sender.testnet", + "tx_hash": "7ZKnhzt2MqMNmsk13dV8GAjGu3Db8aHzSBHeNeu9MJCq", + "sender_account_id": "root.near", "wait_until": "FINAL" } }' @@ -233,10 +238,11 @@ curl -X POST https://archival-rpc.mainnet.fastnear.com \ #### View Access Key (for nonce) ```bash -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -X POST https://rpc.mainnet.fastnear.com \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", @@ -245,8 +251,8 @@ curl -X POST https://rpc.mainnet.fastnear.com \ "params": { "request_type": "view_access_key", "finality": "final", - "account_id": "sender.testnet", - "public_key": "ed25519:DcA2MzgpJbrUATQLLceocVckhhAqrkingax4oJ9kZ847" + "account_id": "root.near", + "public_key": "ed25519:CByX7HPjgSGFi5G26zwgZGDFCFyBHvSv1fe7AFzmVe3" } }' @@ -257,10 +263,11 @@ curl -X POST https://rpc.mainnet.fastnear.com \ #### Get Recent Block Hash ```bash -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -X POST https://rpc.mainnet.fastnear.com \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", diff --git a/docs/transaction-flow/runtime-execution.mdx b/docs/transaction-flow/runtime-execution.mdx index 12c5368..1eb75c4 100644 --- a/docs/transaction-flow/runtime-execution.mdx +++ b/docs/transaction-flow/runtime-execution.mdx @@ -387,18 +387,19 @@ flowchart TD Clients can poll the `tx` RPC method with `wait_until: FINAL` to get all outcomes: ```bash -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -X POST https://archival-rpc.mainnet.fastnear.com \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "1", "method": "tx", "params": { - "tx_hash": "6zgh2u9DqHHiXzdy9ouTP7oGky2T4nugqzqt9wJZwNFm", - "sender_account_id": "sender.near", + "tx_hash": "7ZKnhzt2MqMNmsk13dV8GAjGu3Db8aHzSBHeNeu9MJCq", + "sender_account_id": "root.near", "wait_until": "FINAL" } }' diff --git a/docs/transfers/examples.md b/docs/transfers/examples.md index f7a7fe9..b0235a1 100644 --- a/docs/transfers/examples.md +++ b/docs/transfers/examples.md @@ -10,16 +10,19 @@ page_actions: ## Examples +These shell examples work on the public Transfers and Transactions endpoints. If `FASTNEAR_API_KEY` is already set in your shell, the snippets forward it as a bearer header automatically. + ### What's this account's recent transfer activity? `/v0/transfers` with just `account_id` and `desc: true` returns the most recent transfers touching that account across every asset type, both directions mixed. Each row already carries `human_amount`, `asset_id`, and `transaction_id`, so the feed doubles as a quick activity scan before you reach for filters. ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://transfers.main.fastnear.com/v0/transfers" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id: $account_id, desc: true, limit: 5}')" \ | jq '{ @@ -42,10 +45,11 @@ For `root.near`, the latest rows mix `FtTransfer` and `MtTransfer` assets. `asse ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi FEED="$(curl -s "https://transfers.main.fastnear.com/v0/transfers" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ account_id: $account_id, @@ -67,10 +71,24 @@ For the pinned account this returns recent incoming native-NEAR transfers of at When one row needs its execution anchor, take its `receipt_id` straight to `/v0/receipt`: ```bash +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +FEED="$(curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + direction: "receiver", + asset_id: "native:near", + min_amount: "1000000000000000000000000", + desc: true, + limit: 10 + }')")" RECEIPT_ID="$(echo "$FEED" | jq -r '.transfers[0].receipt_id')" curl -s "https://tx.main.fastnear.com/v0/receipt" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ | jq '.receipt | {receipt_id, transaction_hash, receiver_id, predecessor_id, tx_block_height, is_success}' diff --git a/docs/tx/berry-club.mdx b/docs/tx/berry-club.mdx index 51552e1..45cfa35 100644 --- a/docs/tx/berry-club.mdx +++ b/docs/tx/berry-club.mdx @@ -29,6 +29,8 @@ Start with the live board. If that already answers the question, stop there. Only switch to Transactions API when the question becomes historical: “what did Berry Club look like during one older era, and which `draw` calls made it look that way?” +These shell examples work against the public RPC and Transactions endpoints. If `FASTNEAR_API_KEY` is already set in your shell, the FastNear calls will forward it automatically as a bearer header. + `/v0/blocks` can Hydrate the candidate hashes and keep only top-level `draw` calls: ```bash -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -sS https://tx.main.fastnear.com/v0/transactions \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data '{ "tx_hashes": [ diff --git a/docs/tx/examples.md b/docs/tx/examples.md index 396ca87..e7be78b 100644 --- a/docs/tx/examples.md +++ b/docs/tx/examples.md @@ -10,16 +10,19 @@ page_actions: ## Start Here +All shell examples below work on the public Transactions API hosts as-is. If `FASTNEAR_API_KEY` is set in your shell, they add it as a bearer header automatically; if it is unset, they fall back to the public unauthenticated path. + ### I have one transaction hash. What happened? Paste the hash into `POST /v0/transactions` and one response usually holds the whole story. ```bash TX_HASH=7ZKnhzt2MqMNmsk13dV8GAjGu3Db8aHzSBHeNeu9MJCq -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://tx.main.fastnear.com/v0/transactions" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ @@ -42,10 +45,11 @@ List every logged receipt in the transaction with a flag for whether its logs co ```bash TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL LOG_FRAGMENT=Refund -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://tx.main.fastnear.com/v0/transactions" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg fragment "$LOG_FRAGMENT" ' @@ -71,10 +75,11 @@ The `Refund` fragment attributes to receipt `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2ot ```bash RECEIPT_ID=B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1 -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://tx.main.fastnear.com/v0/receipt" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ | jq '{ @@ -107,10 +112,11 @@ One batch submitted `CreateAccount → Transfer → AddKey → FunctionCall` and ```bash TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://tx.test.fastnear.com/v0/transactions" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ @@ -132,8 +138,12 @@ The tx-level status is `SuccessReceiptId` — the transaction successfully hande Now prove the earlier actions rolled back by asking for the account the batch *tried* to create: ```bash +NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + curl -s "https://rpc.testnet.fastnear.com" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "query", @@ -152,10 +162,11 @@ A tx's outer `execution_outcome.outcome.status` reports `SuccessReceiptId` whene TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL ORIGIN_CONTRACT_ID=wrap.near CALLBACK_METHOD=ft_resolve_transfer -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://tx.main.fastnear.com/v0/transactions" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ @@ -195,10 +206,11 @@ Receipt success is not transitive. A protocol can hand off cleanly and still see ```bash REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://tx.main.fastnear.com/v0/transactions" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg a "$REQUEST_TX" --arg b "$WORKER_TX" '{tx_hashes: [$a, $b]}')" \ | jq '[ diff --git a/docs/tx/socialdb-proofs.mdx b/docs/tx/socialdb-proofs.mdx index fcee464..7774b9a 100644 --- a/docs/tx/socialdb-proofs.mdx +++ b/docs/tx/socialdb-proofs.mdx @@ -11,6 +11,8 @@ page_actions: Use this page only when the starting point is already a readable SocialDB value from `api.near.social` and the next question is historical write lookup. +These shell steps work against the public SocialDB and FastNear endpoints. If `FASTNEAR_API_KEY` is already set in your shell, the FastNear calls will forward it automatically as a bearer header. + For FastNear-first jobs, start with [Transactions Examples](/tx/examples). Come here only when the question has become "which write made this readable SocialDB value true?" ## Canonical example: prove that `root.near` set `profile.name` to `Illia` @@ -34,7 +36,6 @@ For this live anchor: ```bash ACCOUNT_ID=root.near PROFILE_FIELD=profile/name -FASTNEAR_API_KEY= PROFILE="$(curl -s "https://api.near.social/get" \ -H 'content-type: application/json' \ @@ -55,8 +56,21 @@ PROFILE_BLOCK_HEIGHT="$(echo "$PROFILE" | jq -r --arg account_id "$ACCOUNT_ID" ' 2. Reuse that field-level block in FastNear block receipts and recover the receipt plus tx hash. ```bash +ACCOUNT_ID=root.near +PROFILE_FIELD=profile/name +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +PROFILE_BLOCK_HEIGHT="$( + curl -s "https://api.near.social/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" '.[$account_id].profile.name[":block"]' +)" BLOCK_RECEIPTS="$(curl -s "https://tx.main.fastnear.com/v0/block" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ block_id: $block_id, @@ -85,8 +99,37 @@ PROFILE_TX_HASH="$(echo "$BLOCK_RECEIPTS" | jq -r --arg account_id "$ACCOUNT_ID" 3. Reuse that tx hash in `POST /v0/transactions` and decode the SocialDB write payload. ```bash +ACCOUNT_ID=root.near +PROFILE_FIELD=profile/name +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +PROFILE_BLOCK_HEIGHT="$( + curl -s "https://api.near.social/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" '.[$account_id].profile.name[":block"]' +)" +PROFILE_TX_HASH="$( + curl -s "https://tx.main.fastnear.com/v0/block" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" curl -s "https://tx.main.fastnear.com/v0/transactions" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg account_id "$ACCOUNT_ID" '{ 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 7f78e3b..9c1af09 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 @@ -12,7 +12,7 @@ page_actions: Агенты должны аутентифицироваться в FastNear так же, как это делают продовые бэкенды. Не переносите браузерно-демонстрационный режим из UI документации в агента, воркера или среду автоматизации. -Один API-ключ FastNear работает во всех RPC и API-эндпоинтах. Многие публичные чтения работают и без ключа. Для агента важнее не само наличие аутентификации, а где живёт учётная запись, как она прикрепляется к запросам и как не допустить её утечки в промпты, логи или состояние браузера. +Один API-ключ FastNear работает во всех RPC и API-эндпоинтах. Для агента важнее не само наличие аутентификации, а где живёт учётная запись, как она прикрепляется к запросам и как не допустить её утечки в промпты, логи или состояние браузера. ## Если нужно только правило @@ -68,13 +68,13 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { }); ``` -## Когда аутентификации не хватает +## Если в среде выполнения нет ключа -Многие публичные эндпоинты FastNear по-прежнему доступны на чтение без ключа. Если агент может ответить на вопрос пользователя через публичный трафик — делайте так. +Агенту по умолчанию стоит стартовать с настроенным API-ключом FastNear. Некоторые публичные чтения могут сработать и без него, но это не должно быть базовой рабочей моделью. -Когда ключ нужен для повышенных лимитов, платного доступа или аутентифицированного трафика: +Если в настроенной среде ключа пока нет: -- подскажите пользователю создать или забрать ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com) +- подскажите пользователю создать или забрать ключ в [FastNear Dashboard](https://dashboard.fastnear.com) - попросите настроить его в переменной окружения, менеджере секретов или конфигурации бэкенда - не просите вставлять сырой ключ в чат, чтобы агент «носил» его с собой 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 1a7520b..bd1e365 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/agents/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/agents/index.mdx @@ -25,6 +25,7 @@ page_actions: - Используйте индексированные API, когда пользователю нужен ответ в продуктовой форме — балансы, активы, история аккаунта или история переводов. - Используйте [Справочник RPC](/rpc), когда пользователю нужны канонические поля на уровне протокола, вызовы контрактов или отправка транзакций. +- Если вы используете размещённый JS-рантайм на [js.fastnear.com](https://js.fastnear.com), начинайте с низкоуровневых методов вроде `near.view`, `near.queryAccount` и `near.tx.*`, а к `near.recipes.*` обращайтесь только тогда, когда task helper действительно является самым коротким путём к ответу. - Используйте [NEAR Data API](/neardata), когда вопрос касается свежих оптимистичных или финализированных блоков и явного опроса. - Используйте [Снапшоты](/snapshots/) для операторских сценариев, а не для чтения прикладных данных. - Один API-ключ FastNear работает и для RPC, и для API-эндпоинтов. @@ -98,10 +99,10 @@ page_actions: ## Аутентифицируйтесь один раз, используйте везде -Публичные эндпоинты часто работают и без ключа. Добавьте ключ, если нужны повышенные лимиты, единая аутентифицированная модель или платные сценарии. Один и тот же ключ работает со всеми API FastNear выше, включая обычные и архивные RPC-хосты; передавайте его либо в HTTP-заголовке, либо в URL-параметре: +Начните с API-ключа FastNear и используйте его во всех API FastNear выше, включая обычные и архивные RPC-хосты. Передавайте его либо в HTTP-заголовке, либо в URL-параметре: ```bash title="Заголовок Authorization" -FASTNEAR_API_KEY= +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" curl "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -110,12 +111,12 @@ curl "https://rpc.mainnet.fastnear.com" \ ``` ```bash title="URL-параметр" -FASTNEAR_API_KEY= +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" ``` -Получить ключ: [dashboard.fastnear.com](https://dashboard.fastnear.com). Операционный режим для неинтерактивных сред: [Аутентификация для агентов](/agents/auth) — ключи должны жить в переменных окружения или менеджере секретов, а не в браузерном хранилище, логах чатов или промптах. Полный сценарий и детали заголовков: [Аутентификация и доступ](/auth). +Получите API-ключ в [FastNear Dashboard](https://dashboard.fastnear.com). Операционный режим для неинтерактивных сред: [Аутентификация для агентов](/agents/auth) — ключи должны жить в переменных окружения или менеджере секретов, а не в браузерном хранилище, логах чатов или промптах. Полный сценарий и детали заголовков: [Аутентификация и доступ](/auth). ## Как вынимать чистую документацию в промпт diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md index d381fa5..6868726 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/api/examples.md @@ -10,16 +10,19 @@ page_actions: ## Примеры +Все shell-примеры ниже работают на публичных FastNear API-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ### Свести один аккаунт за один вызов `/v1/account/{id}/full` — это агрегатор аккаунтов в FastNear API: один вызов собирает NEAR-состояние аккаунта, каждый FT-контракт, которого он касался, каждую NFT-коллекцию, которую он получил, и каждый валидаторский пул, в который делегировал. Если у вас уже есть `account_id`, это самый быстрый ответ на вопрос «что это за аккаунт?». ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{ account_id, near_balance_yocto: .state.balance, @@ -37,17 +40,18 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ ```bash PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi LOOKUP="$(curl -s "https://api.fastnear.com/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY")" + "${AUTH_HEADER[@]}")" echo "$LOOKUP" | jq '{matched: (.account_ids | length), account_ids}' ACCOUNT_ID="$(echo "$LOOKUP" | jq -r '.account_ids[0]')" curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{account_id, state, tokens: (.tokens|length), nfts: (.nfts|length), pools: (.pools|length)}' ``` @@ -59,10 +63,11 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq ' (.state.balance | tonumber) as $amount | (.state.locked | tonumber) as $locked @@ -92,10 +97,11 @@ jq считает в IEEE-754 double, поэтому NEAR-значения вы ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq ' [ (.tokens // [])[].last_update_block_height, @@ -123,10 +129,11 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ ```bash ACCOUNT_ID=root.near PUBLISHER=sharddog.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq --arg publisher "$PUBLISHER" ' ("." + $publisher) as $suffix | { @@ -155,12 +162,13 @@ curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ ```bash ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi STAKING="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/staking" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY")" + "${AUTH_HEADER[@]}")" FT="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/ft" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY")" + "${AUTH_HEADER[@]}")" jq -n \ --argjson staking "$STAKING" \ @@ -169,11 +177,12 @@ jq -n \ ($staking.pools // []) as $direct | (($ft.tokens // []) | map(select(.contract_id as $id | $providers | index($id)))) as $liquid | { - classification: + classification: ( if ($direct|length)>0 and ($liquid|length)>0 then "mixed" elif ($direct|length)>0 then "direct_only" elif ($liquid|length)>0 then "liquid_only" - else "no_visible_staking_position" end, + else "no_visible_staking_position" end + ), direct_pools: ($direct | map(.pool_id)), liquid_tokens: ($liquid | map({contract_id, balance})) }' diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx index 3a75532..201629d 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/auth/index.mdx @@ -32,18 +32,18 @@ import IconExternalLink from '@theme/Icon/ExternalLink'; # Аутентификация и доступ -Один API-ключ FastNear работает и для [RPC](/rpc), и для [API-эндпоинтов](/api). Многие публичные чтения работают и без него, но когда ключ нужен, модель остаётся простой: используйте один и тот же ключ везде и передавайте его либо через заголовок `Authorization: Bearer`, либо как URL-параметр `?apiKey=`. +Один API-ключ FastNear работает и для [RPC](/rpc), и для [API-эндпоинтов](/api). Держите модель аутентификации простой: используйте один и тот же ключ везде и передавайте его либо через заголовок `Authorization: Bearer`, либо как URL-параметр `?apiKey=`. Та же модель действует и на обычных, и на архивных RPC-хостах. Хранение ключа в браузере для UI документации — это удобство документации, а не продовый шаблон. -Войдите на dashboard.fastnear.com, чтобы получить ключ, и отправляйте его в каждом запросе одним из способов ниже. +Получите ключ в FastNear Dashboard и отправляйте его в каждом запросе одним из способов ниже. Страницы с интерактивными примерами также поддерживают `Copy example URL`, чтобы делиться уже заполненными запросами. Общие URL примеров выполняются автоматически при загрузке, когда в них есть состояние операции, а сохранённые API-ключи и токены никогда не включаются в такие общедоступные URL документации. ## Через заголовок Authorization ```bash -FASTNEAR_API_KEY= +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" curl "https://rpc.mainnet.fastnear.com" \ -H "Authorization: Bearer $FASTNEAR_API_KEY" \ @@ -54,7 +54,7 @@ curl "https://rpc.mainnet.fastnear.com" \ ## Через URL-параметр `?apiKey=` ```bash -FASTNEAR_API_KEY= +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md index db241ec..4656e25 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/examples.md @@ -10,6 +10,8 @@ page_actions: ## Примеры +Все shell-примеры ниже работают на публичных KV FastData-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ### Проверить один точный ключ и сразу посмотреть его историю Если контракт, `predecessor_id` и точный ключ уже известны, начинайте с узкого запроса. `latest` отвечает на вопрос о текущем состоянии, а `history` показывает, менялась ли именно эта строка со временем. @@ -18,12 +20,13 @@ page_actions: CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" LATEST="$(curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY")" + "${AUTH_HEADER[@]}")" echo "$LATEST" | jq '{ latest: ( @@ -39,7 +42,7 @@ echo "$LATEST" | jq '{ }' curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{writes: [.entries[] | {block_height, value}]}' ``` @@ -51,10 +54,11 @@ curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSO ```bash PREDECESSOR_ID=jemartel.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data '{"include_metadata":true,"limit":10}')" @@ -69,12 +73,20 @@ echo "$FIRST" | jq '{ Поднимите самую свежую строку и прогоните её через `history`: ```bash +PREDECESSOR_ID=jemartel.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data '{"include_metadata":true,"limit":10}')" + CURRENT_ACCOUNT_ID="$(echo "$FIRST" | jq -r '.entries[0].current_account_id')" EXACT_KEY="$(echo "$FIRST" | jq -r '.entries[0].key')" ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{entries: [.entries[] | {block_height, value}]}' ``` diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md index c393264..dcefef7 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/fastdata/kv/index.md @@ -30,12 +30,13 @@ https://kv.test.fastnear.com CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{ latest: ( .entries[0] diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/index.md index 49a0a18..04de47a 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/index.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/index.md @@ -54,7 +54,7 @@ import Link from '@site/src/components/LocalizedLink';

- Сначала возьмите ключ — войдите на [dashboard.fastnear.com](https://dashboard.fastnear.com) через Google или email, получите бесплатные стартовые кредиты и подключайте месячную подписку или бессрочные резервные кредиты только тогда, когда понадобятся повышенные лимиты. + Получите API-ключ на dashboard.fastnear.com и используйте его во всех запросах к FastNear из одного и того же рабочего окружения.

@@ -172,7 +172,7 @@ import Link from '@site/src/components/LocalizedLink';
Ключи и оплата - Dashboard + FastNear Dashboard

Войдите, создайте ключи и переходите на сценарии с более высокими лимитами, когда понадобится.

diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md index 8475604..b371522 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/neardata/examples.md @@ -12,15 +12,18 @@ page_actions: NEAR Data возвращает каждый блок полностью гидратированным одним JSON-документом — header плюс per-shard chunks, receipts, результаты исполнения и state changes, — так что один `curl` уже даёт всё необходимое, чтобы отфильтровать нужный контракт без второго запроса. +Все shell-примеры ниже работают на публичных NEAR Data-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ### На каком блоке NEAR сейчас? `/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Прежде чем фильтровать по конкретному контракту, полезно увидеть, как выглядит один блок на уровне протокола: транзакции приходят с разбивкой по shard, поэтому общее число транзакций в блоке — это сумма по shards, а не одно поле верхнего уровня. ```bash -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{ height: .block.header.height, timestamp_nanosec: .block.header.timestamp_nanosec, @@ -37,10 +40,11 @@ curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq --arg contract "$TARGET_CONTRACT" '{ height: .block.header.height, contract: $contract, @@ -62,7 +66,8 @@ Optimistic-блоки ходят по `/v0/block_opt/{height}` примерно ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi count_touches() { jq --arg contract "$1" ' @@ -73,15 +78,15 @@ count_touches() { } OPT_LOCATION="$( - curl -s -D - -o /dev/null -H "Authorization: Bearer $FASTNEAR_API_KEY" "https://mainnet.neardata.xyz/v0/last_block/optimistic" \ + curl -s -D - -o /dev/null "${AUTH_HEADER[@]}" "https://mainnet.neardata.xyz/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" OPT_HEIGHT="${OPT_LOCATION##*/}" echo "optimistic @ $OPT_HEIGHT: $(curl -s "https://mainnet.neardata.xyz$OPT_LOCATION" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" | count_touches "$TARGET_CONTRACT") touches" + "${AUTH_HEADER[@]}" | count_touches "$TARGET_CONTRACT") touches" FINAL="$(curl -s "https://mainnet.neardata.xyz/v0/block/$OPT_HEIGHT" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY")" + "${AUTH_HEADER[@]}")" if [ "$(printf '%s' "$FINAL" | jq 'type')" = '"null"' ]; then echo "finalized @ $OPT_HEIGHT: not caught up yet" else @@ -97,17 +102,18 @@ fi ```bash TARGET_CONTRACT=intents.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi HEAD="$(curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" | jq '.block.header.height')" + "${AUTH_HEADER[@]}" | jq '.block.header.height')" FOUND_HEIGHT="" FOUND_SHARD="" for OFFSET in $(seq 0 15); do H=$((HEAD - OFFSET)) SHARD="$(curl -s "https://mainnet.neardata.xyz/v0/block/$H" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq -r --arg contract "$TARGET_CONTRACT" ' .shards[] | select([.state_changes[]? | select(.change.account_id? == $contract)] | length > 0) @@ -123,7 +129,7 @@ if [ -z "$FOUND_HEIGHT" ]; then echo "no state mutation for $TARGET_CONTRACT in the last 16 finalized blocks" else curl -s "https://mainnet.neardata.xyz/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ | jq --arg contract "$TARGET_CONTRACT" --argjson height "$FOUND_HEIGHT" --argjson shard_id "$FOUND_SHARD" '{ height: $height, shard_id: $shard_id, diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md index 6a2c156..97b749f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/examples.md @@ -12,6 +12,8 @@ page_actions: Начинайте с RPC-метода, который отвечает на вопрос. Используйте `tx`, чтобы отследить включение и финальность по хешу транзакции, и расширяйте поверхность только когда нужны дерево receipts, сырой state или трассировка на уровне shard. +Все shell-примеры ниже работают на публичных RPC-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ## Состояние аккаунта ### Показать баланс и storage аккаунта на finality @@ -20,10 +22,11 @@ page_actions: ```bash ACCOUNT_ID=root.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -43,10 +46,11 @@ curl -s "https://rpc.mainnet.fastnear.com" \ ```bash TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow SIGNER_ACCOUNT_ID=mike.testnet -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://archival-rpc.testnet.fastnear.com" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" --arg signer_id "$SIGNER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "tx", @@ -75,13 +79,14 @@ curl -s "https://archival-rpc.testnet.fastnear.com" \ ```bash EMPTY_TX_ROOT=11111111111111111111111111111111 -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -BLOCK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ +BLOCK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ | jq -r '.result.sync_info.latest_block_hash')" -CHUNK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ +CHUNK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg block_hash "$BLOCK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"block",params:{block_id:$block_hash} }')" \ @@ -91,7 +96,7 @@ CHUNK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bear if [ -z "$CHUNK_HASH" ]; then echo "tip block had no transactions in any chunk — rerun on the next head" else - curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ + curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg chunk_hash "$CHUNK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"chunk",params:{chunk_id:$chunk_hash} }')" \ @@ -131,10 +136,11 @@ fi ```bash ACCOUNT_ID=root.near RECEIVER_ID=social.near -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -s "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer $FASTNEAR_API_KEY" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -172,9 +178,10 @@ View-метод вроде `get_num` всё равно заставляет уз ```bash CONTRACT_ID=counter.near-examples.testnet -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -RAW_B64="$(curl -s "https://rpc.testnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ +RAW_B64="$(curl -s "https://rpc.testnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"view_state",account_id:$contract,prefix_base64:"U1RBVEU=",finality:"final"} @@ -201,11 +208,12 @@ jq -n --arg raw "$RAW_B64" --argjson val "$DECODED_I8" '{raw_bytes_base64: $raw, ```bash ACCOUNT_ID=root.near # account you're writing under SIGNER_ACCOUNT_ID=root.near # account signing the transaction -FASTNEAR_API_KEY= +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -STORAGE="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ +STORAGE="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$STORAGE_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"get_account_storage",args_base64:$args,finality:"final"} @@ -216,7 +224,7 @@ if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then PERMISSION=true else PERM_ARGS_B64="$(jq -nc --arg pred "$SIGNER_ACCOUNT_ID" --arg key "$ACCOUNT_ID" '{predecessor_id:$pred,key:$key}' | base64 | tr -d '\n')" - PERMISSION="$(curl -s "https://rpc.mainnet.fastnear.com" -H "Authorization: Bearer $FASTNEAR_API_KEY" -H 'content-type: application/json' \ + PERMISSION="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$PERM_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"is_write_permission_granted",args_base64:$args,finality:"final"} @@ -243,14 +251,15 @@ SocialDB хранит BOS-виджеты как ключи `/widget/ { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); - } - return to; -}; -var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); -var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); -var index_exports = {}; -module.exports = __toCommonJS(index_exports); -__reExport(index_exports, require("./near.js"), module.exports); -// Annotate the CommonJS export names for ESM import in node: -0 && (module.exports = { - ...require("./near.js") -}); -//# sourceMappingURL=index.cjs.map diff --git a/static/js-loaded-globally/nearjs/0.9.7/cjs/index.cjs.map b/static/js-loaded-globally/nearjs/0.9.7/cjs/index.cjs.map deleted file mode 100644 index 09878da..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/cjs/index.cjs.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/index.ts"],"sourcesContent":["// See tsup.config.ts for additional banner/footer js\nexport * from \"./near.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA;AAAA;AACA,0BAAc,sBADd;","names":[]} \ No newline at end of file diff --git a/static/js-loaded-globally/nearjs/0.9.7/cjs/index.d.cts b/static/js-loaded-globally/nearjs/0.9.7/cjs/index.d.cts deleted file mode 100644 index 77f036e..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/cjs/index.d.cts +++ /dev/null @@ -1,233 +0,0 @@ -import * as borsh from 'borsh'; - -interface NetworkConfig { - networkId: string; - nodeUrl?: string; - walletUrl?: string; - helperUrl?: string; - explorerUrl?: string; - [key: string]: any; -} -interface TxStatus { - txId: string; - updateTimestamp?: number; - [key: string]: any; -} -type TxHistory = Record; - -declare const MaxBlockDelayMs: number; -interface AccessKeyWithError { - result: { - nonce: number; - permission?: any; - error?: string; - }; -} -interface WalletTxResult { - url?: string; - outcomes?: Array<{ - transaction: { - hash: string; - }; - }>; - rejected?: boolean; - error?: string; -} -interface BlockView { - result: { - header: { - hash: string; - timestamp_nanosec: string; - }; - }; -} -interface LastKnownBlock { - header: { - hash: string; - timestamp_nanosec: string; - }; -} -declare function withBlockId(params: Record, blockId?: string): { - finality: string; -} | { - block_id: string; -}; -declare function sendRpc(method: string, params: Record | any[]): Promise; -declare function afterTxSent(txId: string): void; -declare function sendTxToRpc(signedTxBase64: string, waitUntil: string | undefined, txId: string): Promise; -interface AccessKeyView { - nonce: number; - permission: any; -} -/** - * Generates a mock transaction ID. - * - * This function creates a pseudo-unique transaction ID for testing or - * non-production use. It combines the current timestamp with a - * random component for uniqueness. - * - * **Note:** This is not cryptographically secure and should not be used - * for actual transaction processing. - * - * @returns {string} A mock transaction ID in the format `tx-{timestamp}-{random}` - */ -declare function generateTxId(): string; -declare const accountId: () => string | null | undefined; -declare const publicKey: () => string | null | undefined; -declare const config: (newConfig?: Record) => NetworkConfig; -declare const authStatus: () => string | Record; -declare const getPublicKeyForContract: (opts?: any) => string | null | undefined; -declare const selected: () => { - network: string; - nodeUrl: string | undefined; - walletUrl: string | undefined; - helperUrl: string | undefined; - explorerUrl: string | undefined; - account: string | null | undefined; - contract: string | null | undefined; - publicKey: string | null | undefined; -}; -declare const requestSignIn: ({ contractId }: { - contractId: string; -}) => Promise; -declare const view: ({ contractId, methodName, args, argsBase64, blockId, }: { - contractId: string; - methodName: string; - args?: any; - argsBase64?: string; - blockId?: string; -}) => Promise; -declare const queryAccount: ({ accountId, blockId, }: { - accountId: string; - blockId?: string; -}) => Promise; -declare const queryBlock: ({ blockId }: { - blockId?: string; -}) => Promise; -declare const queryAccessKey: ({ accountId, publicKey, blockId, }: { - accountId: string; - publicKey: string; - blockId?: string; -}) => Promise; -declare const queryTx: ({ txHash, accountId }: { - txHash: string; - accountId: string; -}) => Promise; -declare const localTxHistory: () => TxHistory; -declare const signOut: () => void; -declare const sendTx: ({ receiverId, actions, waitUntil, }: { - receiverId: string; - actions: any[]; - waitUntil?: string; -}) => Promise; -declare const exp: { - utils: {}; - borsh: { - serialize: typeof borsh.serialize; - deserialize: typeof borsh.deserialize; - }; - borshSchema: { - Ed25519Signature: borsh.Schema; - Secp256k1Signature: borsh.Schema; - Signature: borsh.Schema; - Ed25519Data: borsh.Schema; - Secp256k1Data: borsh.Schema; - PublicKey: borsh.Schema; - FunctionCallPermission: borsh.Schema; - FullAccessPermission: borsh.Schema; - AccessKeyPermission: borsh.Schema; - AccessKey: borsh.Schema; - CreateAccount: borsh.Schema; - DeployContract: borsh.Schema; - FunctionCall: borsh.Schema; - Transfer: borsh.Schema; - Stake: borsh.Schema; - AddKey: borsh.Schema; - DeleteKey: borsh.Schema; - DeleteAccount: borsh.Schema; - ClassicAction: borsh.Schema; - DelegateAction: borsh.Schema; - SignedDelegate: borsh.Schema; - Action: borsh.Schema; - Transaction: borsh.Schema; - SignedTransaction: borsh.Schema; - }; -}; -declare const utils: {}; -declare const state: {}; -declare const event: any; -declare const actions: { - functionCall: ({ methodName, gas, deposit, args, argsBase64, }: { - methodName: string; - gas?: string; - deposit?: string; - args?: Record; - argsBase64?: string; - }) => { - type: string; - methodName: string; - args: Record | undefined; - argsBase64: string | undefined; - gas: string | undefined; - deposit: string | undefined; - }; - transfer: (yoctoAmount: string) => { - type: string; - deposit: string; - }; - stakeNEAR: ({ amount, publicKey }: { - amount: string; - publicKey: string; - }) => { - type: string; - stake: string; - publicKey: string; - }; - addFullAccessKey: ({ publicKey }: { - publicKey: string; - }) => { - type: string; - publicKey: string; - accessKey: { - permission: string; - }; - }; - addLimitedAccessKey: ({ publicKey, allowance, accountId, methodNames, }: { - publicKey: string; - allowance: string; - accountId: string; - methodNames: string[]; - }) => { - type: string; - publicKey: string; - accessKey: { - permission: string; - allowance: string; - receiverId: string; - methodNames: string[]; - }; - }; - deleteKey: ({ publicKey }: { - publicKey: string; - }) => { - type: string; - publicKey: string; - }; - deleteAccount: ({ beneficiaryId }: { - beneficiaryId: string; - }) => { - type: string; - beneficiaryId: string; - }; - createAccount: () => { - type: string; - }; - deployContract: ({ codeBase64 }: { - codeBase64: string; - }) => { - type: string; - codeBase64: string; - }; -}; - -export { type AccessKeyView, type AccessKeyWithError, type BlockView, type LastKnownBlock, MaxBlockDelayMs, type WalletTxResult, accountId, actions, afterTxSent, authStatus, config, event, exp, generateTxId, getPublicKeyForContract, localTxHistory, publicKey, queryAccessKey, queryAccount, queryBlock, queryTx, requestSignIn, selected, sendRpc, sendTx, sendTxToRpc, signOut, state, utils, view, withBlockId }; diff --git a/static/js-loaded-globally/nearjs/0.9.7/cjs/near.cjs b/static/js-loaded-globally/nearjs/0.9.7/cjs/near.cjs deleted file mode 100644 index 2fb7463..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/cjs/near.cjs +++ /dev/null @@ -1,553 +0,0 @@ -/* ⋈ 🏃🏻💨 FastNear API - CJS (@fastnear/api version 0.9.7) */ -/* https://www.npmjs.com/package/@fastnear/api/v/0.9.7 */ -"use strict"; -var __create = Object.create; -var __defProp = Object.defineProperty; -var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -var __getOwnPropNames = Object.getOwnPropertyNames; -var __getProtoOf = Object.getPrototypeOf; -var __hasOwnProp = Object.prototype.hasOwnProperty; -var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); -var __export = (target, all) => { - for (var name in all) - __defProp(target, name, { get: all[name], enumerable: true }); -}; -var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); - } - return to; -}; -var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( - // If the importer is in node compatibility mode or this is not an ESM - // file that has been converted to a CommonJS file using a Babel- - // compatible transform (i.e. "__esModule" has not been set), then set - // "default" to the CommonJS "module.exports" for node compatibility. - isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, - mod -)); -var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); -var near_exports = {}; -__export(near_exports, { - MaxBlockDelayMs: () => MaxBlockDelayMs, - accountId: () => accountId, - actions: () => actions, - afterTxSent: () => afterTxSent, - authStatus: () => authStatus, - config: () => config, - event: () => event, - exp: () => exp, - generateTxId: () => generateTxId, - getPublicKeyForContract: () => getPublicKeyForContract, - localTxHistory: () => localTxHistory, - publicKey: () => publicKey, - queryAccessKey: () => queryAccessKey, - queryAccount: () => queryAccount, - queryBlock: () => queryBlock, - queryTx: () => queryTx, - requestSignIn: () => requestSignIn, - selected: () => selected, - sendRpc: () => sendRpc, - sendTx: () => sendTx, - sendTxToRpc: () => sendTxToRpc, - signOut: () => signOut, - state: () => state, - utils: () => utils, - view: () => view, - withBlockId: () => withBlockId -}); -module.exports = __toCommonJS(near_exports); -var import_big = __toESM(require("big.js"), 1); -var import_utils = require("@fastnear/utils"); -var import_state = require("./state.js"); -var import_state2 = require("./state.js"); -var import_sha2 = require("@noble/hashes/sha2"); -var reExportAllUtils = __toESM(require("@fastnear/utils"), 1); -var stateExports = __toESM(require("./state.js"), 1); -import_big.default.DP = 27; -const MaxBlockDelayMs = 1e3 * 60 * 60 * 6; -function withBlockId(params, blockId) { - if (blockId === "final" || blockId === "optimistic") { - return { ...params, finality: blockId }; - } - return blockId ? { ...params, block_id: blockId } : { ...params, finality: "optimistic" }; -} -__name(withBlockId, "withBlockId"); -async function sendRpc(method, params) { - const config2 = (0, import_state2.getConfig)(); - if (!config2?.nodeUrl) { - throw new Error("fastnear: getConfig() returned invalid config: missing nodeUrl."); - } - const response = await fetch(config2.nodeUrl, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: `fastnear-${Date.now()}`, - method, - params - }) - }); - const result = await response.json(); - if (result.error) { - throw new Error(JSON.stringify(result.error)); - } - return result; -} -__name(sendRpc, "sendRpc"); -function afterTxSent(txId) { - const txHistory = (0, import_state.getTxHistory)(); - sendRpc("tx", { - tx_hash: txHistory[txId]?.txHash, - sender_account_id: txHistory[txId]?.tx?.signerId, - wait_until: "EXECUTED_OPTIMISTIC" - }).then((result) => { - const successValue = result?.result?.status?.SuccessValue; - (0, import_state.updateTxHistory)({ - txId, - status: "Executed", - result, - successValue: successValue ? (0, import_utils.tryParseJson)((0, import_utils.fromBase64)(successValue)) : void 0, - finalState: true - }); - }).catch((error) => { - (0, import_state.updateTxHistory)({ - txId, - status: "ErrorAfterIncluded", - error: (0, import_utils.tryParseJson)(error.message) ?? error.message, - finalState: true - }); - }); -} -__name(afterTxSent, "afterTxSent"); -async function sendTxToRpc(signedTxBase64, waitUntil, txId) { - waitUntil = waitUntil || "INCLUDED"; - try { - const sendTxRes = await sendRpc("send_tx", { - signed_tx_base64: signedTxBase64, - wait_until: waitUntil - }); - (0, import_state.updateTxHistory)({ txId, status: "Included", finalState: false }); - afterTxSent(txId); - return sendTxRes; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - (0, import_state.updateTxHistory)({ - txId, - status: "Error", - error: (0, import_utils.tryParseJson)(errorMessage) ?? errorMessage, - finalState: false - }); - throw new Error(errorMessage); - } -} -__name(sendTxToRpc, "sendTxToRpc"); -function generateTxId() { - const randomPart = crypto.getRandomValues(new Uint32Array(2)).join(""); - return `tx-${Date.now()}-${parseInt(randomPart, 10).toString(36)}`; -} -__name(generateTxId, "generateTxId"); -const accountId = /* @__PURE__ */ __name(() => import_state._state.accountId, "accountId"); -const publicKey = /* @__PURE__ */ __name(() => import_state._state.publicKey, "publicKey"); -const config = /* @__PURE__ */ __name((newConfig) => { - const current = (0, import_state2.getConfig)(); - if (newConfig) { - if (newConfig.networkId && current.networkId !== newConfig.networkId) { - (0, import_state2.setConfig)(newConfig.networkId); - (0, import_state.update)({ accountId: null, privateKey: null, lastWalletId: null }); - (0, import_utils.lsSet)("block", null); - (0, import_state2.resetTxHistory)(); - } - (0, import_state2.setConfig)({ ...(0, import_state2.getConfig)(), ...newConfig }); - } - return (0, import_state2.getConfig)(); -}, "config"); -const authStatus = /* @__PURE__ */ __name(() => { - if (!import_state._state.accountId) { - return "SignedOut"; - } - return "SignedIn"; -}, "authStatus"); -const getPublicKeyForContract = /* @__PURE__ */ __name((opts) => { - return publicKey(); -}, "getPublicKeyForContract"); -const selected = /* @__PURE__ */ __name(() => { - const network = (0, import_state2.getConfig)().networkId; - const nodeUrl = (0, import_state2.getConfig)().nodeUrl; - const walletUrl = (0, import_state2.getConfig)().walletUrl; - const helperUrl = (0, import_state2.getConfig)().helperUrl; - const explorerUrl = (0, import_state2.getConfig)().explorerUrl; - const account = accountId(); - const contract = import_state._state.accessKeyContractId; - const publicKey2 = getPublicKeyForContract(); - return { - network, - nodeUrl, - walletUrl, - helperUrl, - explorerUrl, - account, - contract, - publicKey: publicKey2 - }; -}, "selected"); -const requestSignIn = /* @__PURE__ */ __name(async ({ contractId }) => { - const privateKey = (0, import_utils.privateKeyFromRandom)(); - (0, import_state.update)({ accessKeyContractId: contractId, accountId: null, privateKey }); - const pubKey = (0, import_utils.publicKeyFromPrivate)(privateKey); - const result = await import_state._adapter.signIn({ - networkId: (0, import_state2.getConfig)().networkId, - contractId, - publicKey: pubKey - }); - if (result.error) { - throw new Error(`Wallet error: ${result.error}`); - } - if (result.url) { - if (typeof window !== "undefined") { - setTimeout(() => { - window.location.href = result.url; - }, 100); - } - } else if (result.accountId) { - (0, import_state.update)({ accountId: result.accountId }); - } -}, "requestSignIn"); -const view = /* @__PURE__ */ __name(async ({ - contractId, - methodName, - args, - argsBase64, - blockId -}) => { - const encodedArgs = argsBase64 || (args ? (0, import_utils.toBase64)(JSON.stringify(args)) : ""); - const queryResult = await sendRpc( - "query", - withBlockId( - { - request_type: "call_function", - account_id: contractId, - method_name: methodName, - args_base64: encodedArgs - }, - blockId - ) - ); - return (0, import_utils.parseJsonFromBytes)(queryResult.result.result); -}, "view"); -const queryAccount = /* @__PURE__ */ __name(async ({ - accountId: accountId2, - blockId -}) => { - return sendRpc( - "query", - withBlockId({ request_type: "view_account", account_id: accountId2 }, blockId) - ); -}, "queryAccount"); -const queryBlock = /* @__PURE__ */ __name(async ({ blockId }) => { - return sendRpc("block", withBlockId({}, blockId)); -}, "queryBlock"); -const queryAccessKey = /* @__PURE__ */ __name(async ({ - accountId: accountId2, - publicKey: publicKey2, - blockId -}) => { - return sendRpc( - "query", - withBlockId( - { request_type: "view_access_key", account_id: accountId2, public_key: publicKey2 }, - blockId - ) - ); -}, "queryAccessKey"); -const queryTx = /* @__PURE__ */ __name(async ({ txHash, accountId: accountId2 }) => { - return sendRpc("tx", [txHash, accountId2]); -}, "queryTx"); -const localTxHistory = /* @__PURE__ */ __name(() => { - return (0, import_state.getTxHistory)(); -}, "localTxHistory"); -const signOut = /* @__PURE__ */ __name(() => { - (0, import_state.update)({ accountId: null, privateKey: null, contractId: null }); - (0, import_state2.setConfig)(import_state.NETWORKS[import_state.DEFAULT_NETWORK_ID]); -}, "signOut"); -const sendTx = /* @__PURE__ */ __name(async ({ - receiverId, - actions: actions2, - waitUntil -}) => { - const signerId = import_state._state.accountId; - if (!signerId) throw new Error("Must sign in"); - const publicKey2 = import_state._state.publicKey ?? ""; - const privKey = import_state._state.privateKey; - const txId = generateTxId(); - if (!privKey || receiverId !== import_state._state.accessKeyContractId || !(0, import_utils.canSignWithLAK)(actions2)) { - const jsonTx = { signerId, receiverId, actions: actions2 }; - (0, import_state.updateTxHistory)({ status: "Pending", txId, tx: jsonTx, finalState: false }); - const url = new URL(typeof window !== "undefined" ? window.location.href : ""); - url.searchParams.set("txIds", txId); - const existingParams = new URLSearchParams(window.location.search); - existingParams.forEach((value, key) => { - if (!url.searchParams.has(key)) { - url.searchParams.set(key, value); - } - }); - url.searchParams.delete("errorCode"); - url.searchParams.delete("errorMessage"); - try { - const result = await import_state._adapter.sendTransactions({ - transactions: [jsonTx], - callbackUrl: url.toString() - }); - if (result.url) { - if (typeof window !== "undefined") { - setTimeout(() => { - window.location.href = result.url; - }, 100); - } - } else if (result.outcomes?.length) { - result.outcomes.forEach( - (r) => (0, import_state.updateTxHistory)({ - txId, - status: "Executed", - result: r, - txHash: r.transaction.hash, - finalState: true - }) - ); - } else if (result.rejected) { - (0, import_state.updateTxHistory)({ txId, status: "RejectedByUser", finalState: true }); - } else if (result.error) { - (0, import_state.updateTxHistory)({ - txId, - status: "Error", - error: (0, import_utils.tryParseJson)(result.error), - finalState: true - }); - } - return result; - } catch (err) { - console.error("fastnear: error sending tx using adapter:", err); - (0, import_state.updateTxHistory)({ - txId, - status: "Error", - error: (0, import_utils.tryParseJson)(err.message), - finalState: true - }); - return Promise.reject(err); - } - } - let nonce = (0, import_utils.lsGet)("nonce"); - if (nonce == null) { - const accessKey = await queryAccessKey({ accountId: signerId, publicKey: publicKey2 }); - if (accessKey.result.error) { - throw new Error(`Access key error: ${accessKey.result.error} when attempting to get nonce for ${signerId} for public key ${publicKey2}`); - } - nonce = accessKey.result.nonce; - (0, import_utils.lsSet)("nonce", nonce); - } - let lastKnownBlock = (0, import_utils.lsGet)("block"); - if (!lastKnownBlock || parseFloat(lastKnownBlock.header.timestamp_nanosec) / 1e6 + MaxBlockDelayMs < Date.now()) { - const latestBlock = await queryBlock({ blockId: "final" }); - lastKnownBlock = { - header: { - hash: latestBlock.result.header.hash, - timestamp_nanosec: latestBlock.result.header.timestamp_nanosec - } - }; - (0, import_utils.lsSet)("block", lastKnownBlock); - } - nonce += 1; - (0, import_utils.lsSet)("nonce", nonce); - const blockHash = lastKnownBlock.header.hash; - const plainTransactionObj = { - signerId, - publicKey: publicKey2, - nonce, - receiverId, - blockHash, - actions: actions2 - }; - const txBytes = (0, import_utils.serializeTransaction)(plainTransactionObj); - const txHashBytes = (0, import_sha2.sha256)(txBytes); - const txHash58 = (0, import_utils.toBase58)(txHashBytes); - const signatureBase58 = (0, import_utils.signHash)(txHashBytes, privKey, { returnBase58: true }); - const signedTransactionBytes = (0, import_utils.serializeSignedTransaction)(plainTransactionObj, signatureBase58); - const signedTxBase64 = (0, import_utils.bytesToBase64)(signedTransactionBytes); - (0, import_state.updateTxHistory)({ - status: "Pending", - txId, - tx: plainTransactionObj, - signature: signatureBase58, - signedTxBase64, - txHash: txHash58, - finalState: false - }); - try { - return await sendTxToRpc(signedTxBase64, waitUntil, txId); - } catch (error) { - console.error("Error Sending Transaction:", error, plainTransactionObj, signedTxBase64); - } -}, "sendTx"); -const exp = { - utils: {}, - // we will map this in a moment, giving keys, for IDE hints - borsh: reExportAllUtils.exp.borsh, - borshSchema: reExportAllUtils.exp.borshSchema.getBorshSchema() -}; -for (const key in reExportAllUtils) { - exp.utils[key] = reExportAllUtils[key]; -} -const utils = exp.utils; -const state = {}; -for (const key in stateExports) { - state[key] = stateExports[key]; -} -const event = state["events"]; -delete state["events"]; -try { - if (typeof window !== "undefined") { - const url = new URL(window.location.href); - const accId = url.searchParams.get("account_id"); - const pubKey = url.searchParams.get("public_key"); - const errCode = url.searchParams.get("errorCode"); - const errMsg = url.searchParams.get("errorMessage"); - const decodedErrMsg = errMsg ? decodeURIComponent(errMsg) : null; - const txHashes = url.searchParams.get("transactionHashes"); - const txIds = url.searchParams.get("txIds"); - if (errCode || errMsg) { - console.warn(new Error(`Wallet raises: -code: ${errCode} -message: ${decodedErrMsg}`)); - } - if (accId && pubKey) { - if (pubKey === import_state._state.publicKey) { - (0, import_state.update)({ accountId: accId }); - } else { - if (authStatus() === "SignedIn") { - console.warn("Public key mismatch from wallet redirect", pubKey, import_state._state.publicKey); - } - url.searchParams.delete("public_key"); - } - } - if (txHashes || txIds) { - const hashArr = txHashes ? txHashes.split(",") : []; - const idArr = txIds ? txIds.split(",") : []; - if (idArr.length > hashArr.length) { - idArr.forEach((id) => { - (0, import_state.updateTxHistory)({ txId: id, status: "RejectedByUser", finalState: true }); - }); - } else if (idArr.length === hashArr.length) { - idArr.forEach((id, i) => { - (0, import_state.updateTxHistory)({ - txId: id, - status: "PendingGotTxHash", - txHash: hashArr[i], - finalState: false - }); - afterTxSent(id); - }); - } else { - console.error(new Error("Transaction hash mismatch from wallet redirect"), idArr, hashArr); - } - } - url.searchParams.delete("txIds"); - if (authStatus() === "SignedOut") { - url.searchParams.delete("errorCode"); - url.searchParams.delete("errorMessage"); - } - } -} catch (e) { - console.error("Error handling wallet redirect:", e); -} -const actions = { - functionCall: /* @__PURE__ */ __name(({ - methodName, - gas, - deposit, - args, - argsBase64 - }) => ({ - type: "FunctionCall", - methodName, - args, - argsBase64, - gas, - deposit - }), "functionCall"), - transfer: /* @__PURE__ */ __name((yoctoAmount) => ({ - type: "Transfer", - deposit: yoctoAmount - }), "transfer"), - stakeNEAR: /* @__PURE__ */ __name(({ amount, publicKey: publicKey2 }) => ({ - type: "Stake", - stake: amount, - publicKey: publicKey2 - }), "stakeNEAR"), - addFullAccessKey: /* @__PURE__ */ __name(({ publicKey: publicKey2 }) => ({ - type: "AddKey", - publicKey: publicKey2, - accessKey: { permission: "FullAccess" } - }), "addFullAccessKey"), - addLimitedAccessKey: /* @__PURE__ */ __name(({ - publicKey: publicKey2, - allowance, - accountId: accountId2, - methodNames - }) => ({ - type: "AddKey", - publicKey: publicKey2, - accessKey: { - permission: "FunctionCall", - allowance, - receiverId: accountId2, - methodNames - } - }), "addLimitedAccessKey"), - deleteKey: /* @__PURE__ */ __name(({ publicKey: publicKey2 }) => ({ - type: "DeleteKey", - publicKey: publicKey2 - }), "deleteKey"), - deleteAccount: /* @__PURE__ */ __name(({ beneficiaryId }) => ({ - type: "DeleteAccount", - beneficiaryId - }), "deleteAccount"), - createAccount: /* @__PURE__ */ __name(() => ({ - type: "CreateAccount" - }), "createAccount"), - deployContract: /* @__PURE__ */ __name(({ codeBase64 }) => ({ - type: "DeployContract", - codeBase64 - }), "deployContract") -}; -// Annotate the CommonJS export names for ESM import in node: -0 && (module.exports = { - MaxBlockDelayMs, - accountId, - actions, - afterTxSent, - authStatus, - config, - event, - exp, - generateTxId, - getPublicKeyForContract, - localTxHistory, - publicKey, - queryAccessKey, - queryAccount, - queryBlock, - queryTx, - requestSignIn, - selected, - sendRpc, - sendTx, - sendTxToRpc, - signOut, - state, - utils, - view, - withBlockId -}); -//# sourceMappingURL=near.cjs.map diff --git a/static/js-loaded-globally/nearjs/0.9.7/cjs/near.cjs.map b/static/js-loaded-globally/nearjs/0.9.7/cjs/near.cjs.map deleted file mode 100644 index 72b8090..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/cjs/near.cjs.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/near.ts"],"sourcesContent":["import Big from \"big.js\";\nimport {\n lsSet,\n lsGet,\n tryParseJson,\n fromBase64,\n toBase64,\n canSignWithLAK,\n toBase58,\n parseJsonFromBytes,\n signHash,\n publicKeyFromPrivate,\n privateKeyFromRandom,\n serializeTransaction,\n serializeSignedTransaction, bytesToBase64, PlainTransaction,\n} from \"@fastnear/utils\";\n\nimport {\n _adapter,\n _state,\n DEFAULT_NETWORK_ID,\n NETWORKS,\n getTxHistory,\n update,\n updateTxHistory,\n} from \"./state.js\";\n\nimport {\n getConfig,\n setConfig,\n resetTxHistory,\n} from \"./state.js\";\n\nimport { sha256 } from \"@noble/hashes/sha2\";\nimport * as reExportAllUtils from \"@fastnear/utils\";\nimport * as stateExports from \"./state.js\";\n\nBig.DP = 27;\nexport const MaxBlockDelayMs = 1000 * 60 * 60 * 6; // 6 hours\n\nexport interface AccessKeyWithError {\n result: {\n nonce: number;\n permission?: any;\n error?: string;\n }\n}\n\nexport interface WalletTxResult {\n url?: string;\n outcomes?: Array<{ transaction: { hash: string } }>;\n rejected?: boolean;\n error?: string;\n}\n\nexport interface BlockView {\n result: {\n header: {\n hash: string;\n timestamp_nanosec: string;\n }\n }\n}\n\n// The structure it's saved to in storage\nexport interface LastKnownBlock {\n header: {\n hash: string;\n timestamp_nanosec: string;\n }\n}\n\nexport function withBlockId(params: Record, blockId?: string) {\n if (blockId === \"final\" || blockId === \"optimistic\") {\n return { ...params, finality: blockId };\n }\n return blockId ? { ...params, block_id: blockId } : { ...params, finality: \"optimistic\" };\n}\n\nexport async function sendRpc(method: string, params: Record | any[]) {\n const config = getConfig();\n if (!config?.nodeUrl) {\n throw new Error(\"fastnear: getConfig() returned invalid config: missing nodeUrl.\");\n }\n const response = await fetch(config.nodeUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: `fastnear-${Date.now()}`,\n method,\n params,\n }),\n });\n const result = await response.json();\n if (result.error) {\n throw new Error(JSON.stringify(result.error));\n }\n return result;\n}\n\nexport function afterTxSent(txId: string) {\n const txHistory = getTxHistory();\n sendRpc(\"tx\", {\n tx_hash: txHistory[txId]?.txHash,\n sender_account_id: txHistory[txId]?.tx?.signerId,\n wait_until: \"EXECUTED_OPTIMISTIC\",\n })\n .then( result => {\n const successValue = result?.result?.status?.SuccessValue;\n updateTxHistory({\n txId,\n status: \"Executed\",\n result,\n successValue: successValue ? tryParseJson(fromBase64(successValue)) : undefined,\n finalState: true,\n });\n })\n .catch((error) => {\n updateTxHistory({\n txId,\n status: \"ErrorAfterIncluded\",\n error: tryParseJson(error.message) ?? error.message,\n finalState: true,\n });\n });\n}\n\nexport async function sendTxToRpc(signedTxBase64: string, waitUntil: string | undefined, txId: string) {\n // default to \"INCLUDED\"\n // see options: https://docs.near.org/api/rpc/transactions#tx-status-result\n waitUntil = waitUntil || \"INCLUDED\";\n\n try {\n const sendTxRes = await sendRpc(\"send_tx\", {\n signed_tx_base64: signedTxBase64,\n wait_until: waitUntil,\n });\n\n updateTxHistory({ txId, status: \"Included\", finalState: false });\n afterTxSent(txId);\n\n return sendTxRes;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n updateTxHistory({\n txId,\n status: \"Error\",\n error: tryParseJson(errorMessage) ?? errorMessage,\n finalState: false,\n });\n throw new Error(errorMessage);\n }\n}\n\nexport interface AccessKeyView {\n nonce: number;\n permission: any;\n}\n\n/**\n * Generates a mock transaction ID.\n *\n * This function creates a pseudo-unique transaction ID for testing or\n * non-production use. It combines the current timestamp with a\n * random component for uniqueness.\n *\n * **Note:** This is not cryptographically secure and should not be used\n * for actual transaction processing.\n *\n * @returns {string} A mock transaction ID in the format `tx-{timestamp}-{random}`\n */\nexport function generateTxId(): string {\n const randomPart = crypto.getRandomValues(new Uint32Array(2)).join(\"\");\n return `tx-${Date.now()}-${parseInt(randomPart, 10).toString(36)}`;\n}\n\nexport const accountId = () => _state.accountId;\nexport const publicKey = () => _state.publicKey;\n\nexport const config = (newConfig?: Record) => {\n const current = getConfig();\n if (newConfig) {\n if (newConfig.networkId && current.networkId !== newConfig.networkId) {\n setConfig(newConfig.networkId);\n update({ accountId: null, privateKey: null, lastWalletId: null });\n lsSet(\"block\", null);\n resetTxHistory();\n }\n setConfig({ ...getConfig(), ...newConfig });\n }\n return getConfig();\n};\n\nexport const authStatus = (): string | Record => {\n if (!_state.accountId) {\n return \"SignedOut\";\n }\n return \"SignedIn\";\n};\n\n// this is an intentional stub\n// and it's probably partially done, to help ease future features\n// for now we'll assume each web end user has one keypair in storage\n// for every contract they wish to interact with\n// later, it may be prudent to hold multiple, but until then this function\n// just returns the access key as if it were among others in the array.\n// we're pretending like we really thought about which access key we're returning\n// based on the opts argument. this allows us to fill this logic in later.\nexport const getPublicKeyForContract = (opts?: any) => {\n return publicKey();\n}\n\n// returns details on the selected:\n// network, wallet, and explorer details as well as\n// sending account, contract, and selected public key\nexport const selected = () => {\n const network = getConfig().networkId;\n const nodeUrl = getConfig().nodeUrl;\n const walletUrl = getConfig().walletUrl;\n const helperUrl = getConfig().helperUrl;\n const explorerUrl = getConfig().explorerUrl;\n\n const account = accountId();\n const contract = _state.accessKeyContractId;\n const publicKey = getPublicKeyForContract();\n\n return {\n network,\n nodeUrl,\n walletUrl,\n helperUrl,\n explorerUrl,\n account,\n contract,\n publicKey\n }\n}\n\nexport const requestSignIn = async ({ contractId }: { contractId: string }) => {\n const privateKey = privateKeyFromRandom();\n update({ accessKeyContractId: contractId, accountId: null, privateKey });\n const pubKey = publicKeyFromPrivate(privateKey);\n\n const result = await _adapter.signIn({\n networkId: getConfig().networkId,\n contractId,\n publicKey: pubKey,\n });\n\n if (result.error) {\n throw new Error(`Wallet error: ${result.error}`);\n }\n if (result.url) {\n if (typeof window !== \"undefined\") {\n setTimeout(() => {\n window.location.href = result.url;\n }, 100);\n }\n } else if (result.accountId) {\n update({ accountId: result.accountId });\n }\n};\n\nexport const view = async ({\n contractId,\n methodName,\n args,\n argsBase64,\n blockId,\n }: {\n contractId: string;\n methodName: string;\n args?: any;\n argsBase64?: string;\n blockId?: string;\n}) => {\n const encodedArgs = argsBase64 || (args ? toBase64(JSON.stringify(args)) : \"\");\n const queryResult = await sendRpc(\n \"query\",\n withBlockId(\n {\n request_type: \"call_function\",\n account_id: contractId,\n method_name: methodName,\n args_base64: encodedArgs,\n },\n blockId\n )\n );\n\n return parseJsonFromBytes(queryResult.result.result);\n};\n\nexport const queryAccount = async ({\n accountId,\n blockId,\n }: {\n accountId: string;\n blockId?: string;\n}) => {\n return sendRpc(\n \"query\",\n withBlockId({ request_type: \"view_account\", account_id: accountId }, blockId)\n );\n};\n\nexport const queryBlock = async ({ blockId }: { blockId?: string }): Promise => {\n return sendRpc(\"block\", withBlockId({}, blockId));\n};\n\nexport const queryAccessKey = async ({\n accountId,\n publicKey,\n blockId,\n }: {\n accountId: string;\n publicKey: string;\n blockId?: string;\n}): Promise => {\n return sendRpc(\n \"query\",\n withBlockId(\n { request_type: \"view_access_key\", account_id: accountId, public_key: publicKey },\n blockId\n )\n );\n};\n\nexport const queryTx = async ({ txHash, accountId }: { txHash: string; accountId: string }) => {\n return sendRpc(\"tx\", [txHash, accountId]);\n};\n\nexport const localTxHistory = () => {\n return getTxHistory();\n};\n\nexport const signOut = () => {\n update({ accountId: null, privateKey: null, contractId: null });\n setConfig(NETWORKS[DEFAULT_NETWORK_ID]);\n};\n\nexport const sendTx = async ({\n receiverId,\n actions,\n waitUntil,\n }: {\n receiverId: string;\n actions: any[];\n waitUntil?: string;\n}) => {\n const signerId = _state.accountId;\n if (!signerId) throw new Error(\"Must sign in\");\n\n const publicKey = _state.publicKey ?? \"\";\n const privKey = _state.privateKey;\n // this generates a mock transaction ID so we can keep track of each tx\n const txId = generateTxId();\n\n if (!privKey || receiverId !== _state.accessKeyContractId || !canSignWithLAK(actions)) {\n const jsonTx = { signerId, receiverId, actions };\n updateTxHistory({ status: \"Pending\", txId, tx: jsonTx, finalState: false });\n\n const url = new URL(typeof window !== \"undefined\" ? window.location.href : \"\");\n url.searchParams.set(\"txIds\", txId);\n\n // preserve existing url params\n const existingParams = new URLSearchParams(window.location.search);\n existingParams.forEach((value, key) => {\n if (!url.searchParams.has(key)) {\n url.searchParams.set(key, value);\n }\n });\n\n // we're wanting to preserve URL params that we send in\n // but make sure we're not feeding back error params\n // from a previous failure\n\n url.searchParams.delete(\"errorCode\");\n url.searchParams.delete(\"errorMessage\");\n\n try {\n const result: WalletTxResult = await _adapter.sendTransactions({\n transactions: [jsonTx],\n callbackUrl: url.toString(),\n });\n\n if (result.url) {\n if (typeof window !== \"undefined\") {\n setTimeout(() => {\n window.location.href = result.url!;\n }, 100);\n }\n } else if (result.outcomes?.length) {\n result.outcomes.forEach((r) =>\n updateTxHistory({\n txId,\n status: \"Executed\",\n result: r,\n txHash: r.transaction.hash,\n finalState: true,\n })\n );\n } else if (result.rejected) {\n updateTxHistory({ txId, status: \"RejectedByUser\", finalState: true });\n } else if (result.error) {\n updateTxHistory({\n txId,\n status: \"Error\",\n error: tryParseJson(result.error),\n finalState: true,\n });\n }\n\n return result;\n } catch (err) {\n console.error('fastnear: error sending tx using adapter:', err)\n updateTxHistory({\n txId,\n status: \"Error\",\n error: tryParseJson((err as Error).message),\n finalState: true,\n });\n\n return Promise.reject(err);\n }\n }\n\n let nonce = lsGet(\"nonce\") as number | null;\n if (nonce == null) {\n const accessKey = await queryAccessKey({ accountId: signerId, publicKey: publicKey });\n if (accessKey.result.error) {\n throw new Error(`Access key error: ${accessKey.result.error} when attempting to get nonce for ${signerId} for public key ${publicKey}`);\n }\n nonce = accessKey.result.nonce;\n lsSet(\"nonce\", nonce);\n }\n\n let lastKnownBlock = lsGet(\"block\") as LastKnownBlock | null;\n if (\n !lastKnownBlock ||\n parseFloat(lastKnownBlock.header.timestamp_nanosec) / 1e6 + MaxBlockDelayMs < Date.now()\n ) {\n const latestBlock = await queryBlock({ blockId: \"final\" });\n lastKnownBlock = {\n header: {\n hash: latestBlock.result.header.hash,\n timestamp_nanosec: latestBlock.result.header.timestamp_nanosec,\n },\n };\n lsSet(\"block\", lastKnownBlock);\n }\n\n nonce += 1;\n lsSet(\"nonce\", nonce);\n\n const blockHash = lastKnownBlock.header.hash;\n\n const plainTransactionObj: PlainTransaction = {\n signerId,\n publicKey,\n nonce,\n receiverId,\n blockHash,\n actions,\n };\n\n const txBytes = serializeTransaction(plainTransactionObj);\n const txHashBytes = sha256(txBytes);\n const txHash58 = toBase58(txHashBytes);\n\n const signatureBase58 = signHash(txHashBytes, privKey, { returnBase58: true });\n const signedTransactionBytes = serializeSignedTransaction(plainTransactionObj, signatureBase58);\n const signedTxBase64 = bytesToBase64(signedTransactionBytes);\n\n updateTxHistory({\n status: \"Pending\",\n txId,\n tx: plainTransactionObj,\n signature: signatureBase58,\n signedTxBase64,\n txHash: txHash58,\n finalState: false,\n });\n\n try {\n return await sendTxToRpc(signedTxBase64, waitUntil, txId);\n } catch (error) {\n console.error(\"Error Sending Transaction:\", error, plainTransactionObj, signedTxBase64);\n }\n};\n\n// exports\nexport const exp = {\n utils: {}, // we will map this in a moment, giving keys, for IDE hints\n borsh: reExportAllUtils.exp.borsh,\n borshSchema: reExportAllUtils.exp.borshSchema.getBorshSchema(),\n};\n\nfor (const key in reExportAllUtils) {\n exp.utils[key] = reExportAllUtils[key];\n}\n\n// devx\nexport const utils = exp.utils;\n\nexport const state = {}\n\nfor (const key in stateExports) {\n state[key] = stateExports[key];\n}\n\n// devx\n\nexport const event = state['events'];\ndelete state['events'];\n\n// Wallet redirect handling\ntry {\n if (typeof window !== \"undefined\") {\n const url = new URL(window.location.href);\n const accId = url.searchParams.get(\"account_id\");\n const pubKey = url.searchParams.get(\"public_key\");\n const errCode = url.searchParams.get(\"errorCode\");\n const errMsg = url.searchParams.get(\"errorMessage\");\n const decodedErrMsg = errMsg ? decodeURIComponent(errMsg) : null;\n\n const txHashes = url.searchParams.get(\"transactionHashes\");\n const txIds = url.searchParams.get(\"txIds\");\n\n if (errCode || errMsg) {\n console.warn(new Error(`Wallet raises:\\ncode: ${errCode}\\nmessage: ${decodedErrMsg}`));\n }\n\n if (accId && pubKey) {\n if (pubKey === _state.publicKey) {\n update({ accountId: accId });\n } else {\n // it's possible the end user has a URL param that's old. we'll remove the public_key param\n // if logged out, no need to throw warning\n if (authStatus() === \"SignedIn\") {\n console.warn(\"Public key mismatch from wallet redirect\", pubKey, _state.publicKey);\n }\n url.searchParams.delete(\"public_key\");\n }\n }\n\n if (txHashes || txIds) {\n const hashArr = txHashes ? txHashes.split(\",\") : [];\n const idArr = txIds ? txIds.split(\",\") : [];\n if (idArr.length > hashArr.length) {\n idArr.forEach((id) => {\n updateTxHistory({ txId: id, status: \"RejectedByUser\", finalState: true });\n });\n } else if (idArr.length === hashArr.length) {\n idArr.forEach((id, i) => {\n updateTxHistory({\n txId: id,\n status: \"PendingGotTxHash\",\n txHash: hashArr[i],\n finalState: false,\n });\n afterTxSent(id);\n });\n } else {\n console.error(new Error(\"Transaction hash mismatch from wallet redirect\"), idArr, hashArr);\n }\n }\n\n // we can consider removing these, but want to be careful because\n // it can be helpful for a dev to have a URL they can debug with\n // we won't want to remove information\n\n // pretty sure txIds can go, especially if you can tell it's been more than 5 minutes or something\n // public_key sometimes confuses it, so this might only be needed when adding a new access key\n // and perhaps once we've confirmed that the transaction hashes are getting saved to storage\n // (not sure about that section of code) then we can get rid of the transactionHashes, too\n\n url.searchParams.delete(\"txIds\");\n if (authStatus() === \"SignedOut\") {\n url.searchParams.delete(\"errorCode\");\n url.searchParams.delete(\"errorMessage\");\n }\n // ^ we've decided these ones make sense to keep\n\n // I'd like to keep this for posterity. for a bit.\n // url.searchParams.delete(\"account_id\");\n // url.searchParams.delete(\"public_key\");\n\n // url.searchParams.delete(\"all_keys\");\n // url.searchParams.delete(\"transactionHashes\");\n // window.history.replaceState({}, \"\", url.toString());\n }\n} catch (e) {\n console.error(\"Error handling wallet redirect:\", e);\n}\n\n// action helpers\nexport const actions = {\n functionCall: ({\n methodName,\n gas,\n deposit,\n args,\n argsBase64,\n }: {\n methodName: string;\n gas?: string;\n deposit?: string;\n args?: Record;\n argsBase64?: string;\n }) => ({\n type: \"FunctionCall\",\n methodName,\n args,\n argsBase64,\n gas,\n deposit,\n }),\n\n transfer: (yoctoAmount: string) => ({\n type: \"Transfer\",\n deposit: yoctoAmount,\n }),\n\n stakeNEAR: ({amount, publicKey}: { amount: string; publicKey: string }) => ({\n type: \"Stake\",\n stake: amount,\n publicKey,\n }),\n\n addFullAccessKey: ({publicKey}: { publicKey: string }) => ({\n type: \"AddKey\",\n publicKey: publicKey,\n accessKey: {permission: \"FullAccess\"},\n }),\n\n addLimitedAccessKey: ({\n publicKey,\n allowance,\n accountId,\n methodNames,\n }: {\n publicKey: string;\n allowance: string;\n accountId: string;\n methodNames: string[];\n }) => ({\n type: \"AddKey\",\n publicKey: publicKey,\n accessKey: {\n permission: \"FunctionCall\",\n allowance,\n receiverId: accountId,\n methodNames,\n },\n }),\n\n deleteKey: ({publicKey}: { publicKey: string }) => ({\n type: \"DeleteKey\",\n publicKey,\n }),\n\n deleteAccount: ({beneficiaryId}: { beneficiaryId: string }) => ({\n type: \"DeleteAccount\",\n beneficiaryId,\n }),\n\n createAccount: () => ({\n type: \"CreateAccount\",\n }),\n\n deployContract: ({codeBase64}: { codeBase64: string }) => ({\n type: \"DeployContract\",\n codeBase64,\n }),\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAgB;AAChB,mBAcO;AAEP,mBAQO;AAEP,IAAAA,gBAIO;AAEP,kBAAuB;AACvB,uBAAkC;AAClC,mBAA8B;AAE9B,WAAAC,QAAI,KAAK;AACF,MAAM,kBAAkB,MAAO,KAAK,KAAK;AAkCzC,SAAS,YAAY,QAA6B,SAAkB;AACzE,MAAI,YAAY,WAAW,YAAY,cAAc;AACnD,WAAO,EAAE,GAAG,QAAQ,UAAU,QAAQ;AAAA,EACxC;AACA,SAAO,UAAU,EAAE,GAAG,QAAQ,UAAU,QAAQ,IAAI,EAAE,GAAG,QAAQ,UAAU,aAAa;AAC1F;AALgB;AAOhB,eAAsB,QAAQ,QAAgB,QAAqC;AACjF,QAAMC,cAAS,yBAAU;AACzB,MAAI,CAACA,SAAQ,SAAS;AACpB,UAAM,IAAI,MAAM,iEAAiE;AAAA,EACnF;AACA,QAAM,WAAW,MAAM,MAAMA,QAAO,SAAS;AAAA,IAC3C,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB,SAAS;AAAA,MACT,IAAI,YAAY,KAAK,IAAI,CAAC;AAAA,MAC1B;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACD,QAAM,SAAS,MAAM,SAAS,KAAK;AACnC,MAAI,OAAO,OAAO;AAChB,UAAM,IAAI,MAAM,KAAK,UAAU,OAAO,KAAK,CAAC;AAAA,EAC9C;AACA,SAAO;AACT;AApBsB;AAsBf,SAAS,YAAY,MAAc;AACxC,QAAM,gBAAY,2BAAa;AAC/B,UAAQ,MAAM;AAAA,IACZ,SAAS,UAAU,IAAI,GAAG;AAAA,IAC1B,mBAAmB,UAAU,IAAI,GAAG,IAAI;AAAA,IACxC,YAAY;AAAA,EACd,CAAC,EACE,KAAM,YAAU;AACf,UAAM,eAAe,QAAQ,QAAQ,QAAQ;AAC7C,sCAAgB;AAAA,MACd;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,cAAc,mBAAe,+BAAa,yBAAW,YAAY,CAAC,IAAI;AAAA,MACtE,YAAY;AAAA,IACd,CAAC;AAAA,EACH,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,sCAAgB;AAAA,MACd;AAAA,MACA,QAAQ;AAAA,MACR,WAAO,2BAAa,MAAM,OAAO,KAAK,MAAM;AAAA,MAC5C,YAAY;AAAA,IACd,CAAC;AAAA,EACH,CAAC;AACL;AAzBgB;AA2BhB,eAAsB,YAAY,gBAAwB,WAA+B,MAAc;AAGrG,cAAY,aAAa;AAEzB,MAAI;AACF,UAAM,YAAY,MAAM,QAAQ,WAAW;AAAA,MACzC,kBAAkB;AAAA,MAClB,YAAY;AAAA,IACd,CAAC;AAED,sCAAgB,EAAE,MAAM,QAAQ,YAAY,YAAY,MAAM,CAAC;AAC/D,gBAAY,IAAI;AAEhB,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU;AAC9D,sCAAgB;AAAA,MACd;AAAA,MACA,QAAQ;AAAA,MACR,WAAO,2BAAa,YAAY,KAAK;AAAA,MACrC,YAAY;AAAA,IACd,CAAC;AACD,UAAM,IAAI,MAAM,YAAY;AAAA,EAC9B;AACF;AAzBsB;AA4Cf,SAAS,eAAuB;AACrC,QAAM,aAAa,OAAO,gBAAgB,IAAI,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE;AACrE,SAAO,MAAM,KAAK,IAAI,CAAC,IAAI,SAAS,YAAY,EAAE,EAAE,SAAS,EAAE,CAAC;AAClE;AAHgB;AAKT,MAAM,YAAY,6BAAM,oBAAO,WAAb;AAClB,MAAM,YAAY,6BAAM,oBAAO,WAAb;AAElB,MAAM,SAAS,wBAAC,cAAoC;AACzD,QAAM,cAAU,yBAAU;AAC1B,MAAI,WAAW;AACb,QAAI,UAAU,aAAa,QAAQ,cAAc,UAAU,WAAW;AACpE,mCAAU,UAAU,SAAS;AAC7B,+BAAO,EAAE,WAAW,MAAM,YAAY,MAAM,cAAc,KAAK,CAAC;AAChE,8BAAM,SAAS,IAAI;AACnB,wCAAe;AAAA,IACjB;AACA,iCAAU,EAAE,OAAG,yBAAU,GAAG,GAAG,UAAU,CAAC;AAAA,EAC5C;AACA,aAAO,yBAAU;AACnB,GAZsB;AAcf,MAAM,aAAa,6BAAoC;AAC5D,MAAI,CAAC,oBAAO,WAAW;AACrB,WAAO;AAAA,EACT;AACA,SAAO;AACT,GAL0B;AAenB,MAAM,0BAA0B,wBAAC,SAAe;AACrD,SAAO,UAAU;AACnB,GAFuC;AAOhC,MAAM,WAAW,6BAAM;AAC5B,QAAM,cAAU,yBAAU,EAAE;AAC5B,QAAM,cAAU,yBAAU,EAAE;AAC5B,QAAM,gBAAY,yBAAU,EAAE;AAC9B,QAAM,gBAAY,yBAAU,EAAE;AAC9B,QAAM,kBAAc,yBAAU,EAAE;AAEhC,QAAM,UAAU,UAAU;AAC1B,QAAM,WAAW,oBAAO;AACxB,QAAMC,aAAY,wBAAwB;AAE1C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAAA;AAAA,EACF;AACF,GArBwB;AAuBjB,MAAM,gBAAgB,8BAAO,EAAE,WAAW,MAA8B;AAC7E,QAAM,iBAAa,mCAAqB;AACxC,2BAAO,EAAE,qBAAqB,YAAY,WAAW,MAAM,WAAW,CAAC;AACvE,QAAM,aAAS,mCAAqB,UAAU;AAE9C,QAAM,SAAS,MAAM,sBAAS,OAAO;AAAA,IACnC,eAAW,yBAAU,EAAE;AAAA,IACvB;AAAA,IACA,WAAW;AAAA,EACb,CAAC;AAED,MAAI,OAAO,OAAO;AAChB,UAAM,IAAI,MAAM,iBAAiB,OAAO,KAAK,EAAE;AAAA,EACjD;AACA,MAAI,OAAO,KAAK;AACd,QAAI,OAAO,WAAW,aAAa;AACjC,iBAAW,MAAM;AACf,eAAO,SAAS,OAAO,OAAO;AAAA,MAChC,GAAG,GAAG;AAAA,IACR;AAAA,EACF,WAAW,OAAO,WAAW;AAC3B,6BAAO,EAAE,WAAW,OAAO,UAAU,CAAC;AAAA,EACxC;AACF,GAvB6B;AAyBtB,MAAM,OAAO,8BAAO;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAMrB;AACJ,QAAM,cAAc,eAAe,WAAO,uBAAS,KAAK,UAAU,IAAI,CAAC,IAAI;AAC3E,QAAM,cAAc,MAAM;AAAA,IACxB;AAAA,IACA;AAAA,MACE;AAAA,QACE,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,aAAa;AAAA,QACb,aAAa;AAAA,MACf;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,aAAO,iCAAmB,YAAY,OAAO,MAAM;AACrD,GA5BoB;AA8Bb,MAAM,eAAe,8BAAO;AAAA,EACH,WAAAC;AAAA,EACA;AACF,MAGxB;AACJ,SAAO;AAAA,IACL;AAAA,IACA,YAAY,EAAE,cAAc,gBAAgB,YAAYA,WAAU,GAAG,OAAO;AAAA,EAC9E;AACF,GAX4B;AAarB,MAAM,aAAa,8BAAO,EAAE,QAAQ,MAAgD;AACzF,SAAO,QAAQ,SAAS,YAAY,CAAC,GAAG,OAAO,CAAC;AAClD,GAF0B;AAInB,MAAM,iBAAiB,8BAAO;AAAA,EACH,WAAAA;AAAA,EACA,WAAAD;AAAA,EACA;AACF,MAIG;AACjC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,EAAE,cAAc,mBAAmB,YAAYC,YAAW,YAAYD,WAAU;AAAA,MAChF;AAAA,IACF;AAAA,EACF;AACF,GAhB8B;AAkBvB,MAAM,UAAU,8BAAO,EAAE,QAAQ,WAAAC,WAAU,MAA6C;AAC7F,SAAO,QAAQ,MAAM,CAAC,QAAQA,UAAS,CAAC;AAC1C,GAFuB;AAIhB,MAAM,iBAAiB,6BAAM;AAClC,aAAO,2BAAa;AACtB,GAF8B;AAIvB,MAAM,UAAU,6BAAM;AAC3B,2BAAO,EAAE,WAAW,MAAM,YAAY,MAAM,YAAY,KAAK,CAAC;AAC9D,+BAAU,sBAAS,+BAAkB,CAAC;AACxC,GAHuB;AAKhB,MAAM,SAAS,8BAAO;AAAA,EACE;AAAA,EACA,SAAAC;AAAA,EACA;AACF,MAIvB;AACJ,QAAM,WAAW,oBAAO;AACxB,MAAI,CAAC,SAAU,OAAM,IAAI,MAAM,cAAc;AAE7C,QAAMF,aAAY,oBAAO,aAAa;AACtC,QAAM,UAAU,oBAAO;AAEvB,QAAM,OAAO,aAAa;AAE1B,MAAI,CAAC,WAAW,eAAe,oBAAO,uBAAuB,KAAC,6BAAeE,QAAO,GAAG;AACrF,UAAM,SAAS,EAAE,UAAU,YAAY,SAAAA,SAAQ;AAC/C,sCAAgB,EAAE,QAAQ,WAAW,MAAM,IAAI,QAAQ,YAAY,MAAM,CAAC;AAE1E,UAAM,MAAM,IAAI,IAAI,OAAO,WAAW,cAAc,OAAO,SAAS,OAAO,EAAE;AAC7E,QAAI,aAAa,IAAI,SAAS,IAAI;AAGlC,UAAM,iBAAiB,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACjE,mBAAe,QAAQ,CAAC,OAAO,QAAQ;AACrC,UAAI,CAAC,IAAI,aAAa,IAAI,GAAG,GAAG;AAC9B,YAAI,aAAa,IAAI,KAAK,KAAK;AAAA,MACjC;AAAA,IACF,CAAC;AAMD,QAAI,aAAa,OAAO,WAAW;AACnC,QAAI,aAAa,OAAO,cAAc;AAEtC,QAAI;AACF,YAAM,SAAyB,MAAM,sBAAS,iBAAiB;AAAA,QAC7D,cAAc,CAAC,MAAM;AAAA,QACrB,aAAa,IAAI,SAAS;AAAA,MAC5B,CAAC;AAED,UAAI,OAAO,KAAK;AACd,YAAI,OAAO,WAAW,aAAa;AACjC,qBAAW,MAAM;AACf,mBAAO,SAAS,OAAO,OAAO;AAAA,UAChC,GAAG,GAAG;AAAA,QACR;AAAA,MACF,WAAW,OAAO,UAAU,QAAQ;AAClC,eAAO,SAAS;AAAA,UAAQ,CAAC,UACvB,8BAAgB;AAAA,YACd;AAAA,YACA,QAAQ;AAAA,YACR,QAAQ;AAAA,YACR,QAAQ,EAAE,YAAY;AAAA,YACtB,YAAY;AAAA,UACd,CAAC;AAAA,QACH;AAAA,MACF,WAAW,OAAO,UAAU;AAC1B,0CAAgB,EAAE,MAAM,QAAQ,kBAAkB,YAAY,KAAK,CAAC;AAAA,MACtE,WAAW,OAAO,OAAO;AACvB,0CAAgB;AAAA,UACd;AAAA,UACA,QAAQ;AAAA,UACR,WAAO,2BAAa,OAAO,KAAK;AAAA,UAChC,YAAY;AAAA,QACd,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,cAAQ,MAAM,6CAA6C,GAAG;AAC9D,wCAAgB;AAAA,QACd;AAAA,QACA,QAAQ;AAAA,QACR,WAAO,2BAAc,IAAc,OAAO;AAAA,QAC1C,YAAY;AAAA,MACd,CAAC;AAED,aAAO,QAAQ,OAAO,GAAG;AAAA,IAC3B;AAAA,EACF;AAEA,MAAI,YAAQ,oBAAM,OAAO;AACzB,MAAI,SAAS,MAAM;AACjB,UAAM,YAAY,MAAM,eAAe,EAAE,WAAW,UAAU,WAAWF,WAAU,CAAC;AACpF,QAAI,UAAU,OAAO,OAAO;AAC1B,YAAM,IAAI,MAAM,qBAAqB,UAAU,OAAO,KAAK,qCAAqC,QAAQ,mBAAmBA,UAAS,EAAE;AAAA,IACxI;AACA,YAAQ,UAAU,OAAO;AACzB,4BAAM,SAAS,KAAK;AAAA,EACtB;AAEA,MAAI,qBAAiB,oBAAM,OAAO;AAClC,MACE,CAAC,kBACD,WAAW,eAAe,OAAO,iBAAiB,IAAI,MAAM,kBAAkB,KAAK,IAAI,GACvF;AACA,UAAM,cAAc,MAAM,WAAW,EAAE,SAAS,QAAQ,CAAC;AACzD,qBAAiB;AAAA,MACf,QAAQ;AAAA,QACN,MAAM,YAAY,OAAO,OAAO;AAAA,QAChC,mBAAmB,YAAY,OAAO,OAAO;AAAA,MAC/C;AAAA,IACF;AACA,4BAAM,SAAS,cAAc;AAAA,EAC/B;AAEA,WAAS;AACT,0BAAM,SAAS,KAAK;AAEpB,QAAM,YAAY,eAAe,OAAO;AAExC,QAAM,sBAAwC;AAAA,IAC5C;AAAA,IACA,WAAAA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAAE;AAAA,EACF;AAEA,QAAM,cAAU,mCAAqB,mBAAmB;AACxD,QAAM,kBAAc,oBAAO,OAAO;AAClC,QAAM,eAAW,uBAAS,WAAW;AAErC,QAAM,sBAAkB,uBAAS,aAAa,SAAS,EAAE,cAAc,KAAK,CAAC;AAC7E,QAAM,6BAAyB,yCAA2B,qBAAqB,eAAe;AAC9F,QAAM,qBAAiB,4BAAc,sBAAsB;AAE3D,oCAAgB;AAAA,IACd,QAAQ;AAAA,IACR;AAAA,IACA,IAAI;AAAA,IACJ,WAAW;AAAA,IACX;AAAA,IACA,QAAQ;AAAA,IACR,YAAY;AAAA,EACd,CAAC;AAED,MAAI;AACF,WAAO,MAAM,YAAY,gBAAgB,WAAW,IAAI;AAAA,EAC1D,SAAS,OAAO;AACd,YAAQ,MAAM,8BAA8B,OAAO,qBAAqB,cAAc;AAAA,EACxF;AACF,GApJsB;AAuJf,MAAM,MAAM;AAAA,EACjB,OAAO,CAAC;AAAA;AAAA,EACR,OAAO,iBAAiB,IAAI;AAAA,EAC5B,aAAa,iBAAiB,IAAI,YAAY,eAAe;AAC/D;AAEA,WAAW,OAAO,kBAAkB;AAClC,MAAI,MAAM,GAAG,IAAI,iBAAiB,GAAG;AACvC;AAGO,MAAM,QAAQ,IAAI;AAElB,MAAM,QAAQ,CAAC;AAEtB,WAAW,OAAO,cAAc;AAC9B,QAAM,GAAG,IAAI,aAAa,GAAG;AAC/B;AAIO,MAAM,QAAQ,MAAM,QAAQ;AACnC,OAAO,MAAM,QAAQ;AAGrB,IAAI;AACF,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,UAAM,QAAQ,IAAI,aAAa,IAAI,YAAY;AAC/C,UAAM,SAAS,IAAI,aAAa,IAAI,YAAY;AAChD,UAAM,UAAU,IAAI,aAAa,IAAI,WAAW;AAChD,UAAM,SAAS,IAAI,aAAa,IAAI,cAAc;AAClD,UAAM,gBAAgB,SAAS,mBAAmB,MAAM,IAAI;AAE5D,UAAM,WAAW,IAAI,aAAa,IAAI,mBAAmB;AACzD,UAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;AAE1C,QAAI,WAAW,QAAQ;AACrB,cAAQ,KAAK,IAAI,MAAM;AAAA,QAAyB,OAAO;AAAA,WAAc,aAAa,EAAE,CAAC;AAAA,IACvF;AAEA,QAAI,SAAS,QAAQ;AACnB,UAAI,WAAW,oBAAO,WAAW;AAC/B,iCAAO,EAAE,WAAW,MAAM,CAAC;AAAA,MAC7B,OAAO;AAGL,YAAI,WAAW,MAAM,YAAY;AAC/B,kBAAQ,KAAK,4CAA4C,QAAQ,oBAAO,SAAS;AAAA,QACnF;AACA,YAAI,aAAa,OAAO,YAAY;AAAA,MACtC;AAAA,IACF;AAEA,QAAI,YAAY,OAAO;AACrB,YAAM,UAAU,WAAW,SAAS,MAAM,GAAG,IAAI,CAAC;AAClD,YAAM,QAAQ,QAAQ,MAAM,MAAM,GAAG,IAAI,CAAC;AAC1C,UAAI,MAAM,SAAS,QAAQ,QAAQ;AACjC,cAAM,QAAQ,CAAC,OAAO;AACpB,4CAAgB,EAAE,MAAM,IAAI,QAAQ,kBAAkB,YAAY,KAAK,CAAC;AAAA,QAC1E,CAAC;AAAA,MACH,WAAW,MAAM,WAAW,QAAQ,QAAQ;AAC1C,cAAM,QAAQ,CAAC,IAAI,MAAM;AACvB,4CAAgB;AAAA,YACd,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,QAAQ,QAAQ,CAAC;AAAA,YACjB,YAAY;AAAA,UACd,CAAC;AACD,sBAAY,EAAE;AAAA,QAChB,CAAC;AAAA,MACH,OAAO;AACL,gBAAQ,MAAM,IAAI,MAAM,gDAAgD,GAAG,OAAO,OAAO;AAAA,MAC3F;AAAA,IACF;AAWA,QAAI,aAAa,OAAO,OAAO;AAC/B,QAAI,WAAW,MAAM,aAAa;AAChC,UAAI,aAAa,OAAO,WAAW;AACnC,UAAI,aAAa,OAAO,cAAc;AAAA,IACxC;AAAA,EAUF;AACF,SAAS,GAAG;AACV,UAAQ,MAAM,mCAAmC,CAAC;AACpD;AAGO,MAAM,UAAU;AAAA,EACrB,cAAc,wBAAC;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,OAMR;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAnBc;AAAA,EAqBd,UAAU,wBAAC,iBAAyB;AAAA,IAClC,MAAM;AAAA,IACN,SAAS;AAAA,EACX,IAHU;AAAA,EAKV,WAAW,wBAAC,EAAC,QAAQ,WAAAF,WAAS,OAA8C;AAAA,IAC1E,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAAA;AAAA,EACF,IAJW;AAAA,EAMX,kBAAkB,wBAAC,EAAC,WAAAA,WAAS,OAA8B;AAAA,IACzD,MAAM;AAAA,IACN,WAAWA;AAAA,IACX,WAAW,EAAC,YAAY,aAAY;AAAA,EACtC,IAJkB;AAAA,EAMlB,qBAAqB,wBAAC;AAAA,IACE,WAAAA;AAAA,IACA;AAAA,IACA,WAAAC;AAAA,IACA;AAAA,EACF,OAKf;AAAA,IACL,MAAM;AAAA,IACN,WAAWD;AAAA,IACX,WAAW;AAAA,MACT,YAAY;AAAA,MACZ;AAAA,MACA,YAAYC;AAAA,MACZ;AAAA,IACF;AAAA,EACF,IAnBqB;AAAA,EAqBrB,WAAW,wBAAC,EAAC,WAAAD,WAAS,OAA8B;AAAA,IAClD,MAAM;AAAA,IACN,WAAAA;AAAA,EACF,IAHW;AAAA,EAKX,eAAe,wBAAC,EAAC,cAAa,OAAkC;AAAA,IAC9D,MAAM;AAAA,IACN;AAAA,EACF,IAHe;AAAA,EAKf,eAAe,8BAAO;AAAA,IACpB,MAAM;AAAA,EACR,IAFe;AAAA,EAIf,gBAAgB,wBAAC,EAAC,WAAU,OAA+B;AAAA,IACzD,MAAM;AAAA,IACN;AAAA,EACF,IAHgB;AAIlB;","names":["import_state","Big","config","publicKey","accountId","actions"]} \ No newline at end of file diff --git a/static/js-loaded-globally/nearjs/0.9.7/cjs/state.cjs b/static/js-loaded-globally/nearjs/0.9.7/cjs/state.cjs deleted file mode 100644 index 7b81417..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/cjs/state.cjs +++ /dev/null @@ -1,207 +0,0 @@ -/* ⋈ 🏃🏻💨 FastNear API - CJS (@fastnear/api version 0.9.7) */ -/* https://www.npmjs.com/package/@fastnear/api/v/0.9.7 */ -"use strict"; -var __defProp = Object.defineProperty; -var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -var __getOwnPropNames = Object.getOwnPropertyNames; -var __hasOwnProp = Object.prototype.hasOwnProperty; -var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); -var __export = (target, all) => { - for (var name in all) - __defProp(target, name, { get: all[name], enumerable: true }); -}; -var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); - } - return to; -}; -var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); -var state_exports = {}; -__export(state_exports, { - DEFAULT_NETWORK_ID: () => DEFAULT_NETWORK_ID, - NETWORKS: () => NETWORKS, - WIDGET_URL: () => WIDGET_URL, - _adapter: () => _adapter, - _config: () => _config, - _state: () => _state, - _txHistory: () => _txHistory, - _unbroadcastedEvents: () => _unbroadcastedEvents, - events: () => events, - getConfig: () => getConfig, - getTxHistory: () => getTxHistory, - getWalletAdapterState: () => getWalletAdapterState, - onAdapterStateUpdate: () => onAdapterStateUpdate, - resetTxHistory: () => resetTxHistory, - setConfig: () => setConfig, - update: () => update, - updateTxHistory: () => updateTxHistory -}); -module.exports = __toCommonJS(state_exports); -var import_utils = require("@fastnear/utils"); -var import_wallet_adapter = require("@fastnear/wallet-adapter"); -const WIDGET_URL = "https://js.cdn.fastnear.com"; -const DEFAULT_NETWORK_ID = "mainnet"; -const NETWORKS = { - testnet: { - networkId: "testnet", - nodeUrl: "https://rpc.testnet.fastnear.com/" - }, - mainnet: { - networkId: "mainnet", - nodeUrl: "https://rpc.mainnet.fastnear.com/" - } -}; -let _config = (0, import_utils.lsGet)("config") || { - ...NETWORKS[DEFAULT_NETWORK_ID] -}; -let _state = (0, import_utils.lsGet)("state") || {}; -const onAdapterStateUpdate = /* @__PURE__ */ __name((state) => { - console.log("Adapter state update:", state); - const { accountId, lastWalletId, privateKey } = state; - update({ - accountId: accountId || void 0, - lastWalletId: lastWalletId || void 0, - ...privateKey ? { privateKey } : {} - }); -}, "onAdapterStateUpdate"); -const getWalletAdapterState = /* @__PURE__ */ __name(() => { - return { - publicKey: _state.publicKey, - accountId: _state.accountId, - lastWalletId: _state.lastWalletId, - networkId: _config.networkId - }; -}, "getWalletAdapterState"); -let _adapter = new import_wallet_adapter.WalletAdapter({ - onStateUpdate: onAdapterStateUpdate, - lastState: getWalletAdapterState(), - widgetUrl: WIDGET_URL -}); -try { - _state.publicKey = _state.privateKey ? (0, import_utils.publicKeyFromPrivate)(_state.privateKey) : null; -} catch (e) { - console.error("Error parsing private key:", e); - _state.privateKey = null; - (0, import_utils.lsSet)("nonce", null); -} -let _txHistory = (0, import_utils.lsGet)("txHistory") || {}; -const _unbroadcastedEvents = { - account: [], - tx: [] -}; -const events = { - _eventListeners: { - account: /* @__PURE__ */ new Set(), - tx: /* @__PURE__ */ new Set() - }, - notifyAccountListeners: /* @__PURE__ */ __name((accountId) => { - if (events._eventListeners.account.size === 0) { - _unbroadcastedEvents.account.push(accountId); - return; - } - events._eventListeners.account.forEach((callback) => { - try { - callback(accountId); - } catch (e) { - console.error(e); - } - }); - }, "notifyAccountListeners"), - notifyTxListeners: /* @__PURE__ */ __name((tx) => { - if (events._eventListeners.tx.size === 0) { - _unbroadcastedEvents.tx.push(tx); - return; - } - events._eventListeners.tx.forEach((callback) => { - try { - callback(tx); - } catch (e) { - console.error(e); - } - }); - }, "notifyTxListeners"), - onAccount: /* @__PURE__ */ __name((callback) => { - events._eventListeners.account.add(callback); - if (_unbroadcastedEvents.account.length > 0) { - const accountEvent = _unbroadcastedEvents.account; - _unbroadcastedEvents.account = []; - accountEvent.forEach(events.notifyAccountListeners); - } - }, "onAccount"), - onTx: /* @__PURE__ */ __name((callback) => { - events._eventListeners.tx.add(callback); - if (_unbroadcastedEvents.tx.length > 0) { - const txEvent = _unbroadcastedEvents.tx; - _unbroadcastedEvents.tx = []; - txEvent.forEach(events.notifyTxListeners); - } - }, "onTx") -}; -const update = /* @__PURE__ */ __name((newState) => { - const oldState = _state; - _state = { ..._state, ...newState }; - (0, import_utils.lsSet)("state", { - accountId: _state.accountId, - privateKey: _state.privateKey, - lastWalletId: _state.lastWalletId, - accessKeyContractId: _state.accessKeyContractId - }); - if (newState.hasOwnProperty("privateKey") && newState.privateKey !== oldState.privateKey) { - _state.publicKey = newState.privateKey ? (0, import_utils.publicKeyFromPrivate)(newState.privateKey) : null; - (0, import_utils.lsSet)("nonce", null); - } - if (newState.accountId !== oldState.accountId) { - events.notifyAccountListeners(newState.accountId); - } - if (newState.hasOwnProperty("lastWalletId") && newState.lastWalletId !== oldState.lastWalletId || newState.hasOwnProperty("accountId") && newState.accountId !== oldState.accountId || newState.hasOwnProperty("privateKey") && newState.privateKey !== oldState.privateKey) { - _adapter.setState(getWalletAdapterState()); - } -}, "update"); -const updateTxHistory = /* @__PURE__ */ __name((txStatus) => { - const txId = txStatus.txId; - _txHistory[txId] = { - ..._txHistory[txId] || {}, - ...txStatus, - updateTimestamp: Date.now() - }; - (0, import_utils.lsSet)("txHistory", _txHistory); - events.notifyTxListeners(_txHistory[txId]); -}, "updateTxHistory"); -const getConfig = /* @__PURE__ */ __name(() => { - return _config; -}, "getConfig"); -const getTxHistory = /* @__PURE__ */ __name(() => { - return _txHistory; -}, "getTxHistory"); -const setConfig = /* @__PURE__ */ __name((newConf) => { - _config = { ...NETWORKS[newConf.networkId], ...newConf }; - (0, import_utils.lsSet)("config", _config); -}, "setConfig"); -const resetTxHistory = /* @__PURE__ */ __name(() => { - _txHistory = {}; - (0, import_utils.lsSet)("txHistory", _txHistory); -}, "resetTxHistory"); -// Annotate the CommonJS export names for ESM import in node: -0 && (module.exports = { - DEFAULT_NETWORK_ID, - NETWORKS, - WIDGET_URL, - _adapter, - _config, - _state, - _txHistory, - _unbroadcastedEvents, - events, - getConfig, - getTxHistory, - getWalletAdapterState, - onAdapterStateUpdate, - resetTxHistory, - setConfig, - update, - updateTxHistory -}); -//# sourceMappingURL=state.cjs.map diff --git a/static/js-loaded-globally/nearjs/0.9.7/cjs/state.cjs.map b/static/js-loaded-globally/nearjs/0.9.7/cjs/state.cjs.map deleted file mode 100644 index 4f7cfdb..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/cjs/state.cjs.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/state.ts"],"sourcesContent":["import {\n lsSet,\n lsGet,\n publicKeyFromPrivate,\n} from \"@fastnear/utils\";\nimport {WalletAdapter} from \"@fastnear/wallet-adapter\";\n\nexport const WIDGET_URL = \"https://js.cdn.fastnear.com\";\n\nexport const DEFAULT_NETWORK_ID = \"mainnet\";\nexport const NETWORKS = {\n testnet: {\n networkId: \"testnet\",\n nodeUrl: \"https://rpc.testnet.fastnear.com/\",\n },\n mainnet: {\n networkId: \"mainnet\",\n nodeUrl: \"https://rpc.mainnet.fastnear.com/\",\n },\n};\n\nexport interface NetworkConfig {\n networkId: string;\n nodeUrl?: string;\n walletUrl?: string;\n helperUrl?: string;\n explorerUrl?: string;\n\n [key: string]: any;\n}\n\nexport interface AppState {\n accountId?: string | null;\n privateKey?: string | null;\n lastWalletId?: string | null;\n publicKey?: string | null;\n accessKeyContractId?: string | null;\n\n [key: string]: any;\n}\n\nexport interface TxStatus {\n txId: string;\n updateTimestamp?: number;\n\n [key: string]: any;\n}\n\nexport type TxHistory = Record;\n\nexport interface EventListeners {\n account: Set<(accountId: string) => void>;\n tx: Set<(tx: TxStatus) => void>;\n}\n\nexport interface UnbroadcastedEvents {\n account: string[];\n tx: TxStatus[];\n}\n\nexport interface WalletAdapterState {\n publicKey?: string | null;\n privateKey?: string | null;\n accountId?: string | null;\n lastWalletId?: string | null;\n networkId: string;\n}\n\n\n// Load config from localStorage or default to the network's config\nexport let _config: NetworkConfig = lsGet(\"config\") || {\n ...NETWORKS[DEFAULT_NETWORK_ID]\n};\n\n// Load application state from localStorage\nexport let _state: AppState = lsGet(\"state\") || {};\n\n// Triggered by the wallet adapter\nexport const onAdapterStateUpdate = (state: WalletAdapterState) => {\n console.log(\"Adapter state update:\", state);\n const { accountId, lastWalletId, privateKey } = state;\n update({\n accountId: accountId || undefined,\n lastWalletId: lastWalletId || undefined,\n ...(privateKey ? { privateKey } : {}),\n });\n}\n\nexport const getWalletAdapterState = (): WalletAdapterState => {\n return {\n publicKey: _state.publicKey,\n accountId: _state.accountId,\n lastWalletId: _state.lastWalletId,\n networkId: _config.networkId,\n };\n}\n\n// We can create an adapter instance here\nexport let _adapter = new WalletAdapter({\n onStateUpdate: onAdapterStateUpdate,\n lastState: getWalletAdapterState(),\n widgetUrl: WIDGET_URL,\n});\n\n// Attempt to set publicKey if we have a privateKey\ntry {\n _state.publicKey = _state.privateKey\n ? publicKeyFromPrivate(_state.privateKey)\n : null;\n} catch (e) {\n console.error(\"Error parsing private key:\", e);\n _state.privateKey = null;\n lsSet(\"nonce\", null);\n}\n\n// Transaction history\nexport let _txHistory: TxHistory = lsGet(\"txHistory\") || {};\n\n\nexport const _unbroadcastedEvents: UnbroadcastedEvents = {\n account: [],\n tx: [],\n};\n\n// events / listeners\nexport const events = {\n _eventListeners: {\n account: new Set(),\n tx: new Set(),\n },\n\n notifyAccountListeners: (accountId: string) => {\n if (events._eventListeners.account.size === 0) {\n _unbroadcastedEvents.account.push(accountId);\n return;\n }\n events._eventListeners.account.forEach((callback: any) => {\n try {\n callback(accountId);\n } catch (e) {\n console.error(e);\n }\n });\n },\n\n notifyTxListeners: (tx: TxStatus) => {\n if (events._eventListeners.tx.size === 0) {\n _unbroadcastedEvents.tx.push(tx);\n return;\n }\n events._eventListeners.tx.forEach((callback: any) => {\n try {\n callback(tx);\n } catch (e) {\n console.error(e);\n }\n });\n },\n\n onAccount: (callback: (accountId: string) => void) => {\n events._eventListeners.account.add(callback);\n if (_unbroadcastedEvents.account.length > 0) {\n const accountEvent = _unbroadcastedEvents.account;\n _unbroadcastedEvents.account = [];\n accountEvent.forEach(events.notifyAccountListeners);\n }\n },\n\n onTx: (callback: (tx: TxStatus) => void): void => {\n events._eventListeners.tx.add(callback);\n if (_unbroadcastedEvents.tx.length > 0) {\n const txEvent = _unbroadcastedEvents.tx;\n _unbroadcastedEvents.tx = [];\n txEvent.forEach(events.notifyTxListeners);\n }\n }\n}\n\n// Mutators\n// @todo: in favor of limiting when out of alpha\n// but haven't given it enough thought ~ mike\nexport const update = (newState: Partial) => {\n const oldState = _state;\n _state = {..._state, ...newState};\n\n lsSet(\"state\", {\n accountId: _state.accountId,\n privateKey: _state.privateKey,\n lastWalletId: _state.lastWalletId,\n accessKeyContractId: _state.accessKeyContractId,\n });\n\n if (\n newState.hasOwnProperty(\"privateKey\") &&\n newState.privateKey !== oldState.privateKey\n ) {\n _state.publicKey = newState.privateKey\n ? publicKeyFromPrivate(newState.privateKey as string)\n : null;\n lsSet(\"nonce\", null);\n }\n\n if (newState.accountId !== oldState.accountId) {\n events.notifyAccountListeners(newState.accountId as string);\n }\n\n if (\n (newState.hasOwnProperty(\"lastWalletId\") &&\n newState.lastWalletId !== oldState.lastWalletId) ||\n (newState.hasOwnProperty(\"accountId\") &&\n newState.accountId !== oldState.accountId) ||\n (newState.hasOwnProperty(\"privateKey\") &&\n newState.privateKey !== oldState.privateKey)\n ) {\n _adapter.setState(getWalletAdapterState());\n }\n}\n\nexport const updateTxHistory = (txStatus: TxStatus) => {\n const txId = txStatus.txId;\n _txHistory[txId] = {\n ...(_txHistory[txId] || {}),\n ...txStatus,\n updateTimestamp: Date.now(),\n };\n lsSet(\"txHistory\", _txHistory);\n events.notifyTxListeners(_txHistory[txId]);\n}\n\nexport const getConfig = (): NetworkConfig => {\n return _config;\n}\n\nexport const getTxHistory = (): TxHistory => {\n return _txHistory;\n}\n\n// Exposed \"write\" functions\nexport const setConfig = (newConf: NetworkConfig): void => {\n _config = { ...NETWORKS[newConf.networkId], ...newConf };\n lsSet(\"config\", _config);\n}\n\nexport const resetTxHistory = (): void => {\n _txHistory = {};\n lsSet(\"txHistory\", _txHistory);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAIO;AACP,4BAA4B;AAErB,MAAM,aAAa;AAEnB,MAAM,qBAAqB;AAC3B,MAAM,WAAW;AAAA,EACtB,SAAS;AAAA,IACP,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AAAA,EACA,SAAS;AAAA,IACP,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AACF;AAmDO,IAAI,cAAyB,oBAAM,QAAQ,KAAK;AAAA,EACrD,GAAG,SAAS,kBAAkB;AAChC;AAGO,IAAI,aAAmB,oBAAM,OAAO,KAAK,CAAC;AAG1C,MAAM,uBAAuB,wBAAC,UAA8B;AACjE,UAAQ,IAAI,yBAAyB,KAAK;AAC1C,QAAM,EAAE,WAAW,cAAc,WAAW,IAAI;AAChD,SAAO;AAAA,IACL,WAAW,aAAa;AAAA,IACxB,cAAc,gBAAgB;AAAA,IAC9B,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,EACrC,CAAC;AACH,GARoC;AAU7B,MAAM,wBAAwB,6BAA0B;AAC7D,SAAO;AAAA,IACL,WAAW,OAAO;AAAA,IAClB,WAAW,OAAO;AAAA,IAClB,cAAc,OAAO;AAAA,IACrB,WAAW,QAAQ;AAAA,EACrB;AACF,GAPqC;AAU9B,IAAI,WAAW,IAAI,oCAAc;AAAA,EACtC,eAAe;AAAA,EACf,WAAW,sBAAsB;AAAA,EACjC,WAAW;AACb,CAAC;AAGD,IAAI;AACF,SAAO,YAAY,OAAO,iBACtB,mCAAqB,OAAO,UAAU,IACtC;AACN,SAAS,GAAG;AACV,UAAQ,MAAM,8BAA8B,CAAC;AAC7C,SAAO,aAAa;AACpB,0BAAM,SAAS,IAAI;AACrB;AAGO,IAAI,iBAAwB,oBAAM,WAAW,KAAK,CAAC;AAGnD,MAAM,uBAA4C;AAAA,EACvD,SAAS,CAAC;AAAA,EACV,IAAI,CAAC;AACP;AAGO,MAAM,SAAS;AAAA,EACpB,iBAAiB;AAAA,IACf,SAAS,oBAAI,IAAI;AAAA,IACjB,IAAI,oBAAI,IAAI;AAAA,EACd;AAAA,EAEA,wBAAwB,wBAAC,cAAsB;AAC7C,QAAI,OAAO,gBAAgB,QAAQ,SAAS,GAAG;AAC7C,2BAAqB,QAAQ,KAAK,SAAS;AAC3C;AAAA,IACF;AACA,WAAO,gBAAgB,QAAQ,QAAQ,CAAC,aAAkB;AACxD,UAAI;AACF,iBAAS,SAAS;AAAA,MACpB,SAAS,GAAG;AACV,gBAAQ,MAAM,CAAC;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH,GAZwB;AAAA,EAcxB,mBAAmB,wBAAC,OAAiB;AACnC,QAAI,OAAO,gBAAgB,GAAG,SAAS,GAAG;AACxC,2BAAqB,GAAG,KAAK,EAAE;AAC/B;AAAA,IACF;AACA,WAAO,gBAAgB,GAAG,QAAQ,CAAC,aAAkB;AACnD,UAAI;AACF,iBAAS,EAAE;AAAA,MACb,SAAS,GAAG;AACV,gBAAQ,MAAM,CAAC;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH,GAZmB;AAAA,EAcnB,WAAW,wBAAC,aAA0C;AACpD,WAAO,gBAAgB,QAAQ,IAAI,QAAQ;AAC3C,QAAI,qBAAqB,QAAQ,SAAS,GAAG;AAC3C,YAAM,eAAe,qBAAqB;AAC1C,2BAAqB,UAAU,CAAC;AAChC,mBAAa,QAAQ,OAAO,sBAAsB;AAAA,IACpD;AAAA,EACF,GAPW;AAAA,EASX,MAAM,wBAAC,aAA2C;AAChD,WAAO,gBAAgB,GAAG,IAAI,QAAQ;AACtC,QAAI,qBAAqB,GAAG,SAAS,GAAG;AACtC,YAAM,UAAU,qBAAqB;AACrC,2BAAqB,KAAK,CAAC;AAC3B,cAAQ,QAAQ,OAAO,iBAAiB;AAAA,IAC1C;AAAA,EACF,GAPM;AAQR;AAKO,MAAM,SAAS,wBAAC,aAAgC;AACrD,QAAM,WAAW;AACjB,WAAS,EAAC,GAAG,QAAQ,GAAG,SAAQ;AAEhC,0BAAM,SAAS;AAAA,IACb,WAAW,OAAO;AAAA,IAClB,YAAY,OAAO;AAAA,IACnB,cAAc,OAAO;AAAA,IACrB,qBAAqB,OAAO;AAAA,EAC9B,CAAC;AAED,MACE,SAAS,eAAe,YAAY,KACpC,SAAS,eAAe,SAAS,YACjC;AACA,WAAO,YAAY,SAAS,iBACxB,mCAAqB,SAAS,UAAoB,IAClD;AACJ,4BAAM,SAAS,IAAI;AAAA,EACrB;AAEA,MAAI,SAAS,cAAc,SAAS,WAAW;AAC7C,WAAO,uBAAuB,SAAS,SAAmB;AAAA,EAC5D;AAEA,MACG,SAAS,eAAe,cAAc,KACrC,SAAS,iBAAiB,SAAS,gBACpC,SAAS,eAAe,WAAW,KAClC,SAAS,cAAc,SAAS,aACjC,SAAS,eAAe,YAAY,KACnC,SAAS,eAAe,SAAS,YACnC;AACA,aAAS,SAAS,sBAAsB,CAAC;AAAA,EAC3C;AACF,GAnCsB;AAqCf,MAAM,kBAAkB,wBAAC,aAAuB;AACrD,QAAM,OAAO,SAAS;AACtB,aAAW,IAAI,IAAI;AAAA,IACjB,GAAI,WAAW,IAAI,KAAK,CAAC;AAAA,IACzB,GAAG;AAAA,IACH,iBAAiB,KAAK,IAAI;AAAA,EAC5B;AACA,0BAAM,aAAa,UAAU;AAC7B,SAAO,kBAAkB,WAAW,IAAI,CAAC;AAC3C,GAT+B;AAWxB,MAAM,YAAY,6BAAqB;AAC5C,SAAO;AACT,GAFyB;AAIlB,MAAM,eAAe,6BAAiB;AAC3C,SAAO;AACT,GAF4B;AAKrB,MAAM,YAAY,wBAAC,YAAiC;AACzD,YAAU,EAAE,GAAG,SAAS,QAAQ,SAAS,GAAG,GAAG,QAAQ;AACvD,0BAAM,UAAU,OAAO;AACzB,GAHyB;AAKlB,MAAM,iBAAiB,6BAAY;AACxC,eAAa,CAAC;AACd,0BAAM,aAAa,UAAU;AAC/B,GAH8B;","names":[]} \ No newline at end of file diff --git a/static/js-loaded-globally/nearjs/0.9.7/esm/index.d.ts b/static/js-loaded-globally/nearjs/0.9.7/esm/index.d.ts deleted file mode 100644 index 77f036e..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/esm/index.d.ts +++ /dev/null @@ -1,233 +0,0 @@ -import * as borsh from 'borsh'; - -interface NetworkConfig { - networkId: string; - nodeUrl?: string; - walletUrl?: string; - helperUrl?: string; - explorerUrl?: string; - [key: string]: any; -} -interface TxStatus { - txId: string; - updateTimestamp?: number; - [key: string]: any; -} -type TxHistory = Record; - -declare const MaxBlockDelayMs: number; -interface AccessKeyWithError { - result: { - nonce: number; - permission?: any; - error?: string; - }; -} -interface WalletTxResult { - url?: string; - outcomes?: Array<{ - transaction: { - hash: string; - }; - }>; - rejected?: boolean; - error?: string; -} -interface BlockView { - result: { - header: { - hash: string; - timestamp_nanosec: string; - }; - }; -} -interface LastKnownBlock { - header: { - hash: string; - timestamp_nanosec: string; - }; -} -declare function withBlockId(params: Record, blockId?: string): { - finality: string; -} | { - block_id: string; -}; -declare function sendRpc(method: string, params: Record | any[]): Promise; -declare function afterTxSent(txId: string): void; -declare function sendTxToRpc(signedTxBase64: string, waitUntil: string | undefined, txId: string): Promise; -interface AccessKeyView { - nonce: number; - permission: any; -} -/** - * Generates a mock transaction ID. - * - * This function creates a pseudo-unique transaction ID for testing or - * non-production use. It combines the current timestamp with a - * random component for uniqueness. - * - * **Note:** This is not cryptographically secure and should not be used - * for actual transaction processing. - * - * @returns {string} A mock transaction ID in the format `tx-{timestamp}-{random}` - */ -declare function generateTxId(): string; -declare const accountId: () => string | null | undefined; -declare const publicKey: () => string | null | undefined; -declare const config: (newConfig?: Record) => NetworkConfig; -declare const authStatus: () => string | Record; -declare const getPublicKeyForContract: (opts?: any) => string | null | undefined; -declare const selected: () => { - network: string; - nodeUrl: string | undefined; - walletUrl: string | undefined; - helperUrl: string | undefined; - explorerUrl: string | undefined; - account: string | null | undefined; - contract: string | null | undefined; - publicKey: string | null | undefined; -}; -declare const requestSignIn: ({ contractId }: { - contractId: string; -}) => Promise; -declare const view: ({ contractId, methodName, args, argsBase64, blockId, }: { - contractId: string; - methodName: string; - args?: any; - argsBase64?: string; - blockId?: string; -}) => Promise; -declare const queryAccount: ({ accountId, blockId, }: { - accountId: string; - blockId?: string; -}) => Promise; -declare const queryBlock: ({ blockId }: { - blockId?: string; -}) => Promise; -declare const queryAccessKey: ({ accountId, publicKey, blockId, }: { - accountId: string; - publicKey: string; - blockId?: string; -}) => Promise; -declare const queryTx: ({ txHash, accountId }: { - txHash: string; - accountId: string; -}) => Promise; -declare const localTxHistory: () => TxHistory; -declare const signOut: () => void; -declare const sendTx: ({ receiverId, actions, waitUntil, }: { - receiverId: string; - actions: any[]; - waitUntil?: string; -}) => Promise; -declare const exp: { - utils: {}; - borsh: { - serialize: typeof borsh.serialize; - deserialize: typeof borsh.deserialize; - }; - borshSchema: { - Ed25519Signature: borsh.Schema; - Secp256k1Signature: borsh.Schema; - Signature: borsh.Schema; - Ed25519Data: borsh.Schema; - Secp256k1Data: borsh.Schema; - PublicKey: borsh.Schema; - FunctionCallPermission: borsh.Schema; - FullAccessPermission: borsh.Schema; - AccessKeyPermission: borsh.Schema; - AccessKey: borsh.Schema; - CreateAccount: borsh.Schema; - DeployContract: borsh.Schema; - FunctionCall: borsh.Schema; - Transfer: borsh.Schema; - Stake: borsh.Schema; - AddKey: borsh.Schema; - DeleteKey: borsh.Schema; - DeleteAccount: borsh.Schema; - ClassicAction: borsh.Schema; - DelegateAction: borsh.Schema; - SignedDelegate: borsh.Schema; - Action: borsh.Schema; - Transaction: borsh.Schema; - SignedTransaction: borsh.Schema; - }; -}; -declare const utils: {}; -declare const state: {}; -declare const event: any; -declare const actions: { - functionCall: ({ methodName, gas, deposit, args, argsBase64, }: { - methodName: string; - gas?: string; - deposit?: string; - args?: Record; - argsBase64?: string; - }) => { - type: string; - methodName: string; - args: Record | undefined; - argsBase64: string | undefined; - gas: string | undefined; - deposit: string | undefined; - }; - transfer: (yoctoAmount: string) => { - type: string; - deposit: string; - }; - stakeNEAR: ({ amount, publicKey }: { - amount: string; - publicKey: string; - }) => { - type: string; - stake: string; - publicKey: string; - }; - addFullAccessKey: ({ publicKey }: { - publicKey: string; - }) => { - type: string; - publicKey: string; - accessKey: { - permission: string; - }; - }; - addLimitedAccessKey: ({ publicKey, allowance, accountId, methodNames, }: { - publicKey: string; - allowance: string; - accountId: string; - methodNames: string[]; - }) => { - type: string; - publicKey: string; - accessKey: { - permission: string; - allowance: string; - receiverId: string; - methodNames: string[]; - }; - }; - deleteKey: ({ publicKey }: { - publicKey: string; - }) => { - type: string; - publicKey: string; - }; - deleteAccount: ({ beneficiaryId }: { - beneficiaryId: string; - }) => { - type: string; - beneficiaryId: string; - }; - createAccount: () => { - type: string; - }; - deployContract: ({ codeBase64 }: { - codeBase64: string; - }) => { - type: string; - codeBase64: string; - }; -}; - -export { type AccessKeyView, type AccessKeyWithError, type BlockView, type LastKnownBlock, MaxBlockDelayMs, type WalletTxResult, accountId, actions, afterTxSent, authStatus, config, event, exp, generateTxId, getPublicKeyForContract, localTxHistory, publicKey, queryAccessKey, queryAccount, queryBlock, queryTx, requestSignIn, selected, sendRpc, sendTx, sendTxToRpc, signOut, state, utils, view, withBlockId }; diff --git a/static/js-loaded-globally/nearjs/0.9.7/esm/index.js b/static/js-loaded-globally/nearjs/0.9.7/esm/index.js deleted file mode 100644 index d84199b..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/esm/index.js +++ /dev/null @@ -1,4 +0,0 @@ -/* ⋈ 🏃🏻💨 FastNear API - ESM (@fastnear/api version 0.9.7) */ -/* https://www.npmjs.com/package/@fastnear/api/v/0.9.7 */ -export * from "./near.js"; -//# sourceMappingURL=index.js.map diff --git a/static/js-loaded-globally/nearjs/0.9.7/esm/index.js.map b/static/js-loaded-globally/nearjs/0.9.7/esm/index.js.map deleted file mode 100644 index 6162288..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/esm/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/index.ts"],"sourcesContent":["// See tsup.config.ts for additional banner/footer js\nexport * from \"./near.js\";\n"],"mappings":";;AACA,cAAc;","names":[]} \ No newline at end of file diff --git a/static/js-loaded-globally/nearjs/0.9.7/esm/near.js b/static/js-loaded-globally/nearjs/0.9.7/esm/near.js deleted file mode 100644 index 7c793d4..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/esm/near.js +++ /dev/null @@ -1,522 +0,0 @@ -/* ⋈ 🏃🏻💨 FastNear API - ESM (@fastnear/api version 0.9.7) */ -/* https://www.npmjs.com/package/@fastnear/api/v/0.9.7 */ -var __defProp = Object.defineProperty; -var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); -import Big from "big.js"; -import { - lsSet, - lsGet, - tryParseJson, - fromBase64, - toBase64, - canSignWithLAK, - toBase58, - parseJsonFromBytes, - signHash, - publicKeyFromPrivate, - privateKeyFromRandom, - serializeTransaction, - serializeSignedTransaction, - bytesToBase64 -} from "@fastnear/utils"; -import { - _adapter, - _state, - DEFAULT_NETWORK_ID, - NETWORKS, - getTxHistory, - update, - updateTxHistory -} from "./state.js"; -import { - getConfig, - setConfig, - resetTxHistory -} from "./state.js"; -import { sha256 } from "@noble/hashes/sha2"; -import * as reExportAllUtils from "@fastnear/utils"; -import * as stateExports from "./state.js"; -Big.DP = 27; -const MaxBlockDelayMs = 1e3 * 60 * 60 * 6; -function withBlockId(params, blockId) { - if (blockId === "final" || blockId === "optimistic") { - return { ...params, finality: blockId }; - } - return blockId ? { ...params, block_id: blockId } : { ...params, finality: "optimistic" }; -} -__name(withBlockId, "withBlockId"); -async function sendRpc(method, params) { - const config2 = getConfig(); - if (!config2?.nodeUrl) { - throw new Error("fastnear: getConfig() returned invalid config: missing nodeUrl."); - } - const response = await fetch(config2.nodeUrl, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: `fastnear-${Date.now()}`, - method, - params - }) - }); - const result = await response.json(); - if (result.error) { - throw new Error(JSON.stringify(result.error)); - } - return result; -} -__name(sendRpc, "sendRpc"); -function afterTxSent(txId) { - const txHistory = getTxHistory(); - sendRpc("tx", { - tx_hash: txHistory[txId]?.txHash, - sender_account_id: txHistory[txId]?.tx?.signerId, - wait_until: "EXECUTED_OPTIMISTIC" - }).then((result) => { - const successValue = result?.result?.status?.SuccessValue; - updateTxHistory({ - txId, - status: "Executed", - result, - successValue: successValue ? tryParseJson(fromBase64(successValue)) : void 0, - finalState: true - }); - }).catch((error) => { - updateTxHistory({ - txId, - status: "ErrorAfterIncluded", - error: tryParseJson(error.message) ?? error.message, - finalState: true - }); - }); -} -__name(afterTxSent, "afterTxSent"); -async function sendTxToRpc(signedTxBase64, waitUntil, txId) { - waitUntil = waitUntil || "INCLUDED"; - try { - const sendTxRes = await sendRpc("send_tx", { - signed_tx_base64: signedTxBase64, - wait_until: waitUntil - }); - updateTxHistory({ txId, status: "Included", finalState: false }); - afterTxSent(txId); - return sendTxRes; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - updateTxHistory({ - txId, - status: "Error", - error: tryParseJson(errorMessage) ?? errorMessage, - finalState: false - }); - throw new Error(errorMessage); - } -} -__name(sendTxToRpc, "sendTxToRpc"); -function generateTxId() { - const randomPart = crypto.getRandomValues(new Uint32Array(2)).join(""); - return `tx-${Date.now()}-${parseInt(randomPart, 10).toString(36)}`; -} -__name(generateTxId, "generateTxId"); -const accountId = /* @__PURE__ */ __name(() => _state.accountId, "accountId"); -const publicKey = /* @__PURE__ */ __name(() => _state.publicKey, "publicKey"); -const config = /* @__PURE__ */ __name((newConfig) => { - const current = getConfig(); - if (newConfig) { - if (newConfig.networkId && current.networkId !== newConfig.networkId) { - setConfig(newConfig.networkId); - update({ accountId: null, privateKey: null, lastWalletId: null }); - lsSet("block", null); - resetTxHistory(); - } - setConfig({ ...getConfig(), ...newConfig }); - } - return getConfig(); -}, "config"); -const authStatus = /* @__PURE__ */ __name(() => { - if (!_state.accountId) { - return "SignedOut"; - } - return "SignedIn"; -}, "authStatus"); -const getPublicKeyForContract = /* @__PURE__ */ __name((opts) => { - return publicKey(); -}, "getPublicKeyForContract"); -const selected = /* @__PURE__ */ __name(() => { - const network = getConfig().networkId; - const nodeUrl = getConfig().nodeUrl; - const walletUrl = getConfig().walletUrl; - const helperUrl = getConfig().helperUrl; - const explorerUrl = getConfig().explorerUrl; - const account = accountId(); - const contract = _state.accessKeyContractId; - const publicKey2 = getPublicKeyForContract(); - return { - network, - nodeUrl, - walletUrl, - helperUrl, - explorerUrl, - account, - contract, - publicKey: publicKey2 - }; -}, "selected"); -const requestSignIn = /* @__PURE__ */ __name(async ({ contractId }) => { - const privateKey = privateKeyFromRandom(); - update({ accessKeyContractId: contractId, accountId: null, privateKey }); - const pubKey = publicKeyFromPrivate(privateKey); - const result = await _adapter.signIn({ - networkId: getConfig().networkId, - contractId, - publicKey: pubKey - }); - if (result.error) { - throw new Error(`Wallet error: ${result.error}`); - } - if (result.url) { - if (typeof window !== "undefined") { - setTimeout(() => { - window.location.href = result.url; - }, 100); - } - } else if (result.accountId) { - update({ accountId: result.accountId }); - } -}, "requestSignIn"); -const view = /* @__PURE__ */ __name(async ({ - contractId, - methodName, - args, - argsBase64, - blockId -}) => { - const encodedArgs = argsBase64 || (args ? toBase64(JSON.stringify(args)) : ""); - const queryResult = await sendRpc( - "query", - withBlockId( - { - request_type: "call_function", - account_id: contractId, - method_name: methodName, - args_base64: encodedArgs - }, - blockId - ) - ); - return parseJsonFromBytes(queryResult.result.result); -}, "view"); -const queryAccount = /* @__PURE__ */ __name(async ({ - accountId: accountId2, - blockId -}) => { - return sendRpc( - "query", - withBlockId({ request_type: "view_account", account_id: accountId2 }, blockId) - ); -}, "queryAccount"); -const queryBlock = /* @__PURE__ */ __name(async ({ blockId }) => { - return sendRpc("block", withBlockId({}, blockId)); -}, "queryBlock"); -const queryAccessKey = /* @__PURE__ */ __name(async ({ - accountId: accountId2, - publicKey: publicKey2, - blockId -}) => { - return sendRpc( - "query", - withBlockId( - { request_type: "view_access_key", account_id: accountId2, public_key: publicKey2 }, - blockId - ) - ); -}, "queryAccessKey"); -const queryTx = /* @__PURE__ */ __name(async ({ txHash, accountId: accountId2 }) => { - return sendRpc("tx", [txHash, accountId2]); -}, "queryTx"); -const localTxHistory = /* @__PURE__ */ __name(() => { - return getTxHistory(); -}, "localTxHistory"); -const signOut = /* @__PURE__ */ __name(() => { - update({ accountId: null, privateKey: null, contractId: null }); - setConfig(NETWORKS[DEFAULT_NETWORK_ID]); -}, "signOut"); -const sendTx = /* @__PURE__ */ __name(async ({ - receiverId, - actions: actions2, - waitUntil -}) => { - const signerId = _state.accountId; - if (!signerId) throw new Error("Must sign in"); - const publicKey2 = _state.publicKey ?? ""; - const privKey = _state.privateKey; - const txId = generateTxId(); - if (!privKey || receiverId !== _state.accessKeyContractId || !canSignWithLAK(actions2)) { - const jsonTx = { signerId, receiverId, actions: actions2 }; - updateTxHistory({ status: "Pending", txId, tx: jsonTx, finalState: false }); - const url = new URL(typeof window !== "undefined" ? window.location.href : ""); - url.searchParams.set("txIds", txId); - const existingParams = new URLSearchParams(window.location.search); - existingParams.forEach((value, key) => { - if (!url.searchParams.has(key)) { - url.searchParams.set(key, value); - } - }); - url.searchParams.delete("errorCode"); - url.searchParams.delete("errorMessage"); - try { - const result = await _adapter.sendTransactions({ - transactions: [jsonTx], - callbackUrl: url.toString() - }); - if (result.url) { - if (typeof window !== "undefined") { - setTimeout(() => { - window.location.href = result.url; - }, 100); - } - } else if (result.outcomes?.length) { - result.outcomes.forEach( - (r) => updateTxHistory({ - txId, - status: "Executed", - result: r, - txHash: r.transaction.hash, - finalState: true - }) - ); - } else if (result.rejected) { - updateTxHistory({ txId, status: "RejectedByUser", finalState: true }); - } else if (result.error) { - updateTxHistory({ - txId, - status: "Error", - error: tryParseJson(result.error), - finalState: true - }); - } - return result; - } catch (err) { - console.error("fastnear: error sending tx using adapter:", err); - updateTxHistory({ - txId, - status: "Error", - error: tryParseJson(err.message), - finalState: true - }); - return Promise.reject(err); - } - } - let nonce = lsGet("nonce"); - if (nonce == null) { - const accessKey = await queryAccessKey({ accountId: signerId, publicKey: publicKey2 }); - if (accessKey.result.error) { - throw new Error(`Access key error: ${accessKey.result.error} when attempting to get nonce for ${signerId} for public key ${publicKey2}`); - } - nonce = accessKey.result.nonce; - lsSet("nonce", nonce); - } - let lastKnownBlock = lsGet("block"); - if (!lastKnownBlock || parseFloat(lastKnownBlock.header.timestamp_nanosec) / 1e6 + MaxBlockDelayMs < Date.now()) { - const latestBlock = await queryBlock({ blockId: "final" }); - lastKnownBlock = { - header: { - hash: latestBlock.result.header.hash, - timestamp_nanosec: latestBlock.result.header.timestamp_nanosec - } - }; - lsSet("block", lastKnownBlock); - } - nonce += 1; - lsSet("nonce", nonce); - const blockHash = lastKnownBlock.header.hash; - const plainTransactionObj = { - signerId, - publicKey: publicKey2, - nonce, - receiverId, - blockHash, - actions: actions2 - }; - const txBytes = serializeTransaction(plainTransactionObj); - const txHashBytes = sha256(txBytes); - const txHash58 = toBase58(txHashBytes); - const signatureBase58 = signHash(txHashBytes, privKey, { returnBase58: true }); - const signedTransactionBytes = serializeSignedTransaction(plainTransactionObj, signatureBase58); - const signedTxBase64 = bytesToBase64(signedTransactionBytes); - updateTxHistory({ - status: "Pending", - txId, - tx: plainTransactionObj, - signature: signatureBase58, - signedTxBase64, - txHash: txHash58, - finalState: false - }); - try { - return await sendTxToRpc(signedTxBase64, waitUntil, txId); - } catch (error) { - console.error("Error Sending Transaction:", error, plainTransactionObj, signedTxBase64); - } -}, "sendTx"); -const exp = { - utils: {}, - // we will map this in a moment, giving keys, for IDE hints - borsh: reExportAllUtils.exp.borsh, - borshSchema: reExportAllUtils.exp.borshSchema.getBorshSchema() -}; -for (const key in reExportAllUtils) { - exp.utils[key] = reExportAllUtils[key]; -} -const utils = exp.utils; -const state = {}; -for (const key in stateExports) { - state[key] = stateExports[key]; -} -const event = state["events"]; -delete state["events"]; -try { - if (typeof window !== "undefined") { - const url = new URL(window.location.href); - const accId = url.searchParams.get("account_id"); - const pubKey = url.searchParams.get("public_key"); - const errCode = url.searchParams.get("errorCode"); - const errMsg = url.searchParams.get("errorMessage"); - const decodedErrMsg = errMsg ? decodeURIComponent(errMsg) : null; - const txHashes = url.searchParams.get("transactionHashes"); - const txIds = url.searchParams.get("txIds"); - if (errCode || errMsg) { - console.warn(new Error(`Wallet raises: -code: ${errCode} -message: ${decodedErrMsg}`)); - } - if (accId && pubKey) { - if (pubKey === _state.publicKey) { - update({ accountId: accId }); - } else { - if (authStatus() === "SignedIn") { - console.warn("Public key mismatch from wallet redirect", pubKey, _state.publicKey); - } - url.searchParams.delete("public_key"); - } - } - if (txHashes || txIds) { - const hashArr = txHashes ? txHashes.split(",") : []; - const idArr = txIds ? txIds.split(",") : []; - if (idArr.length > hashArr.length) { - idArr.forEach((id) => { - updateTxHistory({ txId: id, status: "RejectedByUser", finalState: true }); - }); - } else if (idArr.length === hashArr.length) { - idArr.forEach((id, i) => { - updateTxHistory({ - txId: id, - status: "PendingGotTxHash", - txHash: hashArr[i], - finalState: false - }); - afterTxSent(id); - }); - } else { - console.error(new Error("Transaction hash mismatch from wallet redirect"), idArr, hashArr); - } - } - url.searchParams.delete("txIds"); - if (authStatus() === "SignedOut") { - url.searchParams.delete("errorCode"); - url.searchParams.delete("errorMessage"); - } - } -} catch (e) { - console.error("Error handling wallet redirect:", e); -} -const actions = { - functionCall: /* @__PURE__ */ __name(({ - methodName, - gas, - deposit, - args, - argsBase64 - }) => ({ - type: "FunctionCall", - methodName, - args, - argsBase64, - gas, - deposit - }), "functionCall"), - transfer: /* @__PURE__ */ __name((yoctoAmount) => ({ - type: "Transfer", - deposit: yoctoAmount - }), "transfer"), - stakeNEAR: /* @__PURE__ */ __name(({ amount, publicKey: publicKey2 }) => ({ - type: "Stake", - stake: amount, - publicKey: publicKey2 - }), "stakeNEAR"), - addFullAccessKey: /* @__PURE__ */ __name(({ publicKey: publicKey2 }) => ({ - type: "AddKey", - publicKey: publicKey2, - accessKey: { permission: "FullAccess" } - }), "addFullAccessKey"), - addLimitedAccessKey: /* @__PURE__ */ __name(({ - publicKey: publicKey2, - allowance, - accountId: accountId2, - methodNames - }) => ({ - type: "AddKey", - publicKey: publicKey2, - accessKey: { - permission: "FunctionCall", - allowance, - receiverId: accountId2, - methodNames - } - }), "addLimitedAccessKey"), - deleteKey: /* @__PURE__ */ __name(({ publicKey: publicKey2 }) => ({ - type: "DeleteKey", - publicKey: publicKey2 - }), "deleteKey"), - deleteAccount: /* @__PURE__ */ __name(({ beneficiaryId }) => ({ - type: "DeleteAccount", - beneficiaryId - }), "deleteAccount"), - createAccount: /* @__PURE__ */ __name(() => ({ - type: "CreateAccount" - }), "createAccount"), - deployContract: /* @__PURE__ */ __name(({ codeBase64 }) => ({ - type: "DeployContract", - codeBase64 - }), "deployContract") -}; -export { - MaxBlockDelayMs, - accountId, - actions, - afterTxSent, - authStatus, - config, - event, - exp, - generateTxId, - getPublicKeyForContract, - localTxHistory, - publicKey, - queryAccessKey, - queryAccount, - queryBlock, - queryTx, - requestSignIn, - selected, - sendRpc, - sendTx, - sendTxToRpc, - signOut, - state, - utils, - view, - withBlockId -}; -//# sourceMappingURL=near.js.map diff --git a/static/js-loaded-globally/nearjs/0.9.7/esm/near.js.map b/static/js-loaded-globally/nearjs/0.9.7/esm/near.js.map deleted file mode 100644 index 6105d62..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/esm/near.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/near.ts"],"sourcesContent":["import Big from \"big.js\";\nimport {\n lsSet,\n lsGet,\n tryParseJson,\n fromBase64,\n toBase64,\n canSignWithLAK,\n toBase58,\n parseJsonFromBytes,\n signHash,\n publicKeyFromPrivate,\n privateKeyFromRandom,\n serializeTransaction,\n serializeSignedTransaction, bytesToBase64, PlainTransaction,\n} from \"@fastnear/utils\";\n\nimport {\n _adapter,\n _state,\n DEFAULT_NETWORK_ID,\n NETWORKS,\n getTxHistory,\n update,\n updateTxHistory,\n} from \"./state.js\";\n\nimport {\n getConfig,\n setConfig,\n resetTxHistory,\n} from \"./state.js\";\n\nimport { sha256 } from \"@noble/hashes/sha2\";\nimport * as reExportAllUtils from \"@fastnear/utils\";\nimport * as stateExports from \"./state.js\";\n\nBig.DP = 27;\nexport const MaxBlockDelayMs = 1000 * 60 * 60 * 6; // 6 hours\n\nexport interface AccessKeyWithError {\n result: {\n nonce: number;\n permission?: any;\n error?: string;\n }\n}\n\nexport interface WalletTxResult {\n url?: string;\n outcomes?: Array<{ transaction: { hash: string } }>;\n rejected?: boolean;\n error?: string;\n}\n\nexport interface BlockView {\n result: {\n header: {\n hash: string;\n timestamp_nanosec: string;\n }\n }\n}\n\n// The structure it's saved to in storage\nexport interface LastKnownBlock {\n header: {\n hash: string;\n timestamp_nanosec: string;\n }\n}\n\nexport function withBlockId(params: Record, blockId?: string) {\n if (blockId === \"final\" || blockId === \"optimistic\") {\n return { ...params, finality: blockId };\n }\n return blockId ? { ...params, block_id: blockId } : { ...params, finality: \"optimistic\" };\n}\n\nexport async function sendRpc(method: string, params: Record | any[]) {\n const config = getConfig();\n if (!config?.nodeUrl) {\n throw new Error(\"fastnear: getConfig() returned invalid config: missing nodeUrl.\");\n }\n const response = await fetch(config.nodeUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: `fastnear-${Date.now()}`,\n method,\n params,\n }),\n });\n const result = await response.json();\n if (result.error) {\n throw new Error(JSON.stringify(result.error));\n }\n return result;\n}\n\nexport function afterTxSent(txId: string) {\n const txHistory = getTxHistory();\n sendRpc(\"tx\", {\n tx_hash: txHistory[txId]?.txHash,\n sender_account_id: txHistory[txId]?.tx?.signerId,\n wait_until: \"EXECUTED_OPTIMISTIC\",\n })\n .then( result => {\n const successValue = result?.result?.status?.SuccessValue;\n updateTxHistory({\n txId,\n status: \"Executed\",\n result,\n successValue: successValue ? tryParseJson(fromBase64(successValue)) : undefined,\n finalState: true,\n });\n })\n .catch((error) => {\n updateTxHistory({\n txId,\n status: \"ErrorAfterIncluded\",\n error: tryParseJson(error.message) ?? error.message,\n finalState: true,\n });\n });\n}\n\nexport async function sendTxToRpc(signedTxBase64: string, waitUntil: string | undefined, txId: string) {\n // default to \"INCLUDED\"\n // see options: https://docs.near.org/api/rpc/transactions#tx-status-result\n waitUntil = waitUntil || \"INCLUDED\";\n\n try {\n const sendTxRes = await sendRpc(\"send_tx\", {\n signed_tx_base64: signedTxBase64,\n wait_until: waitUntil,\n });\n\n updateTxHistory({ txId, status: \"Included\", finalState: false });\n afterTxSent(txId);\n\n return sendTxRes;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n updateTxHistory({\n txId,\n status: \"Error\",\n error: tryParseJson(errorMessage) ?? errorMessage,\n finalState: false,\n });\n throw new Error(errorMessage);\n }\n}\n\nexport interface AccessKeyView {\n nonce: number;\n permission: any;\n}\n\n/**\n * Generates a mock transaction ID.\n *\n * This function creates a pseudo-unique transaction ID for testing or\n * non-production use. It combines the current timestamp with a\n * random component for uniqueness.\n *\n * **Note:** This is not cryptographically secure and should not be used\n * for actual transaction processing.\n *\n * @returns {string} A mock transaction ID in the format `tx-{timestamp}-{random}`\n */\nexport function generateTxId(): string {\n const randomPart = crypto.getRandomValues(new Uint32Array(2)).join(\"\");\n return `tx-${Date.now()}-${parseInt(randomPart, 10).toString(36)}`;\n}\n\nexport const accountId = () => _state.accountId;\nexport const publicKey = () => _state.publicKey;\n\nexport const config = (newConfig?: Record) => {\n const current = getConfig();\n if (newConfig) {\n if (newConfig.networkId && current.networkId !== newConfig.networkId) {\n setConfig(newConfig.networkId);\n update({ accountId: null, privateKey: null, lastWalletId: null });\n lsSet(\"block\", null);\n resetTxHistory();\n }\n setConfig({ ...getConfig(), ...newConfig });\n }\n return getConfig();\n};\n\nexport const authStatus = (): string | Record => {\n if (!_state.accountId) {\n return \"SignedOut\";\n }\n return \"SignedIn\";\n};\n\n// this is an intentional stub\n// and it's probably partially done, to help ease future features\n// for now we'll assume each web end user has one keypair in storage\n// for every contract they wish to interact with\n// later, it may be prudent to hold multiple, but until then this function\n// just returns the access key as if it were among others in the array.\n// we're pretending like we really thought about which access key we're returning\n// based on the opts argument. this allows us to fill this logic in later.\nexport const getPublicKeyForContract = (opts?: any) => {\n return publicKey();\n}\n\n// returns details on the selected:\n// network, wallet, and explorer details as well as\n// sending account, contract, and selected public key\nexport const selected = () => {\n const network = getConfig().networkId;\n const nodeUrl = getConfig().nodeUrl;\n const walletUrl = getConfig().walletUrl;\n const helperUrl = getConfig().helperUrl;\n const explorerUrl = getConfig().explorerUrl;\n\n const account = accountId();\n const contract = _state.accessKeyContractId;\n const publicKey = getPublicKeyForContract();\n\n return {\n network,\n nodeUrl,\n walletUrl,\n helperUrl,\n explorerUrl,\n account,\n contract,\n publicKey\n }\n}\n\nexport const requestSignIn = async ({ contractId }: { contractId: string }) => {\n const privateKey = privateKeyFromRandom();\n update({ accessKeyContractId: contractId, accountId: null, privateKey });\n const pubKey = publicKeyFromPrivate(privateKey);\n\n const result = await _adapter.signIn({\n networkId: getConfig().networkId,\n contractId,\n publicKey: pubKey,\n });\n\n if (result.error) {\n throw new Error(`Wallet error: ${result.error}`);\n }\n if (result.url) {\n if (typeof window !== \"undefined\") {\n setTimeout(() => {\n window.location.href = result.url;\n }, 100);\n }\n } else if (result.accountId) {\n update({ accountId: result.accountId });\n }\n};\n\nexport const view = async ({\n contractId,\n methodName,\n args,\n argsBase64,\n blockId,\n }: {\n contractId: string;\n methodName: string;\n args?: any;\n argsBase64?: string;\n blockId?: string;\n}) => {\n const encodedArgs = argsBase64 || (args ? toBase64(JSON.stringify(args)) : \"\");\n const queryResult = await sendRpc(\n \"query\",\n withBlockId(\n {\n request_type: \"call_function\",\n account_id: contractId,\n method_name: methodName,\n args_base64: encodedArgs,\n },\n blockId\n )\n );\n\n return parseJsonFromBytes(queryResult.result.result);\n};\n\nexport const queryAccount = async ({\n accountId,\n blockId,\n }: {\n accountId: string;\n blockId?: string;\n}) => {\n return sendRpc(\n \"query\",\n withBlockId({ request_type: \"view_account\", account_id: accountId }, blockId)\n );\n};\n\nexport const queryBlock = async ({ blockId }: { blockId?: string }): Promise => {\n return sendRpc(\"block\", withBlockId({}, blockId));\n};\n\nexport const queryAccessKey = async ({\n accountId,\n publicKey,\n blockId,\n }: {\n accountId: string;\n publicKey: string;\n blockId?: string;\n}): Promise => {\n return sendRpc(\n \"query\",\n withBlockId(\n { request_type: \"view_access_key\", account_id: accountId, public_key: publicKey },\n blockId\n )\n );\n};\n\nexport const queryTx = async ({ txHash, accountId }: { txHash: string; accountId: string }) => {\n return sendRpc(\"tx\", [txHash, accountId]);\n};\n\nexport const localTxHistory = () => {\n return getTxHistory();\n};\n\nexport const signOut = () => {\n update({ accountId: null, privateKey: null, contractId: null });\n setConfig(NETWORKS[DEFAULT_NETWORK_ID]);\n};\n\nexport const sendTx = async ({\n receiverId,\n actions,\n waitUntil,\n }: {\n receiverId: string;\n actions: any[];\n waitUntil?: string;\n}) => {\n const signerId = _state.accountId;\n if (!signerId) throw new Error(\"Must sign in\");\n\n const publicKey = _state.publicKey ?? \"\";\n const privKey = _state.privateKey;\n // this generates a mock transaction ID so we can keep track of each tx\n const txId = generateTxId();\n\n if (!privKey || receiverId !== _state.accessKeyContractId || !canSignWithLAK(actions)) {\n const jsonTx = { signerId, receiverId, actions };\n updateTxHistory({ status: \"Pending\", txId, tx: jsonTx, finalState: false });\n\n const url = new URL(typeof window !== \"undefined\" ? window.location.href : \"\");\n url.searchParams.set(\"txIds\", txId);\n\n // preserve existing url params\n const existingParams = new URLSearchParams(window.location.search);\n existingParams.forEach((value, key) => {\n if (!url.searchParams.has(key)) {\n url.searchParams.set(key, value);\n }\n });\n\n // we're wanting to preserve URL params that we send in\n // but make sure we're not feeding back error params\n // from a previous failure\n\n url.searchParams.delete(\"errorCode\");\n url.searchParams.delete(\"errorMessage\");\n\n try {\n const result: WalletTxResult = await _adapter.sendTransactions({\n transactions: [jsonTx],\n callbackUrl: url.toString(),\n });\n\n if (result.url) {\n if (typeof window !== \"undefined\") {\n setTimeout(() => {\n window.location.href = result.url!;\n }, 100);\n }\n } else if (result.outcomes?.length) {\n result.outcomes.forEach((r) =>\n updateTxHistory({\n txId,\n status: \"Executed\",\n result: r,\n txHash: r.transaction.hash,\n finalState: true,\n })\n );\n } else if (result.rejected) {\n updateTxHistory({ txId, status: \"RejectedByUser\", finalState: true });\n } else if (result.error) {\n updateTxHistory({\n txId,\n status: \"Error\",\n error: tryParseJson(result.error),\n finalState: true,\n });\n }\n\n return result;\n } catch (err) {\n console.error('fastnear: error sending tx using adapter:', err)\n updateTxHistory({\n txId,\n status: \"Error\",\n error: tryParseJson((err as Error).message),\n finalState: true,\n });\n\n return Promise.reject(err);\n }\n }\n\n let nonce = lsGet(\"nonce\") as number | null;\n if (nonce == null) {\n const accessKey = await queryAccessKey({ accountId: signerId, publicKey: publicKey });\n if (accessKey.result.error) {\n throw new Error(`Access key error: ${accessKey.result.error} when attempting to get nonce for ${signerId} for public key ${publicKey}`);\n }\n nonce = accessKey.result.nonce;\n lsSet(\"nonce\", nonce);\n }\n\n let lastKnownBlock = lsGet(\"block\") as LastKnownBlock | null;\n if (\n !lastKnownBlock ||\n parseFloat(lastKnownBlock.header.timestamp_nanosec) / 1e6 + MaxBlockDelayMs < Date.now()\n ) {\n const latestBlock = await queryBlock({ blockId: \"final\" });\n lastKnownBlock = {\n header: {\n hash: latestBlock.result.header.hash,\n timestamp_nanosec: latestBlock.result.header.timestamp_nanosec,\n },\n };\n lsSet(\"block\", lastKnownBlock);\n }\n\n nonce += 1;\n lsSet(\"nonce\", nonce);\n\n const blockHash = lastKnownBlock.header.hash;\n\n const plainTransactionObj: PlainTransaction = {\n signerId,\n publicKey,\n nonce,\n receiverId,\n blockHash,\n actions,\n };\n\n const txBytes = serializeTransaction(plainTransactionObj);\n const txHashBytes = sha256(txBytes);\n const txHash58 = toBase58(txHashBytes);\n\n const signatureBase58 = signHash(txHashBytes, privKey, { returnBase58: true });\n const signedTransactionBytes = serializeSignedTransaction(plainTransactionObj, signatureBase58);\n const signedTxBase64 = bytesToBase64(signedTransactionBytes);\n\n updateTxHistory({\n status: \"Pending\",\n txId,\n tx: plainTransactionObj,\n signature: signatureBase58,\n signedTxBase64,\n txHash: txHash58,\n finalState: false,\n });\n\n try {\n return await sendTxToRpc(signedTxBase64, waitUntil, txId);\n } catch (error) {\n console.error(\"Error Sending Transaction:\", error, plainTransactionObj, signedTxBase64);\n }\n};\n\n// exports\nexport const exp = {\n utils: {}, // we will map this in a moment, giving keys, for IDE hints\n borsh: reExportAllUtils.exp.borsh,\n borshSchema: reExportAllUtils.exp.borshSchema.getBorshSchema(),\n};\n\nfor (const key in reExportAllUtils) {\n exp.utils[key] = reExportAllUtils[key];\n}\n\n// devx\nexport const utils = exp.utils;\n\nexport const state = {}\n\nfor (const key in stateExports) {\n state[key] = stateExports[key];\n}\n\n// devx\n\nexport const event = state['events'];\ndelete state['events'];\n\n// Wallet redirect handling\ntry {\n if (typeof window !== \"undefined\") {\n const url = new URL(window.location.href);\n const accId = url.searchParams.get(\"account_id\");\n const pubKey = url.searchParams.get(\"public_key\");\n const errCode = url.searchParams.get(\"errorCode\");\n const errMsg = url.searchParams.get(\"errorMessage\");\n const decodedErrMsg = errMsg ? decodeURIComponent(errMsg) : null;\n\n const txHashes = url.searchParams.get(\"transactionHashes\");\n const txIds = url.searchParams.get(\"txIds\");\n\n if (errCode || errMsg) {\n console.warn(new Error(`Wallet raises:\\ncode: ${errCode}\\nmessage: ${decodedErrMsg}`));\n }\n\n if (accId && pubKey) {\n if (pubKey === _state.publicKey) {\n update({ accountId: accId });\n } else {\n // it's possible the end user has a URL param that's old. we'll remove the public_key param\n // if logged out, no need to throw warning\n if (authStatus() === \"SignedIn\") {\n console.warn(\"Public key mismatch from wallet redirect\", pubKey, _state.publicKey);\n }\n url.searchParams.delete(\"public_key\");\n }\n }\n\n if (txHashes || txIds) {\n const hashArr = txHashes ? txHashes.split(\",\") : [];\n const idArr = txIds ? txIds.split(\",\") : [];\n if (idArr.length > hashArr.length) {\n idArr.forEach((id) => {\n updateTxHistory({ txId: id, status: \"RejectedByUser\", finalState: true });\n });\n } else if (idArr.length === hashArr.length) {\n idArr.forEach((id, i) => {\n updateTxHistory({\n txId: id,\n status: \"PendingGotTxHash\",\n txHash: hashArr[i],\n finalState: false,\n });\n afterTxSent(id);\n });\n } else {\n console.error(new Error(\"Transaction hash mismatch from wallet redirect\"), idArr, hashArr);\n }\n }\n\n // we can consider removing these, but want to be careful because\n // it can be helpful for a dev to have a URL they can debug with\n // we won't want to remove information\n\n // pretty sure txIds can go, especially if you can tell it's been more than 5 minutes or something\n // public_key sometimes confuses it, so this might only be needed when adding a new access key\n // and perhaps once we've confirmed that the transaction hashes are getting saved to storage\n // (not sure about that section of code) then we can get rid of the transactionHashes, too\n\n url.searchParams.delete(\"txIds\");\n if (authStatus() === \"SignedOut\") {\n url.searchParams.delete(\"errorCode\");\n url.searchParams.delete(\"errorMessage\");\n }\n // ^ we've decided these ones make sense to keep\n\n // I'd like to keep this for posterity. for a bit.\n // url.searchParams.delete(\"account_id\");\n // url.searchParams.delete(\"public_key\");\n\n // url.searchParams.delete(\"all_keys\");\n // url.searchParams.delete(\"transactionHashes\");\n // window.history.replaceState({}, \"\", url.toString());\n }\n} catch (e) {\n console.error(\"Error handling wallet redirect:\", e);\n}\n\n// action helpers\nexport const actions = {\n functionCall: ({\n methodName,\n gas,\n deposit,\n args,\n argsBase64,\n }: {\n methodName: string;\n gas?: string;\n deposit?: string;\n args?: Record;\n argsBase64?: string;\n }) => ({\n type: \"FunctionCall\",\n methodName,\n args,\n argsBase64,\n gas,\n deposit,\n }),\n\n transfer: (yoctoAmount: string) => ({\n type: \"Transfer\",\n deposit: yoctoAmount,\n }),\n\n stakeNEAR: ({amount, publicKey}: { amount: string; publicKey: string }) => ({\n type: \"Stake\",\n stake: amount,\n publicKey,\n }),\n\n addFullAccessKey: ({publicKey}: { publicKey: string }) => ({\n type: \"AddKey\",\n publicKey: publicKey,\n accessKey: {permission: \"FullAccess\"},\n }),\n\n addLimitedAccessKey: ({\n publicKey,\n allowance,\n accountId,\n methodNames,\n }: {\n publicKey: string;\n allowance: string;\n accountId: string;\n methodNames: string[];\n }) => ({\n type: \"AddKey\",\n publicKey: publicKey,\n accessKey: {\n permission: \"FunctionCall\",\n allowance,\n receiverId: accountId,\n methodNames,\n },\n }),\n\n deleteKey: ({publicKey}: { publicKey: string }) => ({\n type: \"DeleteKey\",\n publicKey,\n }),\n\n deleteAccount: ({beneficiaryId}: { beneficiaryId: string }) => ({\n type: \"DeleteAccount\",\n beneficiaryId,\n }),\n\n createAccount: () => ({\n type: \"CreateAccount\",\n }),\n\n deployContract: ({codeBase64}: { codeBase64: string }) => ({\n type: \"DeployContract\",\n codeBase64,\n }),\n};\n"],"mappings":";;;;AAAA,OAAO,SAAS;AAChB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAA4B;AAAA,OACvB;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,cAAc;AACvB,YAAY,sBAAsB;AAClC,YAAY,kBAAkB;AAE9B,IAAI,KAAK;AACF,MAAM,kBAAkB,MAAO,KAAK,KAAK;AAkCzC,SAAS,YAAY,QAA6B,SAAkB;AACzE,MAAI,YAAY,WAAW,YAAY,cAAc;AACnD,WAAO,EAAE,GAAG,QAAQ,UAAU,QAAQ;AAAA,EACxC;AACA,SAAO,UAAU,EAAE,GAAG,QAAQ,UAAU,QAAQ,IAAI,EAAE,GAAG,QAAQ,UAAU,aAAa;AAC1F;AALgB;AAOhB,eAAsB,QAAQ,QAAgB,QAAqC;AACjF,QAAMA,UAAS,UAAU;AACzB,MAAI,CAACA,SAAQ,SAAS;AACpB,UAAM,IAAI,MAAM,iEAAiE;AAAA,EACnF;AACA,QAAM,WAAW,MAAM,MAAMA,QAAO,SAAS;AAAA,IAC3C,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB,SAAS;AAAA,MACT,IAAI,YAAY,KAAK,IAAI,CAAC;AAAA,MAC1B;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACD,QAAM,SAAS,MAAM,SAAS,KAAK;AACnC,MAAI,OAAO,OAAO;AAChB,UAAM,IAAI,MAAM,KAAK,UAAU,OAAO,KAAK,CAAC;AAAA,EAC9C;AACA,SAAO;AACT;AApBsB;AAsBf,SAAS,YAAY,MAAc;AACxC,QAAM,YAAY,aAAa;AAC/B,UAAQ,MAAM;AAAA,IACZ,SAAS,UAAU,IAAI,GAAG;AAAA,IAC1B,mBAAmB,UAAU,IAAI,GAAG,IAAI;AAAA,IACxC,YAAY;AAAA,EACd,CAAC,EACE,KAAM,YAAU;AACf,UAAM,eAAe,QAAQ,QAAQ,QAAQ;AAC7C,oBAAgB;AAAA,MACd;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,cAAc,eAAe,aAAa,WAAW,YAAY,CAAC,IAAI;AAAA,MACtE,YAAY;AAAA,IACd,CAAC;AAAA,EACH,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,oBAAgB;AAAA,MACd;AAAA,MACA,QAAQ;AAAA,MACR,OAAO,aAAa,MAAM,OAAO,KAAK,MAAM;AAAA,MAC5C,YAAY;AAAA,IACd,CAAC;AAAA,EACH,CAAC;AACL;AAzBgB;AA2BhB,eAAsB,YAAY,gBAAwB,WAA+B,MAAc;AAGrG,cAAY,aAAa;AAEzB,MAAI;AACF,UAAM,YAAY,MAAM,QAAQ,WAAW;AAAA,MACzC,kBAAkB;AAAA,MAClB,YAAY;AAAA,IACd,CAAC;AAED,oBAAgB,EAAE,MAAM,QAAQ,YAAY,YAAY,MAAM,CAAC;AAC/D,gBAAY,IAAI;AAEhB,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU;AAC9D,oBAAgB;AAAA,MACd;AAAA,MACA,QAAQ;AAAA,MACR,OAAO,aAAa,YAAY,KAAK;AAAA,MACrC,YAAY;AAAA,IACd,CAAC;AACD,UAAM,IAAI,MAAM,YAAY;AAAA,EAC9B;AACF;AAzBsB;AA4Cf,SAAS,eAAuB;AACrC,QAAM,aAAa,OAAO,gBAAgB,IAAI,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE;AACrE,SAAO,MAAM,KAAK,IAAI,CAAC,IAAI,SAAS,YAAY,EAAE,EAAE,SAAS,EAAE,CAAC;AAClE;AAHgB;AAKT,MAAM,YAAY,6BAAM,OAAO,WAAb;AAClB,MAAM,YAAY,6BAAM,OAAO,WAAb;AAElB,MAAM,SAAS,wBAAC,cAAoC;AACzD,QAAM,UAAU,UAAU;AAC1B,MAAI,WAAW;AACb,QAAI,UAAU,aAAa,QAAQ,cAAc,UAAU,WAAW;AACpE,gBAAU,UAAU,SAAS;AAC7B,aAAO,EAAE,WAAW,MAAM,YAAY,MAAM,cAAc,KAAK,CAAC;AAChE,YAAM,SAAS,IAAI;AACnB,qBAAe;AAAA,IACjB;AACA,cAAU,EAAE,GAAG,UAAU,GAAG,GAAG,UAAU,CAAC;AAAA,EAC5C;AACA,SAAO,UAAU;AACnB,GAZsB;AAcf,MAAM,aAAa,6BAAoC;AAC5D,MAAI,CAAC,OAAO,WAAW;AACrB,WAAO;AAAA,EACT;AACA,SAAO;AACT,GAL0B;AAenB,MAAM,0BAA0B,wBAAC,SAAe;AACrD,SAAO,UAAU;AACnB,GAFuC;AAOhC,MAAM,WAAW,6BAAM;AAC5B,QAAM,UAAU,UAAU,EAAE;AAC5B,QAAM,UAAU,UAAU,EAAE;AAC5B,QAAM,YAAY,UAAU,EAAE;AAC9B,QAAM,YAAY,UAAU,EAAE;AAC9B,QAAM,cAAc,UAAU,EAAE;AAEhC,QAAM,UAAU,UAAU;AAC1B,QAAM,WAAW,OAAO;AACxB,QAAMC,aAAY,wBAAwB;AAE1C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAAA;AAAA,EACF;AACF,GArBwB;AAuBjB,MAAM,gBAAgB,8BAAO,EAAE,WAAW,MAA8B;AAC7E,QAAM,aAAa,qBAAqB;AACxC,SAAO,EAAE,qBAAqB,YAAY,WAAW,MAAM,WAAW,CAAC;AACvE,QAAM,SAAS,qBAAqB,UAAU;AAE9C,QAAM,SAAS,MAAM,SAAS,OAAO;AAAA,IACnC,WAAW,UAAU,EAAE;AAAA,IACvB;AAAA,IACA,WAAW;AAAA,EACb,CAAC;AAED,MAAI,OAAO,OAAO;AAChB,UAAM,IAAI,MAAM,iBAAiB,OAAO,KAAK,EAAE;AAAA,EACjD;AACA,MAAI,OAAO,KAAK;AACd,QAAI,OAAO,WAAW,aAAa;AACjC,iBAAW,MAAM;AACf,eAAO,SAAS,OAAO,OAAO;AAAA,MAChC,GAAG,GAAG;AAAA,IACR;AAAA,EACF,WAAW,OAAO,WAAW;AAC3B,WAAO,EAAE,WAAW,OAAO,UAAU,CAAC;AAAA,EACxC;AACF,GAvB6B;AAyBtB,MAAM,OAAO,8BAAO;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAMrB;AACJ,QAAM,cAAc,eAAe,OAAO,SAAS,KAAK,UAAU,IAAI,CAAC,IAAI;AAC3E,QAAM,cAAc,MAAM;AAAA,IACxB;AAAA,IACA;AAAA,MACE;AAAA,QACE,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,aAAa;AAAA,QACb,aAAa;AAAA,MACf;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO,mBAAmB,YAAY,OAAO,MAAM;AACrD,GA5BoB;AA8Bb,MAAM,eAAe,8BAAO;AAAA,EACH,WAAAC;AAAA,EACA;AACF,MAGxB;AACJ,SAAO;AAAA,IACL;AAAA,IACA,YAAY,EAAE,cAAc,gBAAgB,YAAYA,WAAU,GAAG,OAAO;AAAA,EAC9E;AACF,GAX4B;AAarB,MAAM,aAAa,8BAAO,EAAE,QAAQ,MAAgD;AACzF,SAAO,QAAQ,SAAS,YAAY,CAAC,GAAG,OAAO,CAAC;AAClD,GAF0B;AAInB,MAAM,iBAAiB,8BAAO;AAAA,EACH,WAAAA;AAAA,EACA,WAAAD;AAAA,EACA;AACF,MAIG;AACjC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,EAAE,cAAc,mBAAmB,YAAYC,YAAW,YAAYD,WAAU;AAAA,MAChF;AAAA,IACF;AAAA,EACF;AACF,GAhB8B;AAkBvB,MAAM,UAAU,8BAAO,EAAE,QAAQ,WAAAC,WAAU,MAA6C;AAC7F,SAAO,QAAQ,MAAM,CAAC,QAAQA,UAAS,CAAC;AAC1C,GAFuB;AAIhB,MAAM,iBAAiB,6BAAM;AAClC,SAAO,aAAa;AACtB,GAF8B;AAIvB,MAAM,UAAU,6BAAM;AAC3B,SAAO,EAAE,WAAW,MAAM,YAAY,MAAM,YAAY,KAAK,CAAC;AAC9D,YAAU,SAAS,kBAAkB,CAAC;AACxC,GAHuB;AAKhB,MAAM,SAAS,8BAAO;AAAA,EACE;AAAA,EACA,SAAAC;AAAA,EACA;AACF,MAIvB;AACJ,QAAM,WAAW,OAAO;AACxB,MAAI,CAAC,SAAU,OAAM,IAAI,MAAM,cAAc;AAE7C,QAAMF,aAAY,OAAO,aAAa;AACtC,QAAM,UAAU,OAAO;AAEvB,QAAM,OAAO,aAAa;AAE1B,MAAI,CAAC,WAAW,eAAe,OAAO,uBAAuB,CAAC,eAAeE,QAAO,GAAG;AACrF,UAAM,SAAS,EAAE,UAAU,YAAY,SAAAA,SAAQ;AAC/C,oBAAgB,EAAE,QAAQ,WAAW,MAAM,IAAI,QAAQ,YAAY,MAAM,CAAC;AAE1E,UAAM,MAAM,IAAI,IAAI,OAAO,WAAW,cAAc,OAAO,SAAS,OAAO,EAAE;AAC7E,QAAI,aAAa,IAAI,SAAS,IAAI;AAGlC,UAAM,iBAAiB,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACjE,mBAAe,QAAQ,CAAC,OAAO,QAAQ;AACrC,UAAI,CAAC,IAAI,aAAa,IAAI,GAAG,GAAG;AAC9B,YAAI,aAAa,IAAI,KAAK,KAAK;AAAA,MACjC;AAAA,IACF,CAAC;AAMD,QAAI,aAAa,OAAO,WAAW;AACnC,QAAI,aAAa,OAAO,cAAc;AAEtC,QAAI;AACF,YAAM,SAAyB,MAAM,SAAS,iBAAiB;AAAA,QAC7D,cAAc,CAAC,MAAM;AAAA,QACrB,aAAa,IAAI,SAAS;AAAA,MAC5B,CAAC;AAED,UAAI,OAAO,KAAK;AACd,YAAI,OAAO,WAAW,aAAa;AACjC,qBAAW,MAAM;AACf,mBAAO,SAAS,OAAO,OAAO;AAAA,UAChC,GAAG,GAAG;AAAA,QACR;AAAA,MACF,WAAW,OAAO,UAAU,QAAQ;AAClC,eAAO,SAAS;AAAA,UAAQ,CAAC,MACvB,gBAAgB;AAAA,YACd;AAAA,YACA,QAAQ;AAAA,YACR,QAAQ;AAAA,YACR,QAAQ,EAAE,YAAY;AAAA,YACtB,YAAY;AAAA,UACd,CAAC;AAAA,QACH;AAAA,MACF,WAAW,OAAO,UAAU;AAC1B,wBAAgB,EAAE,MAAM,QAAQ,kBAAkB,YAAY,KAAK,CAAC;AAAA,MACtE,WAAW,OAAO,OAAO;AACvB,wBAAgB;AAAA,UACd;AAAA,UACA,QAAQ;AAAA,UACR,OAAO,aAAa,OAAO,KAAK;AAAA,UAChC,YAAY;AAAA,QACd,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,cAAQ,MAAM,6CAA6C,GAAG;AAC9D,sBAAgB;AAAA,QACd;AAAA,QACA,QAAQ;AAAA,QACR,OAAO,aAAc,IAAc,OAAO;AAAA,QAC1C,YAAY;AAAA,MACd,CAAC;AAED,aAAO,QAAQ,OAAO,GAAG;AAAA,IAC3B;AAAA,EACF;AAEA,MAAI,QAAQ,MAAM,OAAO;AACzB,MAAI,SAAS,MAAM;AACjB,UAAM,YAAY,MAAM,eAAe,EAAE,WAAW,UAAU,WAAWF,WAAU,CAAC;AACpF,QAAI,UAAU,OAAO,OAAO;AAC1B,YAAM,IAAI,MAAM,qBAAqB,UAAU,OAAO,KAAK,qCAAqC,QAAQ,mBAAmBA,UAAS,EAAE;AAAA,IACxI;AACA,YAAQ,UAAU,OAAO;AACzB,UAAM,SAAS,KAAK;AAAA,EACtB;AAEA,MAAI,iBAAiB,MAAM,OAAO;AAClC,MACE,CAAC,kBACD,WAAW,eAAe,OAAO,iBAAiB,IAAI,MAAM,kBAAkB,KAAK,IAAI,GACvF;AACA,UAAM,cAAc,MAAM,WAAW,EAAE,SAAS,QAAQ,CAAC;AACzD,qBAAiB;AAAA,MACf,QAAQ;AAAA,QACN,MAAM,YAAY,OAAO,OAAO;AAAA,QAChC,mBAAmB,YAAY,OAAO,OAAO;AAAA,MAC/C;AAAA,IACF;AACA,UAAM,SAAS,cAAc;AAAA,EAC/B;AAEA,WAAS;AACT,QAAM,SAAS,KAAK;AAEpB,QAAM,YAAY,eAAe,OAAO;AAExC,QAAM,sBAAwC;AAAA,IAC5C;AAAA,IACA,WAAAA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAAE;AAAA,EACF;AAEA,QAAM,UAAU,qBAAqB,mBAAmB;AACxD,QAAM,cAAc,OAAO,OAAO;AAClC,QAAM,WAAW,SAAS,WAAW;AAErC,QAAM,kBAAkB,SAAS,aAAa,SAAS,EAAE,cAAc,KAAK,CAAC;AAC7E,QAAM,yBAAyB,2BAA2B,qBAAqB,eAAe;AAC9F,QAAM,iBAAiB,cAAc,sBAAsB;AAE3D,kBAAgB;AAAA,IACd,QAAQ;AAAA,IACR;AAAA,IACA,IAAI;AAAA,IACJ,WAAW;AAAA,IACX;AAAA,IACA,QAAQ;AAAA,IACR,YAAY;AAAA,EACd,CAAC;AAED,MAAI;AACF,WAAO,MAAM,YAAY,gBAAgB,WAAW,IAAI;AAAA,EAC1D,SAAS,OAAO;AACd,YAAQ,MAAM,8BAA8B,OAAO,qBAAqB,cAAc;AAAA,EACxF;AACF,GApJsB;AAuJf,MAAM,MAAM;AAAA,EACjB,OAAO,CAAC;AAAA;AAAA,EACR,OAAO,iBAAiB,IAAI;AAAA,EAC5B,aAAa,iBAAiB,IAAI,YAAY,eAAe;AAC/D;AAEA,WAAW,OAAO,kBAAkB;AAClC,MAAI,MAAM,GAAG,IAAI,iBAAiB,GAAG;AACvC;AAGO,MAAM,QAAQ,IAAI;AAElB,MAAM,QAAQ,CAAC;AAEtB,WAAW,OAAO,cAAc;AAC9B,QAAM,GAAG,IAAI,aAAa,GAAG;AAC/B;AAIO,MAAM,QAAQ,MAAM,QAAQ;AACnC,OAAO,MAAM,QAAQ;AAGrB,IAAI;AACF,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,UAAM,QAAQ,IAAI,aAAa,IAAI,YAAY;AAC/C,UAAM,SAAS,IAAI,aAAa,IAAI,YAAY;AAChD,UAAM,UAAU,IAAI,aAAa,IAAI,WAAW;AAChD,UAAM,SAAS,IAAI,aAAa,IAAI,cAAc;AAClD,UAAM,gBAAgB,SAAS,mBAAmB,MAAM,IAAI;AAE5D,UAAM,WAAW,IAAI,aAAa,IAAI,mBAAmB;AACzD,UAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;AAE1C,QAAI,WAAW,QAAQ;AACrB,cAAQ,KAAK,IAAI,MAAM;AAAA,QAAyB,OAAO;AAAA,WAAc,aAAa,EAAE,CAAC;AAAA,IACvF;AAEA,QAAI,SAAS,QAAQ;AACnB,UAAI,WAAW,OAAO,WAAW;AAC/B,eAAO,EAAE,WAAW,MAAM,CAAC;AAAA,MAC7B,OAAO;AAGL,YAAI,WAAW,MAAM,YAAY;AAC/B,kBAAQ,KAAK,4CAA4C,QAAQ,OAAO,SAAS;AAAA,QACnF;AACA,YAAI,aAAa,OAAO,YAAY;AAAA,MACtC;AAAA,IACF;AAEA,QAAI,YAAY,OAAO;AACrB,YAAM,UAAU,WAAW,SAAS,MAAM,GAAG,IAAI,CAAC;AAClD,YAAM,QAAQ,QAAQ,MAAM,MAAM,GAAG,IAAI,CAAC;AAC1C,UAAI,MAAM,SAAS,QAAQ,QAAQ;AACjC,cAAM,QAAQ,CAAC,OAAO;AACpB,0BAAgB,EAAE,MAAM,IAAI,QAAQ,kBAAkB,YAAY,KAAK,CAAC;AAAA,QAC1E,CAAC;AAAA,MACH,WAAW,MAAM,WAAW,QAAQ,QAAQ;AAC1C,cAAM,QAAQ,CAAC,IAAI,MAAM;AACvB,0BAAgB;AAAA,YACd,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,QAAQ,QAAQ,CAAC;AAAA,YACjB,YAAY;AAAA,UACd,CAAC;AACD,sBAAY,EAAE;AAAA,QAChB,CAAC;AAAA,MACH,OAAO;AACL,gBAAQ,MAAM,IAAI,MAAM,gDAAgD,GAAG,OAAO,OAAO;AAAA,MAC3F;AAAA,IACF;AAWA,QAAI,aAAa,OAAO,OAAO;AAC/B,QAAI,WAAW,MAAM,aAAa;AAChC,UAAI,aAAa,OAAO,WAAW;AACnC,UAAI,aAAa,OAAO,cAAc;AAAA,IACxC;AAAA,EAUF;AACF,SAAS,GAAG;AACV,UAAQ,MAAM,mCAAmC,CAAC;AACpD;AAGO,MAAM,UAAU;AAAA,EACrB,cAAc,wBAAC;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,OAMR;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAnBc;AAAA,EAqBd,UAAU,wBAAC,iBAAyB;AAAA,IAClC,MAAM;AAAA,IACN,SAAS;AAAA,EACX,IAHU;AAAA,EAKV,WAAW,wBAAC,EAAC,QAAQ,WAAAF,WAAS,OAA8C;AAAA,IAC1E,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAAA;AAAA,EACF,IAJW;AAAA,EAMX,kBAAkB,wBAAC,EAAC,WAAAA,WAAS,OAA8B;AAAA,IACzD,MAAM;AAAA,IACN,WAAWA;AAAA,IACX,WAAW,EAAC,YAAY,aAAY;AAAA,EACtC,IAJkB;AAAA,EAMlB,qBAAqB,wBAAC;AAAA,IACE,WAAAA;AAAA,IACA;AAAA,IACA,WAAAC;AAAA,IACA;AAAA,EACF,OAKf;AAAA,IACL,MAAM;AAAA,IACN,WAAWD;AAAA,IACX,WAAW;AAAA,MACT,YAAY;AAAA,MACZ;AAAA,MACA,YAAYC;AAAA,MACZ;AAAA,IACF;AAAA,EACF,IAnBqB;AAAA,EAqBrB,WAAW,wBAAC,EAAC,WAAAD,WAAS,OAA8B;AAAA,IAClD,MAAM;AAAA,IACN,WAAAA;AAAA,EACF,IAHW;AAAA,EAKX,eAAe,wBAAC,EAAC,cAAa,OAAkC;AAAA,IAC9D,MAAM;AAAA,IACN;AAAA,EACF,IAHe;AAAA,EAKf,eAAe,8BAAO;AAAA,IACpB,MAAM;AAAA,EACR,IAFe;AAAA,EAIf,gBAAgB,wBAAC,EAAC,WAAU,OAA+B;AAAA,IACzD,MAAM;AAAA,IACN;AAAA,EACF,IAHgB;AAIlB;","names":["config","publicKey","accountId","actions"]} \ No newline at end of file diff --git a/static/js-loaded-globally/nearjs/0.9.7/esm/state.js b/static/js-loaded-globally/nearjs/0.9.7/esm/state.js deleted file mode 100644 index 634b993..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/esm/state.js +++ /dev/null @@ -1,172 +0,0 @@ -/* ⋈ 🏃🏻💨 FastNear API - ESM (@fastnear/api version 0.9.7) */ -/* https://www.npmjs.com/package/@fastnear/api/v/0.9.7 */ -var __defProp = Object.defineProperty; -var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); -import { - lsSet, - lsGet, - publicKeyFromPrivate -} from "@fastnear/utils"; -import { WalletAdapter } from "@fastnear/wallet-adapter"; -const WIDGET_URL = "https://js.cdn.fastnear.com"; -const DEFAULT_NETWORK_ID = "mainnet"; -const NETWORKS = { - testnet: { - networkId: "testnet", - nodeUrl: "https://rpc.testnet.fastnear.com/" - }, - mainnet: { - networkId: "mainnet", - nodeUrl: "https://rpc.mainnet.fastnear.com/" - } -}; -let _config = lsGet("config") || { - ...NETWORKS[DEFAULT_NETWORK_ID] -}; -let _state = lsGet("state") || {}; -const onAdapterStateUpdate = /* @__PURE__ */ __name((state) => { - console.log("Adapter state update:", state); - const { accountId, lastWalletId, privateKey } = state; - update({ - accountId: accountId || void 0, - lastWalletId: lastWalletId || void 0, - ...privateKey ? { privateKey } : {} - }); -}, "onAdapterStateUpdate"); -const getWalletAdapterState = /* @__PURE__ */ __name(() => { - return { - publicKey: _state.publicKey, - accountId: _state.accountId, - lastWalletId: _state.lastWalletId, - networkId: _config.networkId - }; -}, "getWalletAdapterState"); -let _adapter = new WalletAdapter({ - onStateUpdate: onAdapterStateUpdate, - lastState: getWalletAdapterState(), - widgetUrl: WIDGET_URL -}); -try { - _state.publicKey = _state.privateKey ? publicKeyFromPrivate(_state.privateKey) : null; -} catch (e) { - console.error("Error parsing private key:", e); - _state.privateKey = null; - lsSet("nonce", null); -} -let _txHistory = lsGet("txHistory") || {}; -const _unbroadcastedEvents = { - account: [], - tx: [] -}; -const events = { - _eventListeners: { - account: /* @__PURE__ */ new Set(), - tx: /* @__PURE__ */ new Set() - }, - notifyAccountListeners: /* @__PURE__ */ __name((accountId) => { - if (events._eventListeners.account.size === 0) { - _unbroadcastedEvents.account.push(accountId); - return; - } - events._eventListeners.account.forEach((callback) => { - try { - callback(accountId); - } catch (e) { - console.error(e); - } - }); - }, "notifyAccountListeners"), - notifyTxListeners: /* @__PURE__ */ __name((tx) => { - if (events._eventListeners.tx.size === 0) { - _unbroadcastedEvents.tx.push(tx); - return; - } - events._eventListeners.tx.forEach((callback) => { - try { - callback(tx); - } catch (e) { - console.error(e); - } - }); - }, "notifyTxListeners"), - onAccount: /* @__PURE__ */ __name((callback) => { - events._eventListeners.account.add(callback); - if (_unbroadcastedEvents.account.length > 0) { - const accountEvent = _unbroadcastedEvents.account; - _unbroadcastedEvents.account = []; - accountEvent.forEach(events.notifyAccountListeners); - } - }, "onAccount"), - onTx: /* @__PURE__ */ __name((callback) => { - events._eventListeners.tx.add(callback); - if (_unbroadcastedEvents.tx.length > 0) { - const txEvent = _unbroadcastedEvents.tx; - _unbroadcastedEvents.tx = []; - txEvent.forEach(events.notifyTxListeners); - } - }, "onTx") -}; -const update = /* @__PURE__ */ __name((newState) => { - const oldState = _state; - _state = { ..._state, ...newState }; - lsSet("state", { - accountId: _state.accountId, - privateKey: _state.privateKey, - lastWalletId: _state.lastWalletId, - accessKeyContractId: _state.accessKeyContractId - }); - if (newState.hasOwnProperty("privateKey") && newState.privateKey !== oldState.privateKey) { - _state.publicKey = newState.privateKey ? publicKeyFromPrivate(newState.privateKey) : null; - lsSet("nonce", null); - } - if (newState.accountId !== oldState.accountId) { - events.notifyAccountListeners(newState.accountId); - } - if (newState.hasOwnProperty("lastWalletId") && newState.lastWalletId !== oldState.lastWalletId || newState.hasOwnProperty("accountId") && newState.accountId !== oldState.accountId || newState.hasOwnProperty("privateKey") && newState.privateKey !== oldState.privateKey) { - _adapter.setState(getWalletAdapterState()); - } -}, "update"); -const updateTxHistory = /* @__PURE__ */ __name((txStatus) => { - const txId = txStatus.txId; - _txHistory[txId] = { - ..._txHistory[txId] || {}, - ...txStatus, - updateTimestamp: Date.now() - }; - lsSet("txHistory", _txHistory); - events.notifyTxListeners(_txHistory[txId]); -}, "updateTxHistory"); -const getConfig = /* @__PURE__ */ __name(() => { - return _config; -}, "getConfig"); -const getTxHistory = /* @__PURE__ */ __name(() => { - return _txHistory; -}, "getTxHistory"); -const setConfig = /* @__PURE__ */ __name((newConf) => { - _config = { ...NETWORKS[newConf.networkId], ...newConf }; - lsSet("config", _config); -}, "setConfig"); -const resetTxHistory = /* @__PURE__ */ __name(() => { - _txHistory = {}; - lsSet("txHistory", _txHistory); -}, "resetTxHistory"); -export { - DEFAULT_NETWORK_ID, - NETWORKS, - WIDGET_URL, - _adapter, - _config, - _state, - _txHistory, - _unbroadcastedEvents, - events, - getConfig, - getTxHistory, - getWalletAdapterState, - onAdapterStateUpdate, - resetTxHistory, - setConfig, - update, - updateTxHistory -}; -//# sourceMappingURL=state.js.map diff --git a/static/js-loaded-globally/nearjs/0.9.7/esm/state.js.map b/static/js-loaded-globally/nearjs/0.9.7/esm/state.js.map deleted file mode 100644 index c970fe7..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/esm/state.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/state.ts"],"sourcesContent":["import {\n lsSet,\n lsGet,\n publicKeyFromPrivate,\n} from \"@fastnear/utils\";\nimport {WalletAdapter} from \"@fastnear/wallet-adapter\";\n\nexport const WIDGET_URL = \"https://js.cdn.fastnear.com\";\n\nexport const DEFAULT_NETWORK_ID = \"mainnet\";\nexport const NETWORKS = {\n testnet: {\n networkId: \"testnet\",\n nodeUrl: \"https://rpc.testnet.fastnear.com/\",\n },\n mainnet: {\n networkId: \"mainnet\",\n nodeUrl: \"https://rpc.mainnet.fastnear.com/\",\n },\n};\n\nexport interface NetworkConfig {\n networkId: string;\n nodeUrl?: string;\n walletUrl?: string;\n helperUrl?: string;\n explorerUrl?: string;\n\n [key: string]: any;\n}\n\nexport interface AppState {\n accountId?: string | null;\n privateKey?: string | null;\n lastWalletId?: string | null;\n publicKey?: string | null;\n accessKeyContractId?: string | null;\n\n [key: string]: any;\n}\n\nexport interface TxStatus {\n txId: string;\n updateTimestamp?: number;\n\n [key: string]: any;\n}\n\nexport type TxHistory = Record;\n\nexport interface EventListeners {\n account: Set<(accountId: string) => void>;\n tx: Set<(tx: TxStatus) => void>;\n}\n\nexport interface UnbroadcastedEvents {\n account: string[];\n tx: TxStatus[];\n}\n\nexport interface WalletAdapterState {\n publicKey?: string | null;\n privateKey?: string | null;\n accountId?: string | null;\n lastWalletId?: string | null;\n networkId: string;\n}\n\n\n// Load config from localStorage or default to the network's config\nexport let _config: NetworkConfig = lsGet(\"config\") || {\n ...NETWORKS[DEFAULT_NETWORK_ID]\n};\n\n// Load application state from localStorage\nexport let _state: AppState = lsGet(\"state\") || {};\n\n// Triggered by the wallet adapter\nexport const onAdapterStateUpdate = (state: WalletAdapterState) => {\n console.log(\"Adapter state update:\", state);\n const { accountId, lastWalletId, privateKey } = state;\n update({\n accountId: accountId || undefined,\n lastWalletId: lastWalletId || undefined,\n ...(privateKey ? { privateKey } : {}),\n });\n}\n\nexport const getWalletAdapterState = (): WalletAdapterState => {\n return {\n publicKey: _state.publicKey,\n accountId: _state.accountId,\n lastWalletId: _state.lastWalletId,\n networkId: _config.networkId,\n };\n}\n\n// We can create an adapter instance here\nexport let _adapter = new WalletAdapter({\n onStateUpdate: onAdapterStateUpdate,\n lastState: getWalletAdapterState(),\n widgetUrl: WIDGET_URL,\n});\n\n// Attempt to set publicKey if we have a privateKey\ntry {\n _state.publicKey = _state.privateKey\n ? publicKeyFromPrivate(_state.privateKey)\n : null;\n} catch (e) {\n console.error(\"Error parsing private key:\", e);\n _state.privateKey = null;\n lsSet(\"nonce\", null);\n}\n\n// Transaction history\nexport let _txHistory: TxHistory = lsGet(\"txHistory\") || {};\n\n\nexport const _unbroadcastedEvents: UnbroadcastedEvents = {\n account: [],\n tx: [],\n};\n\n// events / listeners\nexport const events = {\n _eventListeners: {\n account: new Set(),\n tx: new Set(),\n },\n\n notifyAccountListeners: (accountId: string) => {\n if (events._eventListeners.account.size === 0) {\n _unbroadcastedEvents.account.push(accountId);\n return;\n }\n events._eventListeners.account.forEach((callback: any) => {\n try {\n callback(accountId);\n } catch (e) {\n console.error(e);\n }\n });\n },\n\n notifyTxListeners: (tx: TxStatus) => {\n if (events._eventListeners.tx.size === 0) {\n _unbroadcastedEvents.tx.push(tx);\n return;\n }\n events._eventListeners.tx.forEach((callback: any) => {\n try {\n callback(tx);\n } catch (e) {\n console.error(e);\n }\n });\n },\n\n onAccount: (callback: (accountId: string) => void) => {\n events._eventListeners.account.add(callback);\n if (_unbroadcastedEvents.account.length > 0) {\n const accountEvent = _unbroadcastedEvents.account;\n _unbroadcastedEvents.account = [];\n accountEvent.forEach(events.notifyAccountListeners);\n }\n },\n\n onTx: (callback: (tx: TxStatus) => void): void => {\n events._eventListeners.tx.add(callback);\n if (_unbroadcastedEvents.tx.length > 0) {\n const txEvent = _unbroadcastedEvents.tx;\n _unbroadcastedEvents.tx = [];\n txEvent.forEach(events.notifyTxListeners);\n }\n }\n}\n\n// Mutators\n// @todo: in favor of limiting when out of alpha\n// but haven't given it enough thought ~ mike\nexport const update = (newState: Partial) => {\n const oldState = _state;\n _state = {..._state, ...newState};\n\n lsSet(\"state\", {\n accountId: _state.accountId,\n privateKey: _state.privateKey,\n lastWalletId: _state.lastWalletId,\n accessKeyContractId: _state.accessKeyContractId,\n });\n\n if (\n newState.hasOwnProperty(\"privateKey\") &&\n newState.privateKey !== oldState.privateKey\n ) {\n _state.publicKey = newState.privateKey\n ? publicKeyFromPrivate(newState.privateKey as string)\n : null;\n lsSet(\"nonce\", null);\n }\n\n if (newState.accountId !== oldState.accountId) {\n events.notifyAccountListeners(newState.accountId as string);\n }\n\n if (\n (newState.hasOwnProperty(\"lastWalletId\") &&\n newState.lastWalletId !== oldState.lastWalletId) ||\n (newState.hasOwnProperty(\"accountId\") &&\n newState.accountId !== oldState.accountId) ||\n (newState.hasOwnProperty(\"privateKey\") &&\n newState.privateKey !== oldState.privateKey)\n ) {\n _adapter.setState(getWalletAdapterState());\n }\n}\n\nexport const updateTxHistory = (txStatus: TxStatus) => {\n const txId = txStatus.txId;\n _txHistory[txId] = {\n ...(_txHistory[txId] || {}),\n ...txStatus,\n updateTimestamp: Date.now(),\n };\n lsSet(\"txHistory\", _txHistory);\n events.notifyTxListeners(_txHistory[txId]);\n}\n\nexport const getConfig = (): NetworkConfig => {\n return _config;\n}\n\nexport const getTxHistory = (): TxHistory => {\n return _txHistory;\n}\n\n// Exposed \"write\" functions\nexport const setConfig = (newConf: NetworkConfig): void => {\n _config = { ...NETWORKS[newConf.networkId], ...newConf };\n lsSet(\"config\", _config);\n}\n\nexport const resetTxHistory = (): void => {\n _txHistory = {};\n lsSet(\"txHistory\", _txHistory);\n}\n"],"mappings":";;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAQ,qBAAoB;AAErB,MAAM,aAAa;AAEnB,MAAM,qBAAqB;AAC3B,MAAM,WAAW;AAAA,EACtB,SAAS;AAAA,IACP,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AAAA,EACA,SAAS;AAAA,IACP,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AACF;AAmDO,IAAI,UAAyB,MAAM,QAAQ,KAAK;AAAA,EACrD,GAAG,SAAS,kBAAkB;AAChC;AAGO,IAAI,SAAmB,MAAM,OAAO,KAAK,CAAC;AAG1C,MAAM,uBAAuB,wBAAC,UAA8B;AACjE,UAAQ,IAAI,yBAAyB,KAAK;AAC1C,QAAM,EAAE,WAAW,cAAc,WAAW,IAAI;AAChD,SAAO;AAAA,IACL,WAAW,aAAa;AAAA,IACxB,cAAc,gBAAgB;AAAA,IAC9B,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,EACrC,CAAC;AACH,GARoC;AAU7B,MAAM,wBAAwB,6BAA0B;AAC7D,SAAO;AAAA,IACL,WAAW,OAAO;AAAA,IAClB,WAAW,OAAO;AAAA,IAClB,cAAc,OAAO;AAAA,IACrB,WAAW,QAAQ;AAAA,EACrB;AACF,GAPqC;AAU9B,IAAI,WAAW,IAAI,cAAc;AAAA,EACtC,eAAe;AAAA,EACf,WAAW,sBAAsB;AAAA,EACjC,WAAW;AACb,CAAC;AAGD,IAAI;AACF,SAAO,YAAY,OAAO,aACtB,qBAAqB,OAAO,UAAU,IACtC;AACN,SAAS,GAAG;AACV,UAAQ,MAAM,8BAA8B,CAAC;AAC7C,SAAO,aAAa;AACpB,QAAM,SAAS,IAAI;AACrB;AAGO,IAAI,aAAwB,MAAM,WAAW,KAAK,CAAC;AAGnD,MAAM,uBAA4C;AAAA,EACvD,SAAS,CAAC;AAAA,EACV,IAAI,CAAC;AACP;AAGO,MAAM,SAAS;AAAA,EACpB,iBAAiB;AAAA,IACf,SAAS,oBAAI,IAAI;AAAA,IACjB,IAAI,oBAAI,IAAI;AAAA,EACd;AAAA,EAEA,wBAAwB,wBAAC,cAAsB;AAC7C,QAAI,OAAO,gBAAgB,QAAQ,SAAS,GAAG;AAC7C,2BAAqB,QAAQ,KAAK,SAAS;AAC3C;AAAA,IACF;AACA,WAAO,gBAAgB,QAAQ,QAAQ,CAAC,aAAkB;AACxD,UAAI;AACF,iBAAS,SAAS;AAAA,MACpB,SAAS,GAAG;AACV,gBAAQ,MAAM,CAAC;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH,GAZwB;AAAA,EAcxB,mBAAmB,wBAAC,OAAiB;AACnC,QAAI,OAAO,gBAAgB,GAAG,SAAS,GAAG;AACxC,2BAAqB,GAAG,KAAK,EAAE;AAC/B;AAAA,IACF;AACA,WAAO,gBAAgB,GAAG,QAAQ,CAAC,aAAkB;AACnD,UAAI;AACF,iBAAS,EAAE;AAAA,MACb,SAAS,GAAG;AACV,gBAAQ,MAAM,CAAC;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH,GAZmB;AAAA,EAcnB,WAAW,wBAAC,aAA0C;AACpD,WAAO,gBAAgB,QAAQ,IAAI,QAAQ;AAC3C,QAAI,qBAAqB,QAAQ,SAAS,GAAG;AAC3C,YAAM,eAAe,qBAAqB;AAC1C,2BAAqB,UAAU,CAAC;AAChC,mBAAa,QAAQ,OAAO,sBAAsB;AAAA,IACpD;AAAA,EACF,GAPW;AAAA,EASX,MAAM,wBAAC,aAA2C;AAChD,WAAO,gBAAgB,GAAG,IAAI,QAAQ;AACtC,QAAI,qBAAqB,GAAG,SAAS,GAAG;AACtC,YAAM,UAAU,qBAAqB;AACrC,2BAAqB,KAAK,CAAC;AAC3B,cAAQ,QAAQ,OAAO,iBAAiB;AAAA,IAC1C;AAAA,EACF,GAPM;AAQR;AAKO,MAAM,SAAS,wBAAC,aAAgC;AACrD,QAAM,WAAW;AACjB,WAAS,EAAC,GAAG,QAAQ,GAAG,SAAQ;AAEhC,QAAM,SAAS;AAAA,IACb,WAAW,OAAO;AAAA,IAClB,YAAY,OAAO;AAAA,IACnB,cAAc,OAAO;AAAA,IACrB,qBAAqB,OAAO;AAAA,EAC9B,CAAC;AAED,MACE,SAAS,eAAe,YAAY,KACpC,SAAS,eAAe,SAAS,YACjC;AACA,WAAO,YAAY,SAAS,aACxB,qBAAqB,SAAS,UAAoB,IAClD;AACJ,UAAM,SAAS,IAAI;AAAA,EACrB;AAEA,MAAI,SAAS,cAAc,SAAS,WAAW;AAC7C,WAAO,uBAAuB,SAAS,SAAmB;AAAA,EAC5D;AAEA,MACG,SAAS,eAAe,cAAc,KACrC,SAAS,iBAAiB,SAAS,gBACpC,SAAS,eAAe,WAAW,KAClC,SAAS,cAAc,SAAS,aACjC,SAAS,eAAe,YAAY,KACnC,SAAS,eAAe,SAAS,YACnC;AACA,aAAS,SAAS,sBAAsB,CAAC;AAAA,EAC3C;AACF,GAnCsB;AAqCf,MAAM,kBAAkB,wBAAC,aAAuB;AACrD,QAAM,OAAO,SAAS;AACtB,aAAW,IAAI,IAAI;AAAA,IACjB,GAAI,WAAW,IAAI,KAAK,CAAC;AAAA,IACzB,GAAG;AAAA,IACH,iBAAiB,KAAK,IAAI;AAAA,EAC5B;AACA,QAAM,aAAa,UAAU;AAC7B,SAAO,kBAAkB,WAAW,IAAI,CAAC;AAC3C,GAT+B;AAWxB,MAAM,YAAY,6BAAqB;AAC5C,SAAO;AACT,GAFyB;AAIlB,MAAM,eAAe,6BAAiB;AAC3C,SAAO;AACT,GAF4B;AAKrB,MAAM,YAAY,wBAAC,YAAiC;AACzD,YAAU,EAAE,GAAG,SAAS,QAAQ,SAAS,GAAG,GAAG,QAAQ;AACvD,QAAM,UAAU,OAAO;AACzB,GAHyB;AAKlB,MAAM,iBAAiB,6BAAY;AACxC,eAAa,CAAC;AACd,QAAM,aAAa,UAAU;AAC/B,GAH8B;","names":[]} \ No newline at end of file diff --git a/static/js-loaded-globally/nearjs/0.9.7/umd/browser.global.js b/static/js-loaded-globally/nearjs/0.9.7/umd/browser.global.js deleted file mode 100644 index f4d0f26..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/umd/browser.global.js +++ /dev/null @@ -1,4379 +0,0 @@ -/* ⋈ 🏃🏻💨 FastNear API - IIFE/UMD (@fastnear/api version 0.9.7) */ -/* https://www.npmjs.com/package/@fastnear/api/v/0.9.7 */ -"use strict"; -var near = (() => { - var __defProp = Object.defineProperty; - var __getOwnPropDesc = Object.getOwnPropertyDescriptor; - var __getOwnPropNames = Object.getOwnPropertyNames; - var __hasOwnProp = Object.prototype.hasOwnProperty; - var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); - var __export = (target, all) => { - for (var name in all) - __defProp(target, name, { get: all[name], enumerable: true }); - }; - var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); - } - return to; - }; - var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2); - - // src/index.ts - var src_exports3 = {}; - __export(src_exports3, { - MaxBlockDelayMs: () => MaxBlockDelayMs, - accountId: () => accountId, - actions: () => actions, - afterTxSent: () => afterTxSent, - authStatus: () => authStatus, - config: () => config, - event: () => event, - exp: () => exp2, - generateTxId: () => generateTxId, - getPublicKeyForContract: () => getPublicKeyForContract, - localTxHistory: () => localTxHistory, - publicKey: () => publicKey, - queryAccessKey: () => queryAccessKey, - queryAccount: () => queryAccount, - queryBlock: () => queryBlock, - queryTx: () => queryTx, - requestSignIn: () => requestSignIn, - selected: () => selected, - sendRpc: () => sendRpc, - sendTx: () => sendTx, - sendTxToRpc: () => sendTxToRpc, - signOut: () => signOut, - state: () => state, - utils: () => utils, - view: () => view, - withBlockId: () => withBlockId - }); - - // ../../node_modules/big.js/big.mjs - var DP = 20; - var RM = 1; - var MAX_DP = 1e6; - var MAX_POWER = 1e6; - var NE = -7; - var PE = 21; - var STRICT = false; - var NAME = "[big.js] "; - var INVALID = NAME + "Invalid "; - var INVALID_DP = INVALID + "decimal places"; - var INVALID_RM = INVALID + "rounding mode"; - var DIV_BY_ZERO = NAME + "Division by zero"; - var P = {}; - var UNDEFINED = void 0; - var NUMERIC = /^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i; - function _Big_() { - function Big2(n) { - var x = this; - if (!(x instanceof Big2)) return n === UNDEFINED ? _Big_() : new Big2(n); - if (n instanceof Big2) { - x.s = n.s; - x.e = n.e; - x.c = n.c.slice(); - } else { - if (typeof n !== "string") { - if (Big2.strict === true && typeof n !== "bigint") { - throw TypeError(INVALID + "value"); - } - n = n === 0 && 1 / n < 0 ? "-0" : String(n); - } - parse(x, n); - } - x.constructor = Big2; - } - __name(Big2, "Big"); - Big2.prototype = P; - Big2.DP = DP; - Big2.RM = RM; - Big2.NE = NE; - Big2.PE = PE; - Big2.strict = STRICT; - Big2.roundDown = 0; - Big2.roundHalfUp = 1; - Big2.roundHalfEven = 2; - Big2.roundUp = 3; - return Big2; - } - __name(_Big_, "_Big_"); - function parse(x, n) { - var e, i, nl; - if (!NUMERIC.test(n)) { - throw Error(INVALID + "number"); - } - x.s = n.charAt(0) == "-" ? (n = n.slice(1), -1) : 1; - if ((e = n.indexOf(".")) > -1) n = n.replace(".", ""); - if ((i = n.search(/e/i)) > 0) { - if (e < 0) e = i; - e += +n.slice(i + 1); - n = n.substring(0, i); - } else if (e < 0) { - e = n.length; - } - nl = n.length; - for (i = 0; i < nl && n.charAt(i) == "0"; ) ++i; - if (i == nl) { - x.c = [x.e = 0]; - } else { - for (; nl > 0 && n.charAt(--nl) == "0"; ) ; - x.e = e - i - 1; - x.c = []; - for (e = 0; i <= nl; ) x.c[e++] = +n.charAt(i++); - } - return x; - } - __name(parse, "parse"); - function round(x, sd, rm, more) { - var xc = x.c; - if (rm === UNDEFINED) rm = x.constructor.RM; - if (rm !== 0 && rm !== 1 && rm !== 2 && rm !== 3) { - throw Error(INVALID_RM); - } - if (sd < 1) { - more = rm === 3 && (more || !!xc[0]) || sd === 0 && (rm === 1 && xc[0] >= 5 || rm === 2 && (xc[0] > 5 || xc[0] === 5 && (more || xc[1] !== UNDEFINED))); - xc.length = 1; - if (more) { - x.e = x.e - sd + 1; - xc[0] = 1; - } else { - xc[0] = x.e = 0; - } - } else if (sd < xc.length) { - more = rm === 1 && xc[sd] >= 5 || rm === 2 && (xc[sd] > 5 || xc[sd] === 5 && (more || xc[sd + 1] !== UNDEFINED || xc[sd - 1] & 1)) || rm === 3 && (more || !!xc[0]); - xc.length = sd; - if (more) { - for (; ++xc[--sd] > 9; ) { - xc[sd] = 0; - if (sd === 0) { - ++x.e; - xc.unshift(1); - break; - } - } - } - for (sd = xc.length; !xc[--sd]; ) xc.pop(); - } - return x; - } - __name(round, "round"); - function stringify(x, doExponential, isNonzero) { - var e = x.e, s = x.c.join(""), n = s.length; - if (doExponential) { - s = s.charAt(0) + (n > 1 ? "." + s.slice(1) : "") + (e < 0 ? "e" : "e+") + e; - } else if (e < 0) { - for (; ++e; ) s = "0" + s; - s = "0." + s; - } else if (e > 0) { - if (++e > n) { - for (e -= n; e--; ) s += "0"; - } else if (e < n) { - s = s.slice(0, e) + "." + s.slice(e); - } - } else if (n > 1) { - s = s.charAt(0) + "." + s.slice(1); - } - return x.s < 0 && isNonzero ? "-" + s : s; - } - __name(stringify, "stringify"); - P.abs = function() { - var x = new this.constructor(this); - x.s = 1; - return x; - }; - P.cmp = function(y) { - var isneg, x = this, xc = x.c, yc = (y = new x.constructor(y)).c, i = x.s, j = y.s, k = x.e, l = y.e; - if (!xc[0] || !yc[0]) return !xc[0] ? !yc[0] ? 0 : -j : i; - if (i != j) return i; - isneg = i < 0; - if (k != l) return k > l ^ isneg ? 1 : -1; - j = (k = xc.length) < (l = yc.length) ? k : l; - for (i = -1; ++i < j; ) { - if (xc[i] != yc[i]) return xc[i] > yc[i] ^ isneg ? 1 : -1; - } - return k == l ? 0 : k > l ^ isneg ? 1 : -1; - }; - P.div = function(y) { - var x = this, Big2 = x.constructor, a = x.c, b = (y = new Big2(y)).c, k = x.s == y.s ? 1 : -1, dp = Big2.DP; - if (dp !== ~~dp || dp < 0 || dp > MAX_DP) { - throw Error(INVALID_DP); - } - if (!b[0]) { - throw Error(DIV_BY_ZERO); - } - if (!a[0]) { - y.s = k; - y.c = [y.e = 0]; - return y; - } - var bl, bt, n, cmp, ri, bz = b.slice(), ai = bl = b.length, al = a.length, r = a.slice(0, bl), rl = r.length, q = y, qc = q.c = [], qi = 0, p = dp + (q.e = x.e - y.e) + 1; - q.s = k; - k = p < 0 ? 0 : p; - bz.unshift(0); - for (; rl++ < bl; ) r.push(0); - do { - for (n = 0; n < 10; n++) { - if (bl != (rl = r.length)) { - cmp = bl > rl ? 1 : -1; - } else { - for (ri = -1, cmp = 0; ++ri < bl; ) { - if (b[ri] != r[ri]) { - cmp = b[ri] > r[ri] ? 1 : -1; - break; - } - } - } - if (cmp < 0) { - for (bt = rl == bl ? b : bz; rl; ) { - if (r[--rl] < bt[rl]) { - ri = rl; - for (; ri && !r[--ri]; ) r[ri] = 9; - --r[ri]; - r[rl] += 10; - } - r[rl] -= bt[rl]; - } - for (; !r[0]; ) r.shift(); - } else { - break; - } - } - qc[qi++] = cmp ? n : ++n; - if (r[0] && cmp) r[rl] = a[ai] || 0; - else r = [a[ai]]; - } while ((ai++ < al || r[0] !== UNDEFINED) && k--); - if (!qc[0] && qi != 1) { - qc.shift(); - q.e--; - p--; - } - if (qi > p) round(q, p, Big2.RM, r[0] !== UNDEFINED); - return q; - }; - P.eq = function(y) { - return this.cmp(y) === 0; - }; - P.gt = function(y) { - return this.cmp(y) > 0; - }; - P.gte = function(y) { - return this.cmp(y) > -1; - }; - P.lt = function(y) { - return this.cmp(y) < 0; - }; - P.lte = function(y) { - return this.cmp(y) < 1; - }; - P.minus = P.sub = function(y) { - var i, j, t, xlty, x = this, Big2 = x.constructor, a = x.s, b = (y = new Big2(y)).s; - if (a != b) { - y.s = -b; - return x.plus(y); - } - var xc = x.c.slice(), xe = x.e, yc = y.c, ye = y.e; - if (!xc[0] || !yc[0]) { - if (yc[0]) { - y.s = -b; - } else if (xc[0]) { - y = new Big2(x); - } else { - y.s = 1; - } - return y; - } - if (a = xe - ye) { - if (xlty = a < 0) { - a = -a; - t = xc; - } else { - ye = xe; - t = yc; - } - t.reverse(); - for (b = a; b--; ) t.push(0); - t.reverse(); - } else { - j = ((xlty = xc.length < yc.length) ? xc : yc).length; - for (a = b = 0; b < j; b++) { - if (xc[b] != yc[b]) { - xlty = xc[b] < yc[b]; - break; - } - } - } - if (xlty) { - t = xc; - xc = yc; - yc = t; - y.s = -y.s; - } - if ((b = (j = yc.length) - (i = xc.length)) > 0) for (; b--; ) xc[i++] = 0; - for (b = i; j > a; ) { - if (xc[--j] < yc[j]) { - for (i = j; i && !xc[--i]; ) xc[i] = 9; - --xc[i]; - xc[j] += 10; - } - xc[j] -= yc[j]; - } - for (; xc[--b] === 0; ) xc.pop(); - for (; xc[0] === 0; ) { - xc.shift(); - --ye; - } - if (!xc[0]) { - y.s = 1; - xc = [ye = 0]; - } - y.c = xc; - y.e = ye; - return y; - }; - P.mod = function(y) { - var ygtx, x = this, Big2 = x.constructor, a = x.s, b = (y = new Big2(y)).s; - if (!y.c[0]) { - throw Error(DIV_BY_ZERO); - } - x.s = y.s = 1; - ygtx = y.cmp(x) == 1; - x.s = a; - y.s = b; - if (ygtx) return new Big2(x); - a = Big2.DP; - b = Big2.RM; - Big2.DP = Big2.RM = 0; - x = x.div(y); - Big2.DP = a; - Big2.RM = b; - return this.minus(x.times(y)); - }; - P.neg = function() { - var x = new this.constructor(this); - x.s = -x.s; - return x; - }; - P.plus = P.add = function(y) { - var e, k, t, x = this, Big2 = x.constructor; - y = new Big2(y); - if (x.s != y.s) { - y.s = -y.s; - return x.minus(y); - } - var xe = x.e, xc = x.c, ye = y.e, yc = y.c; - if (!xc[0] || !yc[0]) { - if (!yc[0]) { - if (xc[0]) { - y = new Big2(x); - } else { - y.s = x.s; - } - } - return y; - } - xc = xc.slice(); - if (e = xe - ye) { - if (e > 0) { - ye = xe; - t = yc; - } else { - e = -e; - t = xc; - } - t.reverse(); - for (; e--; ) t.push(0); - t.reverse(); - } - if (xc.length - yc.length < 0) { - t = yc; - yc = xc; - xc = t; - } - e = yc.length; - for (k = 0; e; xc[e] %= 10) k = (xc[--e] = xc[e] + yc[e] + k) / 10 | 0; - if (k) { - xc.unshift(k); - ++ye; - } - for (e = xc.length; xc[--e] === 0; ) xc.pop(); - y.c = xc; - y.e = ye; - return y; - }; - P.pow = function(n) { - var x = this, one = new x.constructor("1"), y = one, isneg = n < 0; - if (n !== ~~n || n < -MAX_POWER || n > MAX_POWER) { - throw Error(INVALID + "exponent"); - } - if (isneg) n = -n; - for (; ; ) { - if (n & 1) y = y.times(x); - n >>= 1; - if (!n) break; - x = x.times(x); - } - return isneg ? one.div(y) : y; - }; - P.prec = function(sd, rm) { - if (sd !== ~~sd || sd < 1 || sd > MAX_DP) { - throw Error(INVALID + "precision"); - } - return round(new this.constructor(this), sd, rm); - }; - P.round = function(dp, rm) { - if (dp === UNDEFINED) dp = 0; - else if (dp !== ~~dp || dp < -MAX_DP || dp > MAX_DP) { - throw Error(INVALID_DP); - } - return round(new this.constructor(this), dp + this.e + 1, rm); - }; - P.sqrt = function() { - var r, c, t, x = this, Big2 = x.constructor, s = x.s, e = x.e, half = new Big2("0.5"); - if (!x.c[0]) return new Big2(x); - if (s < 0) { - throw Error(NAME + "No square root"); - } - s = Math.sqrt(+stringify(x, true, true)); - if (s === 0 || s === 1 / 0) { - c = x.c.join(""); - if (!(c.length + e & 1)) c += "0"; - s = Math.sqrt(c); - e = ((e + 1) / 2 | 0) - (e < 0 || e & 1); - r = new Big2((s == 1 / 0 ? "5e" : (s = s.toExponential()).slice(0, s.indexOf("e") + 1)) + e); - } else { - r = new Big2(s + ""); - } - e = r.e + (Big2.DP += 4); - do { - t = r; - r = half.times(t.plus(x.div(t))); - } while (t.c.slice(0, e).join("") !== r.c.slice(0, e).join("")); - return round(r, (Big2.DP -= 4) + r.e + 1, Big2.RM); - }; - P.times = P.mul = function(y) { - var c, x = this, Big2 = x.constructor, xc = x.c, yc = (y = new Big2(y)).c, a = xc.length, b = yc.length, i = x.e, j = y.e; - y.s = x.s == y.s ? 1 : -1; - if (!xc[0] || !yc[0]) { - y.c = [y.e = 0]; - return y; - } - y.e = i + j; - if (a < b) { - c = xc; - xc = yc; - yc = c; - j = a; - a = b; - b = j; - } - for (c = new Array(j = a + b); j--; ) c[j] = 0; - for (i = b; i--; ) { - b = 0; - for (j = a + i; j > i; ) { - b = c[j] + yc[i] * xc[j - i - 1] + b; - c[j--] = b % 10; - b = b / 10 | 0; - } - c[j] = b; - } - if (b) ++y.e; - else c.shift(); - for (i = c.length; !c[--i]; ) c.pop(); - y.c = c; - return y; - }; - P.toExponential = function(dp, rm) { - var x = this, n = x.c[0]; - if (dp !== UNDEFINED) { - if (dp !== ~~dp || dp < 0 || dp > MAX_DP) { - throw Error(INVALID_DP); - } - x = round(new x.constructor(x), ++dp, rm); - for (; x.c.length < dp; ) x.c.push(0); - } - return stringify(x, true, !!n); - }; - P.toFixed = function(dp, rm) { - var x = this, n = x.c[0]; - if (dp !== UNDEFINED) { - if (dp !== ~~dp || dp < 0 || dp > MAX_DP) { - throw Error(INVALID_DP); - } - x = round(new x.constructor(x), dp + x.e + 1, rm); - for (dp = dp + x.e + 1; x.c.length < dp; ) x.c.push(0); - } - return stringify(x, false, !!n); - }; - P[Symbol.for("nodejs.util.inspect.custom")] = P.toJSON = P.toString = function() { - var x = this, Big2 = x.constructor; - return stringify(x, x.e <= Big2.NE || x.e >= Big2.PE, !!x.c[0]); - }; - P.toNumber = function() { - var n = +stringify(this, true, true); - if (this.constructor.strict === true && !this.eq(n.toString())) { - throw Error(NAME + "Imprecise conversion"); - } - return n; - }; - P.toPrecision = function(sd, rm) { - var x = this, Big2 = x.constructor, n = x.c[0]; - if (sd !== UNDEFINED) { - if (sd !== ~~sd || sd < 1 || sd > MAX_DP) { - throw Error(INVALID + "precision"); - } - x = round(new Big2(x), sd, rm); - for (; x.c.length < sd; ) x.c.push(0); - } - return stringify(x, sd <= x.e || x.e <= Big2.NE || x.e >= Big2.PE, !!n); - }; - P.valueOf = function() { - var x = this, Big2 = x.constructor; - if (Big2.strict === true) { - throw Error(NAME + "valueOf disallowed"); - } - return stringify(x, x.e <= Big2.NE || x.e >= Big2.PE, true); - }; - var Big = _Big_(); - var big_default = Big; - - // ../utils/src/index.ts - var src_exports2 = {}; - __export(src_exports2, { - LsPrefix: () => LsPrefix, - SCHEMA: () => SCHEMA, - base64ToBytes: () => base64ToBytes, - bytesToBase64: () => bytesToBase64, - canSignWithLAK: () => canSignWithLAK, - convertUnit: () => convertUnit, - createDefaultStorage: () => createDefaultStorage, - deepCopy: () => deepCopy, - exp: () => exp, - fromBase58: () => base58_to_binary_default, - fromBase64: () => fromBase64, - fromHex: () => fromHex, - keyFromString: () => keyFromString, - keyToString: () => keyToString, - lsGet: () => lsGet, - lsSet: () => lsSet, - mapAction: () => mapAction, - mapTransaction: () => mapTransaction, - memoryStore: () => memoryStore, - parseJsonFromBytes: () => parseJsonFromBytes, - privateKeyFromRandom: () => privateKeyFromRandom, - publicKeyFromPrivate: () => publicKeyFromPrivate, - serializeSignedTransaction: () => serializeSignedTransaction, - serializeTransaction: () => serializeTransaction, - sha256: () => sha256, - signBytes: () => signBytes, - signHash: () => signHash, - storage: () => storage, - toBase58: () => binary_to_base58_default, - toBase64: () => toBase64, - toHex: () => toHex, - tryParseJson: () => tryParseJson, - txToJson: () => txToJson, - txToJsonStringified: () => txToJsonStringified - }); - - // ../../node_modules/@noble/hashes/esm/_assert.js - function isBytes(a) { - return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array"; - } - __name(isBytes, "isBytes"); - function abytes(b, ...lengths) { - if (!isBytes(b)) - throw new Error("Uint8Array expected"); - if (lengths.length > 0 && !lengths.includes(b.length)) - throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length); - } - __name(abytes, "abytes"); - function aexists(instance, checkFinished = true) { - if (instance.destroyed) - throw new Error("Hash instance has been destroyed"); - if (checkFinished && instance.finished) - throw new Error("Hash#digest() has already been called"); - } - __name(aexists, "aexists"); - function aoutput(out, instance) { - abytes(out); - const min = instance.outputLen; - if (out.length < min) { - throw new Error("digestInto() expects output buffer of length at least " + min); - } - } - __name(aoutput, "aoutput"); - - // ../../node_modules/@noble/hashes/esm/crypto.js - var crypto2 = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : void 0; - - // ../../node_modules/@noble/hashes/esm/utils.js - function createView(arr) { - return new DataView(arr.buffer, arr.byteOffset, arr.byteLength); - } - __name(createView, "createView"); - function rotr(word, shift) { - return word << 32 - shift | word >>> shift; - } - __name(rotr, "rotr"); - function utf8ToBytes(str) { - if (typeof str !== "string") - throw new Error("utf8ToBytes expected string, got " + typeof str); - return new Uint8Array(new TextEncoder().encode(str)); - } - __name(utf8ToBytes, "utf8ToBytes"); - function toBytes(data) { - if (typeof data === "string") - data = utf8ToBytes(data); - abytes(data); - return data; - } - __name(toBytes, "toBytes"); - var Hash = class { - static { - __name(this, "Hash"); - } - // Safe version that clones internal state - clone() { - return this._cloneInto(); - } - }; - function wrapConstructor(hashCons) { - const hashC = /* @__PURE__ */ __name((msg) => hashCons().update(toBytes(msg)).digest(), "hashC"); - const tmp = hashCons(); - hashC.outputLen = tmp.outputLen; - hashC.blockLen = tmp.blockLen; - hashC.create = () => hashCons(); - return hashC; - } - __name(wrapConstructor, "wrapConstructor"); - function randomBytes(bytesLength = 32) { - if (crypto2 && typeof crypto2.getRandomValues === "function") { - return crypto2.getRandomValues(new Uint8Array(bytesLength)); - } - if (crypto2 && typeof crypto2.randomBytes === "function") { - return crypto2.randomBytes(bytesLength); - } - throw new Error("crypto.getRandomValues must be defined"); - } - __name(randomBytes, "randomBytes"); - - // ../../node_modules/@noble/hashes/esm/_md.js - function setBigUint64(view2, byteOffset, value, isLE) { - if (typeof view2.setBigUint64 === "function") - return view2.setBigUint64(byteOffset, value, isLE); - const _32n2 = BigInt(32); - const _u32_max = BigInt(4294967295); - const wh = Number(value >> _32n2 & _u32_max); - const wl = Number(value & _u32_max); - const h = isLE ? 4 : 0; - const l = isLE ? 0 : 4; - view2.setUint32(byteOffset + h, wh, isLE); - view2.setUint32(byteOffset + l, wl, isLE); - } - __name(setBigUint64, "setBigUint64"); - function Chi(a, b, c) { - return a & b ^ ~a & c; - } - __name(Chi, "Chi"); - function Maj(a, b, c) { - return a & b ^ a & c ^ b & c; - } - __name(Maj, "Maj"); - var HashMD = class extends Hash { - static { - __name(this, "HashMD"); - } - constructor(blockLen, outputLen, padOffset, isLE) { - super(); - this.blockLen = blockLen; - this.outputLen = outputLen; - this.padOffset = padOffset; - this.isLE = isLE; - this.finished = false; - this.length = 0; - this.pos = 0; - this.destroyed = false; - this.buffer = new Uint8Array(blockLen); - this.view = createView(this.buffer); - } - update(data) { - aexists(this); - const { view: view2, buffer, blockLen } = this; - data = toBytes(data); - const len = data.length; - for (let pos = 0; pos < len; ) { - const take = Math.min(blockLen - this.pos, len - pos); - if (take === blockLen) { - const dataView = createView(data); - for (; blockLen <= len - pos; pos += blockLen) - this.process(dataView, pos); - continue; - } - buffer.set(data.subarray(pos, pos + take), this.pos); - this.pos += take; - pos += take; - if (this.pos === blockLen) { - this.process(view2, 0); - this.pos = 0; - } - } - this.length += data.length; - this.roundClean(); - return this; - } - digestInto(out) { - aexists(this); - aoutput(out, this); - this.finished = true; - const { buffer, view: view2, blockLen, isLE } = this; - let { pos } = this; - buffer[pos++] = 128; - this.buffer.subarray(pos).fill(0); - if (this.padOffset > blockLen - pos) { - this.process(view2, 0); - pos = 0; - } - for (let i = pos; i < blockLen; i++) - buffer[i] = 0; - setBigUint64(view2, blockLen - 8, BigInt(this.length * 8), isLE); - this.process(view2, 0); - const oview = createView(out); - const len = this.outputLen; - if (len % 4) - throw new Error("_sha2: outputLen should be aligned to 32bit"); - const outLen = len / 4; - const state2 = this.get(); - if (outLen > state2.length) - throw new Error("_sha2: outputLen bigger than state"); - for (let i = 0; i < outLen; i++) - oview.setUint32(4 * i, state2[i], isLE); - } - digest() { - const { buffer, outputLen } = this; - this.digestInto(buffer); - const res = buffer.slice(0, outputLen); - this.destroy(); - return res; - } - _cloneInto(to) { - to || (to = new this.constructor()); - to.set(...this.get()); - const { blockLen, buffer, length, finished, destroyed, pos } = this; - to.length = length; - to.pos = pos; - to.finished = finished; - to.destroyed = destroyed; - if (length % blockLen) - to.buffer.set(buffer); - return to; - } - }; - - // ../../node_modules/@noble/hashes/esm/_u64.js - var U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1); - var _32n = /* @__PURE__ */ BigInt(32); - function fromBig(n, le = false) { - if (le) - return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) }; - return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 }; - } - __name(fromBig, "fromBig"); - function split(lst, le = false) { - let Ah = new Uint32Array(lst.length); - let Al = new Uint32Array(lst.length); - for (let i = 0; i < lst.length; i++) { - const { h, l } = fromBig(lst[i], le); - [Ah[i], Al[i]] = [h, l]; - } - return [Ah, Al]; - } - __name(split, "split"); - var toBig = /* @__PURE__ */ __name((h, l) => BigInt(h >>> 0) << _32n | BigInt(l >>> 0), "toBig"); - var shrSH = /* @__PURE__ */ __name((h, _l, s) => h >>> s, "shrSH"); - var shrSL = /* @__PURE__ */ __name((h, l, s) => h << 32 - s | l >>> s, "shrSL"); - var rotrSH = /* @__PURE__ */ __name((h, l, s) => h >>> s | l << 32 - s, "rotrSH"); - var rotrSL = /* @__PURE__ */ __name((h, l, s) => h << 32 - s | l >>> s, "rotrSL"); - var rotrBH = /* @__PURE__ */ __name((h, l, s) => h << 64 - s | l >>> s - 32, "rotrBH"); - var rotrBL = /* @__PURE__ */ __name((h, l, s) => h >>> s - 32 | l << 64 - s, "rotrBL"); - var rotr32H = /* @__PURE__ */ __name((_h, l) => l, "rotr32H"); - var rotr32L = /* @__PURE__ */ __name((h, _l) => h, "rotr32L"); - var rotlSH = /* @__PURE__ */ __name((h, l, s) => h << s | l >>> 32 - s, "rotlSH"); - var rotlSL = /* @__PURE__ */ __name((h, l, s) => l << s | h >>> 32 - s, "rotlSL"); - var rotlBH = /* @__PURE__ */ __name((h, l, s) => l << s - 32 | h >>> 64 - s, "rotlBH"); - var rotlBL = /* @__PURE__ */ __name((h, l, s) => h << s - 32 | l >>> 64 - s, "rotlBL"); - function add(Ah, Al, Bh, Bl) { - const l = (Al >>> 0) + (Bl >>> 0); - return { h: Ah + Bh + (l / 2 ** 32 | 0) | 0, l: l | 0 }; - } - __name(add, "add"); - var add3L = /* @__PURE__ */ __name((Al, Bl, Cl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0), "add3L"); - var add3H = /* @__PURE__ */ __name((low, Ah, Bh, Ch) => Ah + Bh + Ch + (low / 2 ** 32 | 0) | 0, "add3H"); - var add4L = /* @__PURE__ */ __name((Al, Bl, Cl, Dl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0), "add4L"); - var add4H = /* @__PURE__ */ __name((low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0, "add4H"); - var add5L = /* @__PURE__ */ __name((Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0), "add5L"); - var add5H = /* @__PURE__ */ __name((low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0, "add5H"); - var u64 = { - fromBig, - split, - toBig, - shrSH, - shrSL, - rotrSH, - rotrSL, - rotrBH, - rotrBL, - rotr32H, - rotr32L, - rotlSH, - rotlSL, - rotlBH, - rotlBL, - add, - add3L, - add3H, - add4L, - add4H, - add5H, - add5L - }; - var u64_default = u64; - - // ../../node_modules/@noble/hashes/esm/sha512.js - var [SHA512_Kh, SHA512_Kl] = /* @__PURE__ */ (() => u64_default.split([ - "0x428a2f98d728ae22", - "0x7137449123ef65cd", - "0xb5c0fbcfec4d3b2f", - "0xe9b5dba58189dbbc", - "0x3956c25bf348b538", - "0x59f111f1b605d019", - "0x923f82a4af194f9b", - "0xab1c5ed5da6d8118", - "0xd807aa98a3030242", - "0x12835b0145706fbe", - "0x243185be4ee4b28c", - "0x550c7dc3d5ffb4e2", - "0x72be5d74f27b896f", - "0x80deb1fe3b1696b1", - "0x9bdc06a725c71235", - "0xc19bf174cf692694", - "0xe49b69c19ef14ad2", - "0xefbe4786384f25e3", - "0x0fc19dc68b8cd5b5", - "0x240ca1cc77ac9c65", - "0x2de92c6f592b0275", - "0x4a7484aa6ea6e483", - "0x5cb0a9dcbd41fbd4", - "0x76f988da831153b5", - "0x983e5152ee66dfab", - "0xa831c66d2db43210", - "0xb00327c898fb213f", - "0xbf597fc7beef0ee4", - "0xc6e00bf33da88fc2", - "0xd5a79147930aa725", - "0x06ca6351e003826f", - "0x142929670a0e6e70", - "0x27b70a8546d22ffc", - "0x2e1b21385c26c926", - "0x4d2c6dfc5ac42aed", - "0x53380d139d95b3df", - "0x650a73548baf63de", - "0x766a0abb3c77b2a8", - "0x81c2c92e47edaee6", - "0x92722c851482353b", - "0xa2bfe8a14cf10364", - "0xa81a664bbc423001", - "0xc24b8b70d0f89791", - "0xc76c51a30654be30", - "0xd192e819d6ef5218", - "0xd69906245565a910", - "0xf40e35855771202a", - "0x106aa07032bbd1b8", - "0x19a4c116b8d2d0c8", - "0x1e376c085141ab53", - "0x2748774cdf8eeb99", - "0x34b0bcb5e19b48a8", - "0x391c0cb3c5c95a63", - "0x4ed8aa4ae3418acb", - "0x5b9cca4f7763e373", - "0x682e6ff3d6b2b8a3", - "0x748f82ee5defb2fc", - "0x78a5636f43172f60", - "0x84c87814a1f0ab72", - "0x8cc702081a6439ec", - "0x90befffa23631e28", - "0xa4506cebde82bde9", - "0xbef9a3f7b2c67915", - "0xc67178f2e372532b", - "0xca273eceea26619c", - "0xd186b8c721c0c207", - "0xeada7dd6cde0eb1e", - "0xf57d4f7fee6ed178", - "0x06f067aa72176fba", - "0x0a637dc5a2c898a6", - "0x113f9804bef90dae", - "0x1b710b35131c471b", - "0x28db77f523047d84", - "0x32caab7b40c72493", - "0x3c9ebe0a15c9bebc", - "0x431d67c49c100d4c", - "0x4cc5d4becb3e42b6", - "0x597f299cfc657e2a", - "0x5fcb6fab3ad6faec", - "0x6c44198c4a475817" - ].map((n) => BigInt(n))))(); - var SHA512_W_H = /* @__PURE__ */ new Uint32Array(80); - var SHA512_W_L = /* @__PURE__ */ new Uint32Array(80); - var SHA512 = class extends HashMD { - static { - __name(this, "SHA512"); - } - constructor() { - super(128, 64, 16, false); - this.Ah = 1779033703 | 0; - this.Al = 4089235720 | 0; - this.Bh = 3144134277 | 0; - this.Bl = 2227873595 | 0; - this.Ch = 1013904242 | 0; - this.Cl = 4271175723 | 0; - this.Dh = 2773480762 | 0; - this.Dl = 1595750129 | 0; - this.Eh = 1359893119 | 0; - this.El = 2917565137 | 0; - this.Fh = 2600822924 | 0; - this.Fl = 725511199 | 0; - this.Gh = 528734635 | 0; - this.Gl = 4215389547 | 0; - this.Hh = 1541459225 | 0; - this.Hl = 327033209 | 0; - } - // prettier-ignore - get() { - const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this; - return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl]; - } - // prettier-ignore - set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) { - this.Ah = Ah | 0; - this.Al = Al | 0; - this.Bh = Bh | 0; - this.Bl = Bl | 0; - this.Ch = Ch | 0; - this.Cl = Cl | 0; - this.Dh = Dh | 0; - this.Dl = Dl | 0; - this.Eh = Eh | 0; - this.El = El | 0; - this.Fh = Fh | 0; - this.Fl = Fl | 0; - this.Gh = Gh | 0; - this.Gl = Gl | 0; - this.Hh = Hh | 0; - this.Hl = Hl | 0; - } - process(view2, offset) { - for (let i = 0; i < 16; i++, offset += 4) { - SHA512_W_H[i] = view2.getUint32(offset); - SHA512_W_L[i] = view2.getUint32(offset += 4); - } - for (let i = 16; i < 80; i++) { - const W15h = SHA512_W_H[i - 15] | 0; - const W15l = SHA512_W_L[i - 15] | 0; - const s0h = u64_default.rotrSH(W15h, W15l, 1) ^ u64_default.rotrSH(W15h, W15l, 8) ^ u64_default.shrSH(W15h, W15l, 7); - const s0l = u64_default.rotrSL(W15h, W15l, 1) ^ u64_default.rotrSL(W15h, W15l, 8) ^ u64_default.shrSL(W15h, W15l, 7); - const W2h = SHA512_W_H[i - 2] | 0; - const W2l = SHA512_W_L[i - 2] | 0; - const s1h = u64_default.rotrSH(W2h, W2l, 19) ^ u64_default.rotrBH(W2h, W2l, 61) ^ u64_default.shrSH(W2h, W2l, 6); - const s1l = u64_default.rotrSL(W2h, W2l, 19) ^ u64_default.rotrBL(W2h, W2l, 61) ^ u64_default.shrSL(W2h, W2l, 6); - const SUMl = u64_default.add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]); - const SUMh = u64_default.add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]); - SHA512_W_H[i] = SUMh | 0; - SHA512_W_L[i] = SUMl | 0; - } - let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this; - for (let i = 0; i < 80; i++) { - const sigma1h = u64_default.rotrSH(Eh, El, 14) ^ u64_default.rotrSH(Eh, El, 18) ^ u64_default.rotrBH(Eh, El, 41); - const sigma1l = u64_default.rotrSL(Eh, El, 14) ^ u64_default.rotrSL(Eh, El, 18) ^ u64_default.rotrBL(Eh, El, 41); - const CHIh = Eh & Fh ^ ~Eh & Gh; - const CHIl = El & Fl ^ ~El & Gl; - const T1ll = u64_default.add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]); - const T1h = u64_default.add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]); - const T1l = T1ll | 0; - const sigma0h = u64_default.rotrSH(Ah, Al, 28) ^ u64_default.rotrBH(Ah, Al, 34) ^ u64_default.rotrBH(Ah, Al, 39); - const sigma0l = u64_default.rotrSL(Ah, Al, 28) ^ u64_default.rotrBL(Ah, Al, 34) ^ u64_default.rotrBL(Ah, Al, 39); - const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch; - const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl; - Hh = Gh | 0; - Hl = Gl | 0; - Gh = Fh | 0; - Gl = Fl | 0; - Fh = Eh | 0; - Fl = El | 0; - ({ h: Eh, l: El } = u64_default.add(Dh | 0, Dl | 0, T1h | 0, T1l | 0)); - Dh = Ch | 0; - Dl = Cl | 0; - Ch = Bh | 0; - Cl = Bl | 0; - Bh = Ah | 0; - Bl = Al | 0; - const All = u64_default.add3L(T1l, sigma0l, MAJl); - Ah = u64_default.add3H(All, T1h, sigma0h, MAJh); - Al = All | 0; - } - ({ h: Ah, l: Al } = u64_default.add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0)); - ({ h: Bh, l: Bl } = u64_default.add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0)); - ({ h: Ch, l: Cl } = u64_default.add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0)); - ({ h: Dh, l: Dl } = u64_default.add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0)); - ({ h: Eh, l: El } = u64_default.add(this.Eh | 0, this.El | 0, Eh | 0, El | 0)); - ({ h: Fh, l: Fl } = u64_default.add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0)); - ({ h: Gh, l: Gl } = u64_default.add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0)); - ({ h: Hh, l: Hl } = u64_default.add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0)); - this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl); - } - roundClean() { - SHA512_W_H.fill(0); - SHA512_W_L.fill(0); - } - destroy() { - this.buffer.fill(0); - this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - } - }; - var sha512 = /* @__PURE__ */ wrapConstructor(() => new SHA512()); - - // ../../node_modules/@noble/curves/esm/abstract/utils.js - var _0n = /* @__PURE__ */ BigInt(0); - var _1n = /* @__PURE__ */ BigInt(1); - var _2n = /* @__PURE__ */ BigInt(2); - function isBytes2(a) { - return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array"; - } - __name(isBytes2, "isBytes"); - function abytes2(item) { - if (!isBytes2(item)) - throw new Error("Uint8Array expected"); - } - __name(abytes2, "abytes"); - function abool(title, value) { - if (typeof value !== "boolean") - throw new Error(title + " boolean expected, got " + value); - } - __name(abool, "abool"); - var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0")); - function bytesToHex(bytes) { - abytes2(bytes); - let hex = ""; - for (let i = 0; i < bytes.length; i++) { - hex += hexes[bytes[i]]; - } - return hex; - } - __name(bytesToHex, "bytesToHex"); - function hexToNumber(hex) { - if (typeof hex !== "string") - throw new Error("hex string expected, got " + typeof hex); - return hex === "" ? _0n : BigInt("0x" + hex); - } - __name(hexToNumber, "hexToNumber"); - var asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 }; - function asciiToBase16(ch) { - if (ch >= asciis._0 && ch <= asciis._9) - return ch - asciis._0; - if (ch >= asciis.A && ch <= asciis.F) - return ch - (asciis.A - 10); - if (ch >= asciis.a && ch <= asciis.f) - return ch - (asciis.a - 10); - return; - } - __name(asciiToBase16, "asciiToBase16"); - function hexToBytes(hex) { - if (typeof hex !== "string") - throw new Error("hex string expected, got " + typeof hex); - const hl = hex.length; - const al = hl / 2; - if (hl % 2) - throw new Error("hex string expected, got unpadded hex of length " + hl); - const array = new Uint8Array(al); - for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) { - const n1 = asciiToBase16(hex.charCodeAt(hi)); - const n2 = asciiToBase16(hex.charCodeAt(hi + 1)); - if (n1 === void 0 || n2 === void 0) { - const char = hex[hi] + hex[hi + 1]; - throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi); - } - array[ai] = n1 * 16 + n2; - } - return array; - } - __name(hexToBytes, "hexToBytes"); - function bytesToNumberBE(bytes) { - return hexToNumber(bytesToHex(bytes)); - } - __name(bytesToNumberBE, "bytesToNumberBE"); - function bytesToNumberLE(bytes) { - abytes2(bytes); - return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse())); - } - __name(bytesToNumberLE, "bytesToNumberLE"); - function numberToBytesBE(n, len) { - return hexToBytes(n.toString(16).padStart(len * 2, "0")); - } - __name(numberToBytesBE, "numberToBytesBE"); - function numberToBytesLE(n, len) { - return numberToBytesBE(n, len).reverse(); - } - __name(numberToBytesLE, "numberToBytesLE"); - function ensureBytes(title, hex, expectedLength) { - let res; - if (typeof hex === "string") { - try { - res = hexToBytes(hex); - } catch (e) { - throw new Error(title + " must be hex string or Uint8Array, cause: " + e); - } - } else if (isBytes2(hex)) { - res = Uint8Array.from(hex); - } else { - throw new Error(title + " must be hex string or Uint8Array"); - } - const len = res.length; - if (typeof expectedLength === "number" && len !== expectedLength) - throw new Error(title + " of length " + expectedLength + " expected, got " + len); - return res; - } - __name(ensureBytes, "ensureBytes"); - function concatBytes(...arrays) { - let sum = 0; - for (let i = 0; i < arrays.length; i++) { - const a = arrays[i]; - abytes2(a); - sum += a.length; - } - const res = new Uint8Array(sum); - for (let i = 0, pad = 0; i < arrays.length; i++) { - const a = arrays[i]; - res.set(a, pad); - pad += a.length; - } - return res; - } - __name(concatBytes, "concatBytes"); - var isPosBig = /* @__PURE__ */ __name((n) => typeof n === "bigint" && _0n <= n, "isPosBig"); - function inRange(n, min, max) { - return isPosBig(n) && isPosBig(min) && isPosBig(max) && min <= n && n < max; - } - __name(inRange, "inRange"); - function aInRange(title, n, min, max) { - if (!inRange(n, min, max)) - throw new Error("expected valid " + title + ": " + min + " <= n < " + max + ", got " + n); - } - __name(aInRange, "aInRange"); - function bitLen(n) { - let len; - for (len = 0; n > _0n; n >>= _1n, len += 1) - ; - return len; - } - __name(bitLen, "bitLen"); - var bitMask = /* @__PURE__ */ __name((n) => (_2n << BigInt(n - 1)) - _1n, "bitMask"); - var validatorFns = { - bigint: /* @__PURE__ */ __name((val) => typeof val === "bigint", "bigint"), - function: /* @__PURE__ */ __name((val) => typeof val === "function", "function"), - boolean: /* @__PURE__ */ __name((val) => typeof val === "boolean", "boolean"), - string: /* @__PURE__ */ __name((val) => typeof val === "string", "string"), - stringOrUint8Array: /* @__PURE__ */ __name((val) => typeof val === "string" || isBytes2(val), "stringOrUint8Array"), - isSafeInteger: /* @__PURE__ */ __name((val) => Number.isSafeInteger(val), "isSafeInteger"), - array: /* @__PURE__ */ __name((val) => Array.isArray(val), "array"), - field: /* @__PURE__ */ __name((val, object) => object.Fp.isValid(val), "field"), - hash: /* @__PURE__ */ __name((val) => typeof val === "function" && Number.isSafeInteger(val.outputLen), "hash") - }; - function validateObject(object, validators, optValidators = {}) { - const checkField = /* @__PURE__ */ __name((fieldName, type, isOptional) => { - const checkVal = validatorFns[type]; - if (typeof checkVal !== "function") - throw new Error("invalid validator function"); - const val = object[fieldName]; - if (isOptional && val === void 0) - return; - if (!checkVal(val, object)) { - throw new Error("param " + String(fieldName) + " is invalid. Expected " + type + ", got " + val); - } - }, "checkField"); - for (const [fieldName, type] of Object.entries(validators)) - checkField(fieldName, type, false); - for (const [fieldName, type] of Object.entries(optValidators)) - checkField(fieldName, type, true); - return object; - } - __name(validateObject, "validateObject"); - function memoized(fn) { - const map = /* @__PURE__ */ new WeakMap(); - return (arg, ...args) => { - const val = map.get(arg); - if (val !== void 0) - return val; - const computed = fn(arg, ...args); - map.set(arg, computed); - return computed; - }; - } - __name(memoized, "memoized"); - - // ../../node_modules/@noble/curves/esm/abstract/modular.js - var _0n2 = BigInt(0); - var _1n2 = BigInt(1); - var _2n2 = /* @__PURE__ */ BigInt(2); - var _3n = /* @__PURE__ */ BigInt(3); - var _4n = /* @__PURE__ */ BigInt(4); - var _5n = /* @__PURE__ */ BigInt(5); - var _8n = /* @__PURE__ */ BigInt(8); - var _9n = /* @__PURE__ */ BigInt(9); - var _16n = /* @__PURE__ */ BigInt(16); - function mod(a, b) { - const result = a % b; - return result >= _0n2 ? result : b + result; - } - __name(mod, "mod"); - function pow(num, power, modulo) { - if (power < _0n2) - throw new Error("invalid exponent, negatives unsupported"); - if (modulo <= _0n2) - throw new Error("invalid modulus"); - if (modulo === _1n2) - return _0n2; - let res = _1n2; - while (power > _0n2) { - if (power & _1n2) - res = res * num % modulo; - num = num * num % modulo; - power >>= _1n2; - } - return res; - } - __name(pow, "pow"); - function pow2(x, power, modulo) { - let res = x; - while (power-- > _0n2) { - res *= res; - res %= modulo; - } - return res; - } - __name(pow2, "pow2"); - function invert(number, modulo) { - if (number === _0n2) - throw new Error("invert: expected non-zero number"); - if (modulo <= _0n2) - throw new Error("invert: expected positive modulus, got " + modulo); - let a = mod(number, modulo); - let b = modulo; - let x = _0n2, y = _1n2, u = _1n2, v = _0n2; - while (a !== _0n2) { - const q = b / a; - const r = b % a; - const m = x - u * q; - const n = y - v * q; - b = a, a = r, x = u, y = v, u = m, v = n; - } - const gcd = b; - if (gcd !== _1n2) - throw new Error("invert: does not exist"); - return mod(x, modulo); - } - __name(invert, "invert"); - function tonelliShanks(P2) { - const legendreC = (P2 - _1n2) / _2n2; - let Q, S, Z; - for (Q = P2 - _1n2, S = 0; Q % _2n2 === _0n2; Q /= _2n2, S++) - ; - for (Z = _2n2; Z < P2 && pow(Z, legendreC, P2) !== P2 - _1n2; Z++) { - if (Z > 1e3) - throw new Error("Cannot find square root: likely non-prime P"); - } - if (S === 1) { - const p1div4 = (P2 + _1n2) / _4n; - return /* @__PURE__ */ __name(function tonelliFast(Fp2, n) { - const root = Fp2.pow(n, p1div4); - if (!Fp2.eql(Fp2.sqr(root), n)) - throw new Error("Cannot find square root"); - return root; - }, "tonelliFast"); - } - const Q1div2 = (Q + _1n2) / _2n2; - return /* @__PURE__ */ __name(function tonelliSlow(Fp2, n) { - if (Fp2.pow(n, legendreC) === Fp2.neg(Fp2.ONE)) - throw new Error("Cannot find square root"); - let r = S; - let g = Fp2.pow(Fp2.mul(Fp2.ONE, Z), Q); - let x = Fp2.pow(n, Q1div2); - let b = Fp2.pow(n, Q); - while (!Fp2.eql(b, Fp2.ONE)) { - if (Fp2.eql(b, Fp2.ZERO)) - return Fp2.ZERO; - let m = 1; - for (let t2 = Fp2.sqr(b); m < r; m++) { - if (Fp2.eql(t2, Fp2.ONE)) - break; - t2 = Fp2.sqr(t2); - } - const ge = Fp2.pow(g, _1n2 << BigInt(r - m - 1)); - g = Fp2.sqr(ge); - x = Fp2.mul(x, ge); - b = Fp2.mul(b, g); - r = m; - } - return x; - }, "tonelliSlow"); - } - __name(tonelliShanks, "tonelliShanks"); - function FpSqrt(P2) { - if (P2 % _4n === _3n) { - const p1div4 = (P2 + _1n2) / _4n; - return /* @__PURE__ */ __name(function sqrt3mod4(Fp2, n) { - const root = Fp2.pow(n, p1div4); - if (!Fp2.eql(Fp2.sqr(root), n)) - throw new Error("Cannot find square root"); - return root; - }, "sqrt3mod4"); - } - if (P2 % _8n === _5n) { - const c1 = (P2 - _5n) / _8n; - return /* @__PURE__ */ __name(function sqrt5mod8(Fp2, n) { - const n2 = Fp2.mul(n, _2n2); - const v = Fp2.pow(n2, c1); - const nv = Fp2.mul(n, v); - const i = Fp2.mul(Fp2.mul(nv, _2n2), v); - const root = Fp2.mul(nv, Fp2.sub(i, Fp2.ONE)); - if (!Fp2.eql(Fp2.sqr(root), n)) - throw new Error("Cannot find square root"); - return root; - }, "sqrt5mod8"); - } - if (P2 % _16n === _9n) { - } - return tonelliShanks(P2); - } - __name(FpSqrt, "FpSqrt"); - var isNegativeLE = /* @__PURE__ */ __name((num, modulo) => (mod(num, modulo) & _1n2) === _1n2, "isNegativeLE"); - var FIELD_FIELDS = [ - "create", - "isValid", - "is0", - "neg", - "inv", - "sqrt", - "sqr", - "eql", - "add", - "sub", - "mul", - "pow", - "div", - "addN", - "subN", - "mulN", - "sqrN" - ]; - function validateField(field) { - const initial = { - ORDER: "bigint", - MASK: "bigint", - BYTES: "isSafeInteger", - BITS: "isSafeInteger" - }; - const opts = FIELD_FIELDS.reduce((map, val) => { - map[val] = "function"; - return map; - }, initial); - return validateObject(field, opts); - } - __name(validateField, "validateField"); - function FpPow(f, num, power) { - if (power < _0n2) - throw new Error("invalid exponent, negatives unsupported"); - if (power === _0n2) - return f.ONE; - if (power === _1n2) - return num; - let p = f.ONE; - let d = num; - while (power > _0n2) { - if (power & _1n2) - p = f.mul(p, d); - d = f.sqr(d); - power >>= _1n2; - } - return p; - } - __name(FpPow, "FpPow"); - function FpInvertBatch(f, nums) { - const tmp = new Array(nums.length); - const lastMultiplied = nums.reduce((acc, num, i) => { - if (f.is0(num)) - return acc; - tmp[i] = acc; - return f.mul(acc, num); - }, f.ONE); - const inverted = f.inv(lastMultiplied); - nums.reduceRight((acc, num, i) => { - if (f.is0(num)) - return acc; - tmp[i] = f.mul(acc, tmp[i]); - return f.mul(acc, num); - }, inverted); - return tmp; - } - __name(FpInvertBatch, "FpInvertBatch"); - function nLength(n, nBitLength) { - const _nBitLength = nBitLength !== void 0 ? nBitLength : n.toString(2).length; - const nByteLength = Math.ceil(_nBitLength / 8); - return { nBitLength: _nBitLength, nByteLength }; - } - __name(nLength, "nLength"); - function Field(ORDER, bitLen2, isLE = false, redef = {}) { - if (ORDER <= _0n2) - throw new Error("invalid field: expected ORDER > 0, got " + ORDER); - const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen2); - if (BYTES > 2048) - throw new Error("invalid field: expected ORDER of <= 2048 bytes"); - let sqrtP; - const f = Object.freeze({ - ORDER, - isLE, - BITS, - BYTES, - MASK: bitMask(BITS), - ZERO: _0n2, - ONE: _1n2, - create: /* @__PURE__ */ __name((num) => mod(num, ORDER), "create"), - isValid: /* @__PURE__ */ __name((num) => { - if (typeof num !== "bigint") - throw new Error("invalid field element: expected bigint, got " + typeof num); - return _0n2 <= num && num < ORDER; - }, "isValid"), - is0: /* @__PURE__ */ __name((num) => num === _0n2, "is0"), - isOdd: /* @__PURE__ */ __name((num) => (num & _1n2) === _1n2, "isOdd"), - neg: /* @__PURE__ */ __name((num) => mod(-num, ORDER), "neg"), - eql: /* @__PURE__ */ __name((lhs, rhs) => lhs === rhs, "eql"), - sqr: /* @__PURE__ */ __name((num) => mod(num * num, ORDER), "sqr"), - add: /* @__PURE__ */ __name((lhs, rhs) => mod(lhs + rhs, ORDER), "add"), - sub: /* @__PURE__ */ __name((lhs, rhs) => mod(lhs - rhs, ORDER), "sub"), - mul: /* @__PURE__ */ __name((lhs, rhs) => mod(lhs * rhs, ORDER), "mul"), - pow: /* @__PURE__ */ __name((num, power) => FpPow(f, num, power), "pow"), - div: /* @__PURE__ */ __name((lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER), "div"), - // Same as above, but doesn't normalize - sqrN: /* @__PURE__ */ __name((num) => num * num, "sqrN"), - addN: /* @__PURE__ */ __name((lhs, rhs) => lhs + rhs, "addN"), - subN: /* @__PURE__ */ __name((lhs, rhs) => lhs - rhs, "subN"), - mulN: /* @__PURE__ */ __name((lhs, rhs) => lhs * rhs, "mulN"), - inv: /* @__PURE__ */ __name((num) => invert(num, ORDER), "inv"), - sqrt: redef.sqrt || ((n) => { - if (!sqrtP) - sqrtP = FpSqrt(ORDER); - return sqrtP(f, n); - }), - invertBatch: /* @__PURE__ */ __name((lst) => FpInvertBatch(f, lst), "invertBatch"), - // TODO: do we really need constant cmov? - // We don't have const-time bigints anyway, so probably will be not very useful - cmov: /* @__PURE__ */ __name((a, b, c) => c ? b : a, "cmov"), - toBytes: /* @__PURE__ */ __name((num) => isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES), "toBytes"), - fromBytes: /* @__PURE__ */ __name((bytes) => { - if (bytes.length !== BYTES) - throw new Error("Field.fromBytes: expected " + BYTES + " bytes, got " + bytes.length); - return isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes); - }, "fromBytes") - }); - return Object.freeze(f); - } - __name(Field, "Field"); - - // ../../node_modules/@noble/curves/esm/abstract/curve.js - var _0n3 = BigInt(0); - var _1n3 = BigInt(1); - function constTimeNegate(condition, item) { - const neg = item.negate(); - return condition ? neg : item; - } - __name(constTimeNegate, "constTimeNegate"); - function validateW(W, bits) { - if (!Number.isSafeInteger(W) || W <= 0 || W > bits) - throw new Error("invalid window size, expected [1.." + bits + "], got W=" + W); - } - __name(validateW, "validateW"); - function calcWOpts(W, bits) { - validateW(W, bits); - const windows = Math.ceil(bits / W) + 1; - const windowSize = 2 ** (W - 1); - return { windows, windowSize }; - } - __name(calcWOpts, "calcWOpts"); - function validateMSMPoints(points, c) { - if (!Array.isArray(points)) - throw new Error("array expected"); - points.forEach((p, i) => { - if (!(p instanceof c)) - throw new Error("invalid point at index " + i); - }); - } - __name(validateMSMPoints, "validateMSMPoints"); - function validateMSMScalars(scalars, field) { - if (!Array.isArray(scalars)) - throw new Error("array of scalars expected"); - scalars.forEach((s, i) => { - if (!field.isValid(s)) - throw new Error("invalid scalar at index " + i); - }); - } - __name(validateMSMScalars, "validateMSMScalars"); - var pointPrecomputes = /* @__PURE__ */ new WeakMap(); - var pointWindowSizes = /* @__PURE__ */ new WeakMap(); - function getW(P2) { - return pointWindowSizes.get(P2) || 1; - } - __name(getW, "getW"); - function wNAF(c, bits) { - return { - constTimeNegate, - hasPrecomputes(elm) { - return getW(elm) !== 1; - }, - // non-const time multiplication ladder - unsafeLadder(elm, n, p = c.ZERO) { - let d = elm; - while (n > _0n3) { - if (n & _1n3) - p = p.add(d); - d = d.double(); - n >>= _1n3; - } - return p; - }, - /** - * Creates a wNAF precomputation window. Used for caching. - * Default window size is set by `utils.precompute()` and is equal to 8. - * Number of precomputed points depends on the curve size: - * 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where: - * - 𝑊 is the window size - * - 𝑛 is the bitlength of the curve order. - * For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224. - * @param elm Point instance - * @param W window size - * @returns precomputed point tables flattened to a single array - */ - precomputeWindow(elm, W) { - const { windows, windowSize } = calcWOpts(W, bits); - const points = []; - let p = elm; - let base = p; - for (let window2 = 0; window2 < windows; window2++) { - base = p; - points.push(base); - for (let i = 1; i < windowSize; i++) { - base = base.add(p); - points.push(base); - } - p = base.double(); - } - return points; - }, - /** - * Implements ec multiplication using precomputed tables and w-ary non-adjacent form. - * @param W window size - * @param precomputes precomputed tables - * @param n scalar (we don't check here, but should be less than curve order) - * @returns real and fake (for const-time) points - */ - wNAF(W, precomputes, n) { - const { windows, windowSize } = calcWOpts(W, bits); - let p = c.ZERO; - let f = c.BASE; - const mask = BigInt(2 ** W - 1); - const maxNumber = 2 ** W; - const shiftBy = BigInt(W); - for (let window2 = 0; window2 < windows; window2++) { - const offset = window2 * windowSize; - let wbits = Number(n & mask); - n >>= shiftBy; - if (wbits > windowSize) { - wbits -= maxNumber; - n += _1n3; - } - const offset1 = offset; - const offset2 = offset + Math.abs(wbits) - 1; - const cond1 = window2 % 2 !== 0; - const cond2 = wbits < 0; - if (wbits === 0) { - f = f.add(constTimeNegate(cond1, precomputes[offset1])); - } else { - p = p.add(constTimeNegate(cond2, precomputes[offset2])); - } - } - return { p, f }; - }, - /** - * Implements ec unsafe (non const-time) multiplication using precomputed tables and w-ary non-adjacent form. - * @param W window size - * @param precomputes precomputed tables - * @param n scalar (we don't check here, but should be less than curve order) - * @param acc accumulator point to add result of multiplication - * @returns point - */ - wNAFUnsafe(W, precomputes, n, acc = c.ZERO) { - const { windows, windowSize } = calcWOpts(W, bits); - const mask = BigInt(2 ** W - 1); - const maxNumber = 2 ** W; - const shiftBy = BigInt(W); - for (let window2 = 0; window2 < windows; window2++) { - const offset = window2 * windowSize; - if (n === _0n3) - break; - let wbits = Number(n & mask); - n >>= shiftBy; - if (wbits > windowSize) { - wbits -= maxNumber; - n += _1n3; - } - if (wbits === 0) - continue; - let curr = precomputes[offset + Math.abs(wbits) - 1]; - if (wbits < 0) - curr = curr.negate(); - acc = acc.add(curr); - } - return acc; - }, - getPrecomputes(W, P2, transform) { - let comp = pointPrecomputes.get(P2); - if (!comp) { - comp = this.precomputeWindow(P2, W); - if (W !== 1) - pointPrecomputes.set(P2, transform(comp)); - } - return comp; - }, - wNAFCached(P2, n, transform) { - const W = getW(P2); - return this.wNAF(W, this.getPrecomputes(W, P2, transform), n); - }, - wNAFCachedUnsafe(P2, n, transform, prev) { - const W = getW(P2); - if (W === 1) - return this.unsafeLadder(P2, n, prev); - return this.wNAFUnsafe(W, this.getPrecomputes(W, P2, transform), n, prev); - }, - // We calculate precomputes for elliptic curve point multiplication - // using windowed method. This specifies window size and - // stores precomputed values. Usually only base point would be precomputed. - setWindowSize(P2, W) { - validateW(W, bits); - pointWindowSizes.set(P2, W); - pointPrecomputes.delete(P2); - } - }; - } - __name(wNAF, "wNAF"); - function pippenger(c, fieldN, points, scalars) { - validateMSMPoints(points, c); - validateMSMScalars(scalars, fieldN); - if (points.length !== scalars.length) - throw new Error("arrays of points and scalars must have equal length"); - const zero = c.ZERO; - const wbits = bitLen(BigInt(points.length)); - const windowSize = wbits > 12 ? wbits - 3 : wbits > 4 ? wbits - 2 : wbits ? 2 : 1; - const MASK = (1 << windowSize) - 1; - const buckets = new Array(MASK + 1).fill(zero); - const lastBits = Math.floor((fieldN.BITS - 1) / windowSize) * windowSize; - let sum = zero; - for (let i = lastBits; i >= 0; i -= windowSize) { - buckets.fill(zero); - for (let j = 0; j < scalars.length; j++) { - const scalar = scalars[j]; - const wbits2 = Number(scalar >> BigInt(i) & BigInt(MASK)); - buckets[wbits2] = buckets[wbits2].add(points[j]); - } - let resI = zero; - for (let j = buckets.length - 1, sumI = zero; j > 0; j--) { - sumI = sumI.add(buckets[j]); - resI = resI.add(sumI); - } - sum = sum.add(resI); - if (i !== 0) - for (let j = 0; j < windowSize; j++) - sum = sum.double(); - } - return sum; - } - __name(pippenger, "pippenger"); - function validateBasic(curve) { - validateField(curve.Fp); - validateObject(curve, { - n: "bigint", - h: "bigint", - Gx: "field", - Gy: "field" - }, { - nBitLength: "isSafeInteger", - nByteLength: "isSafeInteger" - }); - return Object.freeze({ - ...nLength(curve.n, curve.nBitLength), - ...curve, - ...{ p: curve.Fp.ORDER } - }); - } - __name(validateBasic, "validateBasic"); - - // ../../node_modules/@noble/curves/esm/abstract/edwards.js - var _0n4 = BigInt(0); - var _1n4 = BigInt(1); - var _2n3 = BigInt(2); - var _8n2 = BigInt(8); - var VERIFY_DEFAULT = { zip215: true }; - function validateOpts(curve) { - const opts = validateBasic(curve); - validateObject(curve, { - hash: "function", - a: "bigint", - d: "bigint", - randomBytes: "function" - }, { - adjustScalarBytes: "function", - domain: "function", - uvRatio: "function", - mapToCurve: "function" - }); - return Object.freeze({ ...opts }); - } - __name(validateOpts, "validateOpts"); - function twistedEdwards(curveDef) { - const CURVE = validateOpts(curveDef); - const { Fp: Fp2, n: CURVE_ORDER, prehash, hash: cHash, randomBytes: randomBytes2, nByteLength, h: cofactor } = CURVE; - const MASK = _2n3 << BigInt(nByteLength * 8) - _1n4; - const modP = Fp2.create; - const Fn = Field(CURVE.n, CURVE.nBitLength); - const uvRatio2 = CURVE.uvRatio || ((u, v) => { - try { - return { isValid: true, value: Fp2.sqrt(u * Fp2.inv(v)) }; - } catch (e) { - return { isValid: false, value: _0n4 }; - } - }); - const adjustScalarBytes2 = CURVE.adjustScalarBytes || ((bytes) => bytes); - const domain = CURVE.domain || ((data, ctx, phflag) => { - abool("phflag", phflag); - if (ctx.length || phflag) - throw new Error("Contexts/pre-hash are not supported"); - return data; - }); - function aCoordinate(title, n) { - aInRange("coordinate " + title, n, _0n4, MASK); - } - __name(aCoordinate, "aCoordinate"); - function assertPoint(other) { - if (!(other instanceof Point)) - throw new Error("ExtendedPoint expected"); - } - __name(assertPoint, "assertPoint"); - const toAffineMemo = memoized((p, iz) => { - const { ex: x, ey: y, ez: z } = p; - const is0 = p.is0(); - if (iz == null) - iz = is0 ? _8n2 : Fp2.inv(z); - const ax = modP(x * iz); - const ay = modP(y * iz); - const zz = modP(z * iz); - if (is0) - return { x: _0n4, y: _1n4 }; - if (zz !== _1n4) - throw new Error("invZ was invalid"); - return { x: ax, y: ay }; - }); - const assertValidMemo = memoized((p) => { - const { a, d } = CURVE; - if (p.is0()) - throw new Error("bad point: ZERO"); - const { ex: X, ey: Y, ez: Z, et: T } = p; - const X2 = modP(X * X); - const Y2 = modP(Y * Y); - const Z2 = modP(Z * Z); - const Z4 = modP(Z2 * Z2); - const aX2 = modP(X2 * a); - const left = modP(Z2 * modP(aX2 + Y2)); - const right = modP(Z4 + modP(d * modP(X2 * Y2))); - if (left !== right) - throw new Error("bad point: equation left != right (1)"); - const XY = modP(X * Y); - const ZT = modP(Z * T); - if (XY !== ZT) - throw new Error("bad point: equation left != right (2)"); - return true; - }); - class Point { - static { - __name(this, "Point"); - } - constructor(ex, ey, ez, et) { - this.ex = ex; - this.ey = ey; - this.ez = ez; - this.et = et; - aCoordinate("x", ex); - aCoordinate("y", ey); - aCoordinate("z", ez); - aCoordinate("t", et); - Object.freeze(this); - } - get x() { - return this.toAffine().x; - } - get y() { - return this.toAffine().y; - } - static fromAffine(p) { - if (p instanceof Point) - throw new Error("extended point not allowed"); - const { x, y } = p || {}; - aCoordinate("x", x); - aCoordinate("y", y); - return new Point(x, y, _1n4, modP(x * y)); - } - static normalizeZ(points) { - const toInv = Fp2.invertBatch(points.map((p) => p.ez)); - return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine); - } - // Multiscalar Multiplication - static msm(points, scalars) { - return pippenger(Point, Fn, points, scalars); - } - // "Private method", don't use it directly - _setWindowSize(windowSize) { - wnaf.setWindowSize(this, windowSize); - } - // Not required for fromHex(), which always creates valid points. - // Could be useful for fromAffine(). - assertValidity() { - assertValidMemo(this); - } - // Compare one point to another. - equals(other) { - assertPoint(other); - const { ex: X1, ey: Y1, ez: Z1 } = this; - const { ex: X2, ey: Y2, ez: Z2 } = other; - const X1Z2 = modP(X1 * Z2); - const X2Z1 = modP(X2 * Z1); - const Y1Z2 = modP(Y1 * Z2); - const Y2Z1 = modP(Y2 * Z1); - return X1Z2 === X2Z1 && Y1Z2 === Y2Z1; - } - is0() { - return this.equals(Point.ZERO); - } - negate() { - return new Point(modP(-this.ex), this.ey, this.ez, modP(-this.et)); - } - // Fast algo for doubling Extended Point. - // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd - // Cost: 4M + 4S + 1*a + 6add + 1*2. - double() { - const { a } = CURVE; - const { ex: X1, ey: Y1, ez: Z1 } = this; - const A = modP(X1 * X1); - const B = modP(Y1 * Y1); - const C = modP(_2n3 * modP(Z1 * Z1)); - const D = modP(a * A); - const x1y1 = X1 + Y1; - const E = modP(modP(x1y1 * x1y1) - A - B); - const G2 = D + B; - const F = G2 - C; - const H = D - B; - const X3 = modP(E * F); - const Y3 = modP(G2 * H); - const T3 = modP(E * H); - const Z3 = modP(F * G2); - return new Point(X3, Y3, Z3, T3); - } - // Fast algo for adding 2 Extended Points. - // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd - // Cost: 9M + 1*a + 1*d + 7add. - add(other) { - assertPoint(other); - const { a, d } = CURVE; - const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this; - const { ex: X2, ey: Y2, ez: Z2, et: T2 } = other; - if (a === BigInt(-1)) { - const A2 = modP((Y1 - X1) * (Y2 + X2)); - const B2 = modP((Y1 + X1) * (Y2 - X2)); - const F2 = modP(B2 - A2); - if (F2 === _0n4) - return this.double(); - const C2 = modP(Z1 * _2n3 * T2); - const D2 = modP(T1 * _2n3 * Z2); - const E2 = D2 + C2; - const G3 = B2 + A2; - const H2 = D2 - C2; - const X32 = modP(E2 * F2); - const Y32 = modP(G3 * H2); - const T32 = modP(E2 * H2); - const Z32 = modP(F2 * G3); - return new Point(X32, Y32, Z32, T32); - } - const A = modP(X1 * X2); - const B = modP(Y1 * Y2); - const C = modP(T1 * d * T2); - const D = modP(Z1 * Z2); - const E = modP((X1 + Y1) * (X2 + Y2) - A - B); - const F = D - C; - const G2 = D + C; - const H = modP(B - a * A); - const X3 = modP(E * F); - const Y3 = modP(G2 * H); - const T3 = modP(E * H); - const Z3 = modP(F * G2); - return new Point(X3, Y3, Z3, T3); - } - subtract(other) { - return this.add(other.negate()); - } - wNAF(n) { - return wnaf.wNAFCached(this, n, Point.normalizeZ); - } - // Constant-time multiplication. - multiply(scalar) { - const n = scalar; - aInRange("scalar", n, _1n4, CURVE_ORDER); - const { p, f } = this.wNAF(n); - return Point.normalizeZ([p, f])[0]; - } - // Non-constant-time multiplication. Uses double-and-add algorithm. - // It's faster, but should only be used when you don't care about - // an exposed private key e.g. sig verification. - // Does NOT allow scalars higher than CURVE.n. - // Accepts optional accumulator to merge with multiply (important for sparse scalars) - multiplyUnsafe(scalar, acc = Point.ZERO) { - const n = scalar; - aInRange("scalar", n, _0n4, CURVE_ORDER); - if (n === _0n4) - return I; - if (this.is0() || n === _1n4) - return this; - return wnaf.wNAFCachedUnsafe(this, n, Point.normalizeZ, acc); - } - // Checks if point is of small order. - // If you add something to small order point, you will have "dirty" - // point with torsion component. - // Multiplies point by cofactor and checks if the result is 0. - isSmallOrder() { - return this.multiplyUnsafe(cofactor).is0(); - } - // Multiplies point by curve order and checks if the result is 0. - // Returns `false` is the point is dirty. - isTorsionFree() { - return wnaf.unsafeLadder(this, CURVE_ORDER).is0(); - } - // Converts Extended point to default (x, y) coordinates. - // Can accept precomputed Z^-1 - for example, from invertBatch. - toAffine(iz) { - return toAffineMemo(this, iz); - } - clearCofactor() { - const { h: cofactor2 } = CURVE; - if (cofactor2 === _1n4) - return this; - return this.multiplyUnsafe(cofactor2); - } - // Converts hash string or Uint8Array to Point. - // Uses algo from RFC8032 5.1.3. - static fromHex(hex, zip215 = false) { - const { d, a } = CURVE; - const len = Fp2.BYTES; - hex = ensureBytes("pointHex", hex, len); - abool("zip215", zip215); - const normed = hex.slice(); - const lastByte = hex[len - 1]; - normed[len - 1] = lastByte & ~128; - const y = bytesToNumberLE(normed); - const max = zip215 ? MASK : Fp2.ORDER; - aInRange("pointHex.y", y, _0n4, max); - const y2 = modP(y * y); - const u = modP(y2 - _1n4); - const v = modP(d * y2 - a); - let { isValid, value: x } = uvRatio2(u, v); - if (!isValid) - throw new Error("Point.fromHex: invalid y coordinate"); - const isXOdd = (x & _1n4) === _1n4; - const isLastByteOdd = (lastByte & 128) !== 0; - if (!zip215 && x === _0n4 && isLastByteOdd) - throw new Error("Point.fromHex: x=0 and x_0=1"); - if (isLastByteOdd !== isXOdd) - x = modP(-x); - return Point.fromAffine({ x, y }); - } - static fromPrivateKey(privKey) { - return getExtendedPublicKey(privKey).point; - } - toRawBytes() { - const { x, y } = this.toAffine(); - const bytes = numberToBytesLE(y, Fp2.BYTES); - bytes[bytes.length - 1] |= x & _1n4 ? 128 : 0; - return bytes; - } - toHex() { - return bytesToHex(this.toRawBytes()); - } - } - Point.BASE = new Point(CURVE.Gx, CURVE.Gy, _1n4, modP(CURVE.Gx * CURVE.Gy)); - Point.ZERO = new Point(_0n4, _1n4, _1n4, _0n4); - const { BASE: G, ZERO: I } = Point; - const wnaf = wNAF(Point, nByteLength * 8); - function modN(a) { - return mod(a, CURVE_ORDER); - } - __name(modN, "modN"); - function modN_LE(hash) { - return modN(bytesToNumberLE(hash)); - } - __name(modN_LE, "modN_LE"); - function getExtendedPublicKey(key) { - const len = Fp2.BYTES; - key = ensureBytes("private key", key, len); - const hashed = ensureBytes("hashed private key", cHash(key), 2 * len); - const head = adjustScalarBytes2(hashed.slice(0, len)); - const prefix = hashed.slice(len, 2 * len); - const scalar = modN_LE(head); - const point = G.multiply(scalar); - const pointBytes = point.toRawBytes(); - return { head, prefix, scalar, point, pointBytes }; - } - __name(getExtendedPublicKey, "getExtendedPublicKey"); - function getPublicKey(privKey) { - return getExtendedPublicKey(privKey).pointBytes; - } - __name(getPublicKey, "getPublicKey"); - function hashDomainToScalar(context = new Uint8Array(), ...msgs) { - const msg = concatBytes(...msgs); - return modN_LE(cHash(domain(msg, ensureBytes("context", context), !!prehash))); - } - __name(hashDomainToScalar, "hashDomainToScalar"); - function sign(msg, privKey, options = {}) { - msg = ensureBytes("message", msg); - if (prehash) - msg = prehash(msg); - const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey); - const r = hashDomainToScalar(options.context, prefix, msg); - const R = G.multiply(r).toRawBytes(); - const k = hashDomainToScalar(options.context, R, pointBytes, msg); - const s = modN(r + k * scalar); - aInRange("signature.s", s, _0n4, CURVE_ORDER); - const res = concatBytes(R, numberToBytesLE(s, Fp2.BYTES)); - return ensureBytes("result", res, Fp2.BYTES * 2); - } - __name(sign, "sign"); - const verifyOpts = VERIFY_DEFAULT; - function verify(sig, msg, publicKey2, options = verifyOpts) { - const { context, zip215 } = options; - const len = Fp2.BYTES; - sig = ensureBytes("signature", sig, 2 * len); - msg = ensureBytes("message", msg); - publicKey2 = ensureBytes("publicKey", publicKey2, len); - if (zip215 !== void 0) - abool("zip215", zip215); - if (prehash) - msg = prehash(msg); - const s = bytesToNumberLE(sig.slice(len, 2 * len)); - let A, R, SB; - try { - A = Point.fromHex(publicKey2, zip215); - R = Point.fromHex(sig.slice(0, len), zip215); - SB = G.multiplyUnsafe(s); - } catch (error) { - return false; - } - if (!zip215 && A.isSmallOrder()) - return false; - const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg); - const RkA = R.add(A.multiplyUnsafe(k)); - return RkA.subtract(SB).clearCofactor().equals(Point.ZERO); - } - __name(verify, "verify"); - G._setWindowSize(8); - const utils2 = { - getExtendedPublicKey, - // ed25519 private keys are uniform 32b. No need to check for modulo bias, like in secp256k1. - randomPrivateKey: /* @__PURE__ */ __name(() => randomBytes2(Fp2.BYTES), "randomPrivateKey"), - /** - * We're doing scalar multiplication (used in getPublicKey etc) with precomputed BASE_POINT - * values. This slows down first getPublicKey() by milliseconds (see Speed section), - * but allows to speed-up subsequent getPublicKey() calls up to 20x. - * @param windowSize 2, 4, 8, 16 - */ - precompute(windowSize = 8, point = Point.BASE) { - point._setWindowSize(windowSize); - point.multiply(BigInt(3)); - return point; - } - }; - return { - CURVE, - getPublicKey, - sign, - verify, - ExtendedPoint: Point, - utils: utils2 - }; - } - __name(twistedEdwards, "twistedEdwards"); - - // ../../node_modules/@noble/curves/esm/ed25519.js - var ED25519_P = BigInt("57896044618658097711785492504343953926634992332820282019728792003956564819949"); - var ED25519_SQRT_M1 = /* @__PURE__ */ BigInt("19681161376707505956807079304988542015446066515923890162744021073123829784752"); - var _0n5 = BigInt(0); - var _1n5 = BigInt(1); - var _2n4 = BigInt(2); - var _3n2 = BigInt(3); - var _5n2 = BigInt(5); - var _8n3 = BigInt(8); - function ed25519_pow_2_252_3(x) { - const _10n = BigInt(10), _20n = BigInt(20), _40n = BigInt(40), _80n = BigInt(80); - const P2 = ED25519_P; - const x2 = x * x % P2; - const b2 = x2 * x % P2; - const b4 = pow2(b2, _2n4, P2) * b2 % P2; - const b5 = pow2(b4, _1n5, P2) * x % P2; - const b10 = pow2(b5, _5n2, P2) * b5 % P2; - const b20 = pow2(b10, _10n, P2) * b10 % P2; - const b40 = pow2(b20, _20n, P2) * b20 % P2; - const b80 = pow2(b40, _40n, P2) * b40 % P2; - const b160 = pow2(b80, _80n, P2) * b80 % P2; - const b240 = pow2(b160, _80n, P2) * b80 % P2; - const b250 = pow2(b240, _10n, P2) * b10 % P2; - const pow_p_5_8 = pow2(b250, _2n4, P2) * x % P2; - return { pow_p_5_8, b2 }; - } - __name(ed25519_pow_2_252_3, "ed25519_pow_2_252_3"); - function adjustScalarBytes(bytes) { - bytes[0] &= 248; - bytes[31] &= 127; - bytes[31] |= 64; - return bytes; - } - __name(adjustScalarBytes, "adjustScalarBytes"); - function uvRatio(u, v) { - const P2 = ED25519_P; - const v3 = mod(v * v * v, P2); - const v7 = mod(v3 * v3 * v, P2); - const pow3 = ed25519_pow_2_252_3(u * v7).pow_p_5_8; - let x = mod(u * v3 * pow3, P2); - const vx2 = mod(v * x * x, P2); - const root1 = x; - const root2 = mod(x * ED25519_SQRT_M1, P2); - const useRoot1 = vx2 === u; - const useRoot2 = vx2 === mod(-u, P2); - const noRoot = vx2 === mod(-u * ED25519_SQRT_M1, P2); - if (useRoot1) - x = root1; - if (useRoot2 || noRoot) - x = root2; - if (isNegativeLE(x, P2)) - x = mod(-x, P2); - return { isValid: useRoot1 || useRoot2, value: x }; - } - __name(uvRatio, "uvRatio"); - var Fp = /* @__PURE__ */ (() => Field(ED25519_P, void 0, true))(); - var ed25519Defaults = /* @__PURE__ */ (() => ({ - // Param: a - a: BigInt(-1), - // Fp.create(-1) is proper; our way still works and is faster - // d is equal to -121665/121666 over finite field. - // Negative number is P - number, and division is invert(number, P) - d: BigInt("37095705934669439343138083508754565189542113879843219016388785533085940283555"), - // Finite field 𝔽p over which we'll do calculations; 2n**255n - 19n - Fp, - // Subgroup order: how many points curve has - // 2n**252n + 27742317777372353535851937790883648493n; - n: BigInt("7237005577332262213973186563042994240857116359379907606001950938285454250989"), - // Cofactor - h: _8n3, - // Base point (x, y) aka generator point - Gx: BigInt("15112221349535400772501151409588531511454012693041857206046113283949847762202"), - Gy: BigInt("46316835694926478169428394003475163141307993866256225615783033603165251855960"), - hash: sha512, - randomBytes, - adjustScalarBytes, - // dom2 - // Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3. - // Constant-time, u/√v - uvRatio - }))(); - var ed25519 = /* @__PURE__ */ (() => twistedEdwards(ed25519Defaults))(); - - // ../../node_modules/@noble/hashes/esm/sha256.js - var SHA256_K = /* @__PURE__ */ new Uint32Array([ - 1116352408, - 1899447441, - 3049323471, - 3921009573, - 961987163, - 1508970993, - 2453635748, - 2870763221, - 3624381080, - 310598401, - 607225278, - 1426881987, - 1925078388, - 2162078206, - 2614888103, - 3248222580, - 3835390401, - 4022224774, - 264347078, - 604807628, - 770255983, - 1249150122, - 1555081692, - 1996064986, - 2554220882, - 2821834349, - 2952996808, - 3210313671, - 3336571891, - 3584528711, - 113926993, - 338241895, - 666307205, - 773529912, - 1294757372, - 1396182291, - 1695183700, - 1986661051, - 2177026350, - 2456956037, - 2730485921, - 2820302411, - 3259730800, - 3345764771, - 3516065817, - 3600352804, - 4094571909, - 275423344, - 430227734, - 506948616, - 659060556, - 883997877, - 958139571, - 1322822218, - 1537002063, - 1747873779, - 1955562222, - 2024104815, - 2227730452, - 2361852424, - 2428436474, - 2756734187, - 3204031479, - 3329325298 - ]); - var SHA256_IV = /* @__PURE__ */ new Uint32Array([ - 1779033703, - 3144134277, - 1013904242, - 2773480762, - 1359893119, - 2600822924, - 528734635, - 1541459225 - ]); - var SHA256_W = /* @__PURE__ */ new Uint32Array(64); - var SHA256 = class extends HashMD { - static { - __name(this, "SHA256"); - } - constructor() { - super(64, 32, 8, false); - this.A = SHA256_IV[0] | 0; - this.B = SHA256_IV[1] | 0; - this.C = SHA256_IV[2] | 0; - this.D = SHA256_IV[3] | 0; - this.E = SHA256_IV[4] | 0; - this.F = SHA256_IV[5] | 0; - this.G = SHA256_IV[6] | 0; - this.H = SHA256_IV[7] | 0; - } - get() { - const { A, B, C, D, E, F, G, H } = this; - return [A, B, C, D, E, F, G, H]; - } - // prettier-ignore - set(A, B, C, D, E, F, G, H) { - this.A = A | 0; - this.B = B | 0; - this.C = C | 0; - this.D = D | 0; - this.E = E | 0; - this.F = F | 0; - this.G = G | 0; - this.H = H | 0; - } - process(view2, offset) { - for (let i = 0; i < 16; i++, offset += 4) - SHA256_W[i] = view2.getUint32(offset, false); - for (let i = 16; i < 64; i++) { - const W15 = SHA256_W[i - 15]; - const W2 = SHA256_W[i - 2]; - const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3; - const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10; - SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0; - } - let { A, B, C, D, E, F, G, H } = this; - for (let i = 0; i < 64; i++) { - const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25); - const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0; - const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22); - const T2 = sigma0 + Maj(A, B, C) | 0; - H = G; - G = F; - F = E; - E = D + T1 | 0; - D = C; - C = B; - B = A; - A = T1 + T2 | 0; - } - A = A + this.A | 0; - B = B + this.B | 0; - C = C + this.C | 0; - D = D + this.D | 0; - E = E + this.E | 0; - F = F + this.F | 0; - G = G + this.G | 0; - H = H + this.H | 0; - this.set(A, B, C, D, E, F, G, H); - } - roundClean() { - SHA256_W.fill(0); - } - destroy() { - this.set(0, 0, 0, 0, 0, 0, 0, 0); - this.buffer.fill(0); - } - }; - var sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256()); - - // ../utils/node_modules/base58-js/base58_chars.js - var base58_chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; - var base58_chars_default = base58_chars; - - // ../utils/node_modules/base58-js/base58_to_binary.js - function base58_to_binary(base58String) { - if (!base58String || typeof base58String !== "string") - throw new Error(`Expected base58 string but got \u201C${base58String}\u201D`); - if (base58String.match(/[IOl0]/gmu)) - throw new Error( - `Invalid base58 character \u201C${base58String.match(/[IOl0]/gmu)}\u201D` - ); - const lz = base58String.match(/^1+/gmu); - const psz = lz ? lz[0].length : 0; - const size = (base58String.length - psz) * (Math.log(58) / Math.log(256)) + 1 >>> 0; - return new Uint8Array([ - ...new Uint8Array(psz), - ...base58String.match(/.{1}/gmu).map((i) => base58_chars_default.indexOf(i)).reduce((acc, i) => { - acc = acc.map((j) => { - const x = j * 58 + i; - i = x >> 8; - return x; - }); - return acc; - }, new Uint8Array(size)).reverse().filter( - /* @__PURE__ */ ((lastValue) => (value) => ( - // @ts-ignore - lastValue = lastValue || value - ))(false) - ) - ]); - } - __name(base58_to_binary, "base58_to_binary"); - var base58_to_binary_default = base58_to_binary; - - // ../utils/node_modules/base58-js/create_base58_map.js - var create_base58_map = /* @__PURE__ */ __name(() => { - const base58M = Array(256).fill(-1); - for (let i = 0; i < base58_chars_default.length; ++i) - base58M[base58_chars_default.charCodeAt(i)] = i; - return base58M; - }, "create_base58_map"); - var create_base58_map_default = create_base58_map; - - // ../utils/node_modules/base58-js/binary_to_base58.js - var base58Map = create_base58_map_default(); - function binary_to_base58(uint8array) { - const result = []; - for (const byte of uint8array) { - let carry = byte; - for (let j = 0; j < result.length; ++j) { - const x = (base58Map[result[j]] << 8) + carry; - result[j] = base58_chars_default.charCodeAt(x % 58); - carry = x / 58 | 0; - } - while (carry) { - result.push(base58_chars_default.charCodeAt(carry % 58)); - carry = carry / 58 | 0; - } - } - for (const byte of uint8array) - if (byte) break; - else result.push("1".charCodeAt(0)); - result.reverse(); - return String.fromCharCode(...result); - } - __name(binary_to_base58, "binary_to_base58"); - var binary_to_base58_default = binary_to_base58; - - // ../../node_modules/js-base64/base64.mjs - var _hasBuffer = typeof Buffer === "function"; - var _TD = typeof TextDecoder === "function" ? new TextDecoder() : void 0; - var _TE = typeof TextEncoder === "function" ? new TextEncoder() : void 0; - var b64ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - var b64chs = Array.prototype.slice.call(b64ch); - var b64tab = ((a) => { - let tab = {}; - a.forEach((c, i) => tab[c] = i); - return tab; - })(b64chs); - var b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/; - var _fromCC = String.fromCharCode.bind(String); - var _U8Afrom = typeof Uint8Array.from === "function" ? Uint8Array.from.bind(Uint8Array) : (it) => new Uint8Array(Array.prototype.slice.call(it, 0)); - var _mkUriSafe = /* @__PURE__ */ __name((src) => src.replace(/=/g, "").replace(/[+\/]/g, (m0) => m0 == "+" ? "-" : "_"), "_mkUriSafe"); - var _tidyB64 = /* @__PURE__ */ __name((s) => s.replace(/[^A-Za-z0-9\+\/]/g, ""), "_tidyB64"); - var btoaPolyfill = /* @__PURE__ */ __name((bin) => { - let u32, c0, c1, c2, asc = ""; - const pad = bin.length % 3; - for (let i = 0; i < bin.length; ) { - if ((c0 = bin.charCodeAt(i++)) > 255 || (c1 = bin.charCodeAt(i++)) > 255 || (c2 = bin.charCodeAt(i++)) > 255) - throw new TypeError("invalid character found"); - u32 = c0 << 16 | c1 << 8 | c2; - asc += b64chs[u32 >> 18 & 63] + b64chs[u32 >> 12 & 63] + b64chs[u32 >> 6 & 63] + b64chs[u32 & 63]; - } - return pad ? asc.slice(0, pad - 3) + "===".substring(pad) : asc; - }, "btoaPolyfill"); - var _btoa = typeof btoa === "function" ? (bin) => btoa(bin) : _hasBuffer ? (bin) => Buffer.from(bin, "binary").toString("base64") : btoaPolyfill; - var _fromUint8Array = _hasBuffer ? (u8a) => Buffer.from(u8a).toString("base64") : (u8a) => { - const maxargs = 4096; - let strs = []; - for (let i = 0, l = u8a.length; i < l; i += maxargs) { - strs.push(_fromCC.apply(null, u8a.subarray(i, i + maxargs))); - } - return _btoa(strs.join("")); - }; - var fromUint8Array = /* @__PURE__ */ __name((u8a, urlsafe = false) => urlsafe ? _mkUriSafe(_fromUint8Array(u8a)) : _fromUint8Array(u8a), "fromUint8Array"); - var cb_utob = /* @__PURE__ */ __name((c) => { - if (c.length < 2) { - var cc = c.charCodeAt(0); - return cc < 128 ? c : cc < 2048 ? _fromCC(192 | cc >>> 6) + _fromCC(128 | cc & 63) : _fromCC(224 | cc >>> 12 & 15) + _fromCC(128 | cc >>> 6 & 63) + _fromCC(128 | cc & 63); - } else { - var cc = 65536 + (c.charCodeAt(0) - 55296) * 1024 + (c.charCodeAt(1) - 56320); - return _fromCC(240 | cc >>> 18 & 7) + _fromCC(128 | cc >>> 12 & 63) + _fromCC(128 | cc >>> 6 & 63) + _fromCC(128 | cc & 63); - } - }, "cb_utob"); - var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g; - var utob = /* @__PURE__ */ __name((u) => u.replace(re_utob, cb_utob), "utob"); - var _encode = _hasBuffer ? (s) => Buffer.from(s, "utf8").toString("base64") : _TE ? (s) => _fromUint8Array(_TE.encode(s)) : (s) => _btoa(utob(s)); - var encode = /* @__PURE__ */ __name((src, urlsafe = false) => urlsafe ? _mkUriSafe(_encode(src)) : _encode(src), "encode"); - var re_btou = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g; - var cb_btou = /* @__PURE__ */ __name((cccc) => { - switch (cccc.length) { - case 4: - var cp = (7 & cccc.charCodeAt(0)) << 18 | (63 & cccc.charCodeAt(1)) << 12 | (63 & cccc.charCodeAt(2)) << 6 | 63 & cccc.charCodeAt(3), offset = cp - 65536; - return _fromCC((offset >>> 10) + 55296) + _fromCC((offset & 1023) + 56320); - case 3: - return _fromCC((15 & cccc.charCodeAt(0)) << 12 | (63 & cccc.charCodeAt(1)) << 6 | 63 & cccc.charCodeAt(2)); - default: - return _fromCC((31 & cccc.charCodeAt(0)) << 6 | 63 & cccc.charCodeAt(1)); - } - }, "cb_btou"); - var btou = /* @__PURE__ */ __name((b) => b.replace(re_btou, cb_btou), "btou"); - var atobPolyfill = /* @__PURE__ */ __name((asc) => { - asc = asc.replace(/\s+/g, ""); - if (!b64re.test(asc)) - throw new TypeError("malformed base64."); - asc += "==".slice(2 - (asc.length & 3)); - let u24, bin = "", r1, r2; - for (let i = 0; i < asc.length; ) { - u24 = b64tab[asc.charAt(i++)] << 18 | b64tab[asc.charAt(i++)] << 12 | (r1 = b64tab[asc.charAt(i++)]) << 6 | (r2 = b64tab[asc.charAt(i++)]); - bin += r1 === 64 ? _fromCC(u24 >> 16 & 255) : r2 === 64 ? _fromCC(u24 >> 16 & 255, u24 >> 8 & 255) : _fromCC(u24 >> 16 & 255, u24 >> 8 & 255, u24 & 255); - } - return bin; - }, "atobPolyfill"); - var _atob = typeof atob === "function" ? (asc) => atob(_tidyB64(asc)) : _hasBuffer ? (asc) => Buffer.from(asc, "base64").toString("binary") : atobPolyfill; - var _toUint8Array = _hasBuffer ? (a) => _U8Afrom(Buffer.from(a, "base64")) : (a) => _U8Afrom(_atob(a).split("").map((c) => c.charCodeAt(0))); - var toUint8Array = /* @__PURE__ */ __name((a) => _toUint8Array(_unURI(a)), "toUint8Array"); - var _decode = _hasBuffer ? (a) => Buffer.from(a, "base64").toString("utf8") : _TD ? (a) => _TD.decode(_toUint8Array(a)) : (a) => btou(_atob(a)); - var _unURI = /* @__PURE__ */ __name((a) => _tidyB64(a.replace(/[-_]/g, (m0) => m0 == "-" ? "+" : "/")), "_unURI"); - var decode = /* @__PURE__ */ __name((src) => _decode(_unURI(src)), "decode"); - - // ../utils/src/storage.ts - var LsPrefix = "__fastnear_"; - var createDefaultStorage = /* @__PURE__ */ __name(() => typeof localStorage !== "undefined" ? localStorage : { - getItem: /* @__PURE__ */ __name((key) => memoryStore.get(key) || null, "getItem"), - setItem: /* @__PURE__ */ __name((key, value) => memoryStore.set(key, value), "setItem"), - removeItem: /* @__PURE__ */ __name((key) => memoryStore.delete(key), "removeItem"), - clear: /* @__PURE__ */ __name(() => memoryStore.clear(), "clear") - }, "createDefaultStorage"); - var memoryStore = /* @__PURE__ */ new Map(); - var storageBackend = createDefaultStorage(); - var storage = { - setBackend: /* @__PURE__ */ __name((customBackend) => { - storageBackend = customBackend; - }, "setBackend"), - set: /* @__PURE__ */ __name((key, value) => { - if (value === null || value === void 0) { - storageBackend.removeItem(LsPrefix + key); - } else { - storageBackend.setItem(LsPrefix + key, JSON.stringify(value)); - } - }, "set"), - get: /* @__PURE__ */ __name((key) => { - const value = storageBackend.getItem(LsPrefix + key); - if (value === null) return null; - try { - return JSON.parse(value); - } catch { - return value; - } - }, "get"), - remove: /* @__PURE__ */ __name((key) => storageBackend.removeItem(key), "remove"), - clear: /* @__PURE__ */ __name(() => storageBackend.clear(), "clear") - }; - - // ../utils/src/misc.ts - function toHex(data) { - return Array.from(data).map((b) => b.toString(16).padStart(2, "0")).join(""); - } - __name(toHex, "toHex"); - function fromHex(hex) { - if (hex.length % 2) throw new Error("Hex string must be even length"); - const bytes = new Uint8Array(hex.length / 2); - for (let i = 0; i < hex.length; i += 2) { - bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16); - } - return bytes; - } - __name(fromHex, "fromHex"); - function base64ToBytes(b64Val) { - return toUint8Array(b64Val); - } - __name(base64ToBytes, "base64ToBytes"); - function bytesToBase64(bytesArr) { - return fromUint8Array(bytesArr); - } - __name(bytesToBase64, "bytesToBase64"); - function toBase64(strVal) { - try { - return encode(strVal); - } catch (e) { - console.error("Issue base64 encoding", e); - return null; - } - } - __name(toBase64, "toBase64"); - function fromBase64(strVal) { - try { - return decode(strVal); - } catch (e) { - console.error("Issue base64 decoding", e); - return null; - } - } - __name(fromBase64, "fromBase64"); - function convertUnit(s, ...args) { - if (Array.isArray(s)) { - s = s.reduce((acc, part, i) => { - return acc + (args[i - 1] ?? "") + part; - }); - } - if (typeof s == "string") { - const match = s.match(/([0-9.,_]+)\s*([a-zA-Z]+)?/); - if (match) { - const amount = match[1].replace(/[_,]/g, ""); - const unitPart = match[2]; - if (unitPart) { - switch (unitPart.toLowerCase()) { - case "near": - return big_default(amount).mul(big_default(10).pow(24)).toFixed(0); - case "tgas": - return big_default(amount).mul(big_default(10).pow(12)).toFixed(0); - case "ggas": - return big_default(amount).mul(big_default(10).pow(9)).toFixed(0); - case "gas": - case "yoctonear": - return big_default(amount).toFixed(0); - default: - throw new Error(`Unknown unit: ${unitPart}`); - } - } else { - return big_default(amount).toFixed(0); - } - } - } - return big_default(`${s}`).toFixed(0); - } - __name(convertUnit, "convertUnit"); - function lsSet(key, value) { - storage.set(key, value); - } - __name(lsSet, "lsSet"); - function lsGet(key) { - return storage.get(key); - } - __name(lsGet, "lsGet"); - function deepCopy(obj) { - return JSON.parse(JSON.stringify(obj)); - } - __name(deepCopy, "deepCopy"); - function tryParseJson(...args) { - try { - return JSON.parse(args[0]); - } catch { - if (args.length > 1) { - return args[1]; - } - return args[0]; - } - } - __name(tryParseJson, "tryParseJson"); - function parseJsonFromBytes(bytes) { - try { - const decoder = new TextDecoder(); - return JSON.parse( - decoder.decode(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)) - ); - } catch (e) { - try { - return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes); - } catch (e2) { - return bytes; - } - } - } - __name(parseJsonFromBytes, "parseJsonFromBytes"); - function canSignWithLAK(actions2) { - return actions2.length === 1 && actions2[0].type === "FunctionCall" && big_default(actions2[0]?.deposit ?? "0").eq(0); - } - __name(canSignWithLAK, "canSignWithLAK"); - - // ../utils/src/crypto.ts - var keyFromString = /* @__PURE__ */ __name((key) => base58_to_binary_default( - key.includes(":") ? (() => { - const [curve, keyPart] = key.split(":"); - if (curve !== "ed25519") { - throw new Error(`Unsupported curve: ${curve}`); - } - return keyPart; - })() : key - ), "keyFromString"); - var keyToString = /* @__PURE__ */ __name((key) => `ed25519:${binary_to_base58_default(key)}`, "keyToString"); - function publicKeyFromPrivate(privateKey) { - const secret = keyFromString(privateKey).slice(0, 32); - const publicKey2 = ed25519.getPublicKey(secret); - return keyToString(publicKey2); - } - __name(publicKeyFromPrivate, "publicKeyFromPrivate"); - function privateKeyFromRandom() { - const privateKey = crypto.getRandomValues(new Uint8Array(64)); - return keyToString(privateKey); - } - __name(privateKeyFromRandom, "privateKeyFromRandom"); - function signHash(hashBytes, privateKey, opts) { - const secret = keyFromString(privateKey).slice(0, 32); - const signature = ed25519.sign(hashBytes, secret); - if (opts?.returnBase58) { - return binary_to_base58_default(signature); - } - return signature; - } - __name(signHash, "signHash"); - function signBytes(bytes, privateKey) { - const hash = sha256(bytes); - return signHash(hash, privateKey); - } - __name(signBytes, "signBytes"); - - // ../../node_modules/borsh/lib/esm/types.js - var integers = ["u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128", "f32", "f64"]; - - // ../../node_modules/borsh/lib/esm/buffer.js - var EncodeBuffer = ( - /** @class */ - function() { - function EncodeBuffer2() { - this.offset = 0; - this.buffer_size = 256; - this.buffer = new ArrayBuffer(this.buffer_size); - this.view = new DataView(this.buffer); - } - __name(EncodeBuffer2, "EncodeBuffer"); - EncodeBuffer2.prototype.resize_if_necessary = function(needed_space) { - if (this.buffer_size - this.offset < needed_space) { - this.buffer_size = Math.max(this.buffer_size * 2, this.buffer_size + needed_space); - var new_buffer = new ArrayBuffer(this.buffer_size); - new Uint8Array(new_buffer).set(new Uint8Array(this.buffer)); - this.buffer = new_buffer; - this.view = new DataView(new_buffer); - } - }; - EncodeBuffer2.prototype.get_used_buffer = function() { - return new Uint8Array(this.buffer).slice(0, this.offset); - }; - EncodeBuffer2.prototype.store_value = function(value, type) { - var bSize = type.substring(1); - var size = parseInt(bSize) / 8; - this.resize_if_necessary(size); - var toCall = type[0] === "f" ? "setFloat".concat(bSize) : type[0] === "i" ? "setInt".concat(bSize) : "setUint".concat(bSize); - this.view[toCall](this.offset, value, true); - this.offset += size; - }; - EncodeBuffer2.prototype.store_bytes = function(from) { - this.resize_if_necessary(from.length); - new Uint8Array(this.buffer).set(new Uint8Array(from), this.offset); - this.offset += from.length; - }; - return EncodeBuffer2; - }() - ); - var DecodeBuffer = ( - /** @class */ - function() { - function DecodeBuffer2(buf) { - this.offset = 0; - this.buffer_size = buf.length; - this.buffer = new ArrayBuffer(buf.length); - new Uint8Array(this.buffer).set(buf); - this.view = new DataView(this.buffer); - } - __name(DecodeBuffer2, "DecodeBuffer"); - DecodeBuffer2.prototype.assert_enough_buffer = function(size) { - if (this.offset + size > this.buffer.byteLength) { - throw new Error("Error in schema, the buffer is smaller than expected"); - } - }; - DecodeBuffer2.prototype.consume_value = function(type) { - var bSize = type.substring(1); - var size = parseInt(bSize) / 8; - this.assert_enough_buffer(size); - var toCall = type[0] === "f" ? "getFloat".concat(bSize) : type[0] === "i" ? "getInt".concat(bSize) : "getUint".concat(bSize); - var ret = this.view[toCall](this.offset, true); - this.offset += size; - return ret; - }; - DecodeBuffer2.prototype.consume_bytes = function(size) { - this.assert_enough_buffer(size); - var ret = this.buffer.slice(this.offset, this.offset + size); - this.offset += size; - return ret; - }; - return DecodeBuffer2; - }() - ); - - // ../../node_modules/borsh/lib/esm/utils.js - var __extends = /* @__PURE__ */ function() { - var extendStatics = /* @__PURE__ */ __name(function(d, b) { - extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d2, b2) { - d2.__proto__ = b2; - } || function(d2, b2) { - for (var p in b2) if (Object.prototype.hasOwnProperty.call(b2, p)) d2[p] = b2[p]; - }; - return extendStatics(d, b); - }, "extendStatics"); - return function(d, b) { - if (typeof b !== "function" && b !== null) - throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); - extendStatics(d, b); - function __() { - this.constructor = d; - } - __name(__, "__"); - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; - }(); - function isArrayLike(value) { - return Array.isArray(value) || !!value && typeof value === "object" && "length" in value && typeof value.length === "number" && (value.length === 0 || value.length > 0 && value.length - 1 in value); - } - __name(isArrayLike, "isArrayLike"); - function expect_type(value, type, fieldPath) { - if (typeof value !== type) { - throw new Error("Expected ".concat(type, " not ").concat(typeof value, "(").concat(value, ") at ").concat(fieldPath.join("."))); - } - } - __name(expect_type, "expect_type"); - function expect_bigint(value, fieldPath) { - var basicType = ["number", "string", "bigint", "boolean"].includes(typeof value); - var strObject = typeof value === "object" && value !== null && "toString" in value; - if (!basicType && !strObject) { - throw new Error("Expected bigint, number, boolean or string not ".concat(typeof value, "(").concat(value, ") at ").concat(fieldPath.join("."))); - } - } - __name(expect_bigint, "expect_bigint"); - function expect_same_size(length, expected, fieldPath) { - if (length !== expected) { - throw new Error("Array length ".concat(length, " does not match schema length ").concat(expected, " at ").concat(fieldPath.join("."))); - } - } - __name(expect_same_size, "expect_same_size"); - function expect_enum(value, fieldPath) { - if (typeof value !== "object" || value === null) { - throw new Error("Expected object not ".concat(typeof value, "(").concat(value, ") at ").concat(fieldPath.join("."))); - } - } - __name(expect_enum, "expect_enum"); - var VALID_STRING_TYPES = integers.concat(["bool", "string"]); - var VALID_OBJECT_KEYS = ["option", "enum", "array", "set", "map", "struct"]; - var ErrorSchema = ( - /** @class */ - function(_super) { - __extends(ErrorSchema2, _super); - function ErrorSchema2(schema, expected) { - var message = "Invalid schema: ".concat(JSON.stringify(schema), " expected ").concat(expected); - return _super.call(this, message) || this; - } - __name(ErrorSchema2, "ErrorSchema"); - return ErrorSchema2; - }(Error) - ); - function validate_schema(schema) { - if (typeof schema === "string" && VALID_STRING_TYPES.includes(schema)) { - return; - } - if (schema && typeof schema === "object") { - var keys = Object.keys(schema); - if (keys.length === 1 && VALID_OBJECT_KEYS.includes(keys[0])) { - var key = keys[0]; - if (key === "option") - return validate_schema(schema[key]); - if (key === "enum") - return validate_enum_schema(schema[key]); - if (key === "array") - return validate_array_schema(schema[key]); - if (key === "set") - return validate_schema(schema[key]); - if (key === "map") - return validate_map_schema(schema[key]); - if (key === "struct") - return validate_struct_schema(schema[key]); - } - } - throw new ErrorSchema(schema, VALID_OBJECT_KEYS.join(", ") + " or " + VALID_STRING_TYPES.join(", ")); - } - __name(validate_schema, "validate_schema"); - function validate_enum_schema(schema) { - if (!Array.isArray(schema)) - throw new ErrorSchema(schema, "Array"); - for (var _i = 0, schema_1 = schema; _i < schema_1.length; _i++) { - var sch = schema_1[_i]; - if (typeof sch !== "object" || !("struct" in sch)) { - throw new Error('Missing "struct" key in enum schema'); - } - if (typeof sch.struct !== "object" || Object.keys(sch.struct).length !== 1) { - throw new Error('The "struct" in each enum must have a single key'); - } - validate_schema({ struct: sch.struct }); - } - } - __name(validate_enum_schema, "validate_enum_schema"); - function validate_array_schema(schema) { - if (typeof schema !== "object") - throw new ErrorSchema(schema, "{ type, len? }"); - if (schema.len && typeof schema.len !== "number") { - throw new Error("Invalid schema: ".concat(schema)); - } - if ("type" in schema) - return validate_schema(schema.type); - throw new ErrorSchema(schema, "{ type, len? }"); - } - __name(validate_array_schema, "validate_array_schema"); - function validate_map_schema(schema) { - if (typeof schema === "object" && "key" in schema && "value" in schema) { - validate_schema(schema.key); - validate_schema(schema.value); - } else { - throw new ErrorSchema(schema, "{ key, value }"); - } - } - __name(validate_map_schema, "validate_map_schema"); - function validate_struct_schema(schema) { - if (typeof schema !== "object") - throw new ErrorSchema(schema, "object"); - for (var key in schema) { - validate_schema(schema[key]); - } - } - __name(validate_struct_schema, "validate_struct_schema"); - - // ../../node_modules/borsh/lib/esm/serialize.js - var BorshSerializer = ( - /** @class */ - function() { - function BorshSerializer2(checkTypes) { - this.encoded = new EncodeBuffer(); - this.fieldPath = ["value"]; - this.checkTypes = checkTypes; - } - __name(BorshSerializer2, "BorshSerializer"); - BorshSerializer2.prototype.encode = function(value, schema) { - this.encode_value(value, schema); - return this.encoded.get_used_buffer(); - }; - BorshSerializer2.prototype.encode_value = function(value, schema) { - if (typeof schema === "string") { - if (integers.includes(schema)) - return this.encode_integer(value, schema); - if (schema === "string") - return this.encode_string(value); - if (schema === "bool") - return this.encode_boolean(value); - } - if (typeof schema === "object") { - if ("option" in schema) - return this.encode_option(value, schema); - if ("enum" in schema) - return this.encode_enum(value, schema); - if ("array" in schema) - return this.encode_array(value, schema); - if ("set" in schema) - return this.encode_set(value, schema); - if ("map" in schema) - return this.encode_map(value, schema); - if ("struct" in schema) - return this.encode_struct(value, schema); - } - }; - BorshSerializer2.prototype.encode_integer = function(value, schema) { - var size = parseInt(schema.substring(1)); - if (size <= 32 || schema == "f64") { - this.checkTypes && expect_type(value, "number", this.fieldPath); - this.encoded.store_value(value, schema); - } else { - this.checkTypes && expect_bigint(value, this.fieldPath); - this.encode_bigint(BigInt(value), size); - } - }; - BorshSerializer2.prototype.encode_bigint = function(value, size) { - var buffer_len = size / 8; - var buffer = new Uint8Array(buffer_len); - for (var i = 0; i < buffer_len; i++) { - buffer[i] = Number(value & BigInt(255)); - value = value >> BigInt(8); - } - this.encoded.store_bytes(new Uint8Array(buffer)); - }; - BorshSerializer2.prototype.encode_string = function(value) { - this.checkTypes && expect_type(value, "string", this.fieldPath); - var _value = value; - var utf8Bytes = []; - for (var i = 0; i < _value.length; i++) { - var charCode = _value.charCodeAt(i); - if (charCode < 128) { - utf8Bytes.push(charCode); - } else if (charCode < 2048) { - utf8Bytes.push(192 | charCode >> 6, 128 | charCode & 63); - } else if (charCode < 55296 || charCode >= 57344) { - utf8Bytes.push(224 | charCode >> 12, 128 | charCode >> 6 & 63, 128 | charCode & 63); - } else { - i++; - charCode = 65536 + ((charCode & 1023) << 10 | _value.charCodeAt(i) & 1023); - utf8Bytes.push(240 | charCode >> 18, 128 | charCode >> 12 & 63, 128 | charCode >> 6 & 63, 128 | charCode & 63); - } - } - this.encoded.store_value(utf8Bytes.length, "u32"); - this.encoded.store_bytes(new Uint8Array(utf8Bytes)); - }; - BorshSerializer2.prototype.encode_boolean = function(value) { - this.checkTypes && expect_type(value, "boolean", this.fieldPath); - this.encoded.store_value(value ? 1 : 0, "u8"); - }; - BorshSerializer2.prototype.encode_option = function(value, schema) { - if (value === null || value === void 0) { - this.encoded.store_value(0, "u8"); - } else { - this.encoded.store_value(1, "u8"); - this.encode_value(value, schema.option); - } - }; - BorshSerializer2.prototype.encode_enum = function(value, schema) { - this.checkTypes && expect_enum(value, this.fieldPath); - var valueKey = Object.keys(value)[0]; - for (var i = 0; i < schema["enum"].length; i++) { - var valueSchema = schema["enum"][i]; - if (valueKey === Object.keys(valueSchema.struct)[0]) { - this.encoded.store_value(i, "u8"); - return this.encode_struct(value, valueSchema); - } - } - throw new Error("Enum key (".concat(valueKey, ") not found in enum schema: ").concat(JSON.stringify(schema), " at ").concat(this.fieldPath.join("."))); - }; - BorshSerializer2.prototype.encode_array = function(value, schema) { - if (isArrayLike(value)) - return this.encode_arraylike(value, schema); - if (value instanceof ArrayBuffer) - return this.encode_buffer(value, schema); - throw new Error("Expected Array-like not ".concat(typeof value, "(").concat(value, ") at ").concat(this.fieldPath.join("."))); - }; - BorshSerializer2.prototype.encode_arraylike = function(value, schema) { - if (schema.array.len) { - expect_same_size(value.length, schema.array.len, this.fieldPath); - } else { - this.encoded.store_value(value.length, "u32"); - } - for (var i = 0; i < value.length; i++) { - this.encode_value(value[i], schema.array.type); - } - }; - BorshSerializer2.prototype.encode_buffer = function(value, schema) { - if (schema.array.len) { - expect_same_size(value.byteLength, schema.array.len, this.fieldPath); - } else { - this.encoded.store_value(value.byteLength, "u32"); - } - this.encoded.store_bytes(new Uint8Array(value)); - }; - BorshSerializer2.prototype.encode_set = function(value, schema) { - this.checkTypes && expect_type(value, "object", this.fieldPath); - var isSet = value instanceof Set; - var values = isSet ? Array.from(value.values()) : Object.values(value); - this.encoded.store_value(values.length, "u32"); - for (var _i = 0, values_1 = values; _i < values_1.length; _i++) { - var value_1 = values_1[_i]; - this.encode_value(value_1, schema.set); - } - }; - BorshSerializer2.prototype.encode_map = function(value, schema) { - this.checkTypes && expect_type(value, "object", this.fieldPath); - var isMap = value instanceof Map; - var keys = isMap ? Array.from(value.keys()) : Object.keys(value); - this.encoded.store_value(keys.length, "u32"); - for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { - var key = keys_1[_i]; - this.encode_value(key, schema.map.key); - this.encode_value(isMap ? value.get(key) : value[key], schema.map.value); - } - }; - BorshSerializer2.prototype.encode_struct = function(value, schema) { - this.checkTypes && expect_type(value, "object", this.fieldPath); - for (var _i = 0, _a = Object.keys(schema.struct); _i < _a.length; _i++) { - var key = _a[_i]; - this.fieldPath.push(key); - this.encode_value(value[key], schema.struct[key]); - this.fieldPath.pop(); - } - }; - return BorshSerializer2; - }() - ); - - // ../../node_modules/borsh/lib/esm/deserialize.js - var BorshDeserializer = ( - /** @class */ - function() { - function BorshDeserializer2(bufferArray) { - this.buffer = new DecodeBuffer(bufferArray); - } - __name(BorshDeserializer2, "BorshDeserializer"); - BorshDeserializer2.prototype.decode = function(schema) { - return this.decode_value(schema); - }; - BorshDeserializer2.prototype.decode_value = function(schema) { - if (typeof schema === "string") { - if (integers.includes(schema)) - return this.decode_integer(schema); - if (schema === "string") - return this.decode_string(); - if (schema === "bool") - return this.decode_boolean(); - } - if (typeof schema === "object") { - if ("option" in schema) - return this.decode_option(schema); - if ("enum" in schema) - return this.decode_enum(schema); - if ("array" in schema) - return this.decode_array(schema); - if ("set" in schema) - return this.decode_set(schema); - if ("map" in schema) - return this.decode_map(schema); - if ("struct" in schema) - return this.decode_struct(schema); - } - throw new Error("Unsupported type: ".concat(schema)); - }; - BorshDeserializer2.prototype.decode_integer = function(schema) { - var size = parseInt(schema.substring(1)); - if (size <= 32 || schema == "f64") { - return this.buffer.consume_value(schema); - } - return this.decode_bigint(size, schema.startsWith("i")); - }; - BorshDeserializer2.prototype.decode_bigint = function(size, signed) { - if (signed === void 0) { - signed = false; - } - var buffer_len = size / 8; - var buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len)); - var bits = buffer.reduceRight(function(r, x) { - return r + x.toString(16).padStart(2, "0"); - }, ""); - if (signed && buffer[buffer_len - 1]) { - return BigInt.asIntN(size, BigInt("0x".concat(bits))); - } - return BigInt("0x".concat(bits)); - }; - BorshDeserializer2.prototype.decode_string = function() { - var len = this.decode_integer("u32"); - var buffer = new Uint8Array(this.buffer.consume_bytes(len)); - var codePoints = []; - for (var i = 0; i < len; ++i) { - var byte = buffer[i]; - if (byte < 128) { - codePoints.push(byte); - } else if (byte < 224) { - codePoints.push((byte & 31) << 6 | buffer[++i] & 63); - } else if (byte < 240) { - codePoints.push((byte & 15) << 12 | (buffer[++i] & 63) << 6 | buffer[++i] & 63); - } else { - var codePoint = (byte & 7) << 18 | (buffer[++i] & 63) << 12 | (buffer[++i] & 63) << 6 | buffer[++i] & 63; - codePoints.push(codePoint); - } - } - return String.fromCodePoint.apply(String, codePoints); - }; - BorshDeserializer2.prototype.decode_boolean = function() { - return this.buffer.consume_value("u8") > 0; - }; - BorshDeserializer2.prototype.decode_option = function(schema) { - var option = this.buffer.consume_value("u8"); - if (option === 1) { - return this.decode_value(schema.option); - } - if (option !== 0) { - throw new Error("Invalid option ".concat(option)); - } - return null; - }; - BorshDeserializer2.prototype.decode_enum = function(schema) { - var _a; - var valueIndex = this.buffer.consume_value("u8"); - if (valueIndex > schema["enum"].length) { - throw new Error("Enum option ".concat(valueIndex, " is not available")); - } - var struct = schema["enum"][valueIndex].struct; - var key = Object.keys(struct)[0]; - return _a = {}, _a[key] = this.decode_value(struct[key]), _a; - }; - BorshDeserializer2.prototype.decode_array = function(schema) { - var result = []; - var len = schema.array.len ? schema.array.len : this.decode_integer("u32"); - for (var i = 0; i < len; ++i) { - result.push(this.decode_value(schema.array.type)); - } - return result; - }; - BorshDeserializer2.prototype.decode_set = function(schema) { - var len = this.decode_integer("u32"); - var result = /* @__PURE__ */ new Set(); - for (var i = 0; i < len; ++i) { - result.add(this.decode_value(schema.set)); - } - return result; - }; - BorshDeserializer2.prototype.decode_map = function(schema) { - var len = this.decode_integer("u32"); - var result = /* @__PURE__ */ new Map(); - for (var i = 0; i < len; ++i) { - var key = this.decode_value(schema.map.key); - var value = this.decode_value(schema.map.value); - result.set(key, value); - } - return result; - }; - BorshDeserializer2.prototype.decode_struct = function(schema) { - var result = {}; - for (var key in schema.struct) { - result[key] = this.decode_value(schema.struct[key]); - } - return result; - }; - return BorshDeserializer2; - }() - ); - - // ../../node_modules/borsh/lib/esm/index.js - function serialize(schema, value, validate) { - if (validate === void 0) { - validate = true; - } - if (validate) - validate_schema(schema); - var serializer = new BorshSerializer(validate); - return serializer.encode(value, schema); - } - __name(serialize, "serialize"); - function deserialize(schema, buffer, validate) { - if (validate === void 0) { - validate = true; - } - if (validate) - validate_schema(schema); - var deserializer = new BorshDeserializer(buffer); - return deserializer.decode(schema); - } - __name(deserialize, "deserialize"); - - // ../borsh-schema/src/index.ts - var src_exports = {}; - __export(src_exports, { - getBorshSchema: () => getBorshSchema, - nearChainSchema: () => nearChainSchema - }); - var nearChainSchema = new class BorshSchema { - static { - __name(this, "BorshSchema"); - } - Ed25519Signature = { - struct: { - data: { array: { type: "u8", len: 64 } } - } - }; - Secp256k1Signature = { - struct: { - data: { array: { type: "u8", len: 65 } } - } - }; - Signature = { - enum: [ - { struct: { ed25519Signature: this.Ed25519Signature } }, - { struct: { secp256k1Signature: this.Secp256k1Signature } } - ] - }; - Ed25519Data = { - struct: { - data: { array: { type: "u8", len: 32 } } - } - }; - Secp256k1Data = { - struct: { - data: { array: { type: "u8", len: 64 } } - } - }; - PublicKey = { - enum: [ - { struct: { ed25519Key: this.Ed25519Data } }, - { struct: { secp256k1Key: this.Secp256k1Data } } - ] - }; - FunctionCallPermission = { - struct: { - allowance: { option: "u128" }, - receiverId: "string", - methodNames: { array: { type: "string" } } - } - }; - FullAccessPermission = { - struct: {} - }; - AccessKeyPermission = { - enum: [ - { struct: { functionCall: this.FunctionCallPermission } }, - { struct: { fullAccess: this.FullAccessPermission } } - ] - }; - AccessKey = { - struct: { - nonce: "u64", - permission: this.AccessKeyPermission - } - }; - CreateAccount = { - struct: {} - }; - DeployContract = { - struct: { - code: { array: { type: "u8" } } - } - }; - FunctionCall = { - struct: { - methodName: "string", - args: { array: { type: "u8" } }, - gas: "u64", - deposit: "u128" - } - }; - Transfer = { - struct: { - deposit: "u128" - } - }; - Stake = { - struct: { - stake: "u128", - publicKey: this.PublicKey - } - }; - AddKey = { - struct: { - publicKey: this.PublicKey, - accessKey: this.AccessKey - } - }; - DeleteKey = { - struct: { - publicKey: this.PublicKey - } - }; - DeleteAccount = { - struct: { - beneficiaryId: "string" - } - }; - ClassicAction = { - enum: [ - { struct: { createAccount: this.CreateAccount } }, - { struct: { deployContract: this.DeployContract } }, - { struct: { functionCall: this.FunctionCall } }, - { struct: { transfer: this.Transfer } }, - { struct: { stake: this.Stake } }, - { struct: { addKey: this.AddKey } }, - { struct: { deleteKey: this.DeleteKey } }, - { struct: { deleteAccount: this.DeleteAccount } } - ] - }; - DelegateAction = { - struct: { - senderId: "string", - receiverId: "string", - actions: { array: { type: this.ClassicAction } }, - nonce: "u64", - maxBlockHeight: "u64", - publicKey: this.PublicKey - } - }; - SignedDelegate = { - struct: { - delegateAction: this.DelegateAction, - signature: this.Signature - } - }; - Action = { - enum: [ - { struct: { createAccount: this.CreateAccount } }, - { struct: { deployContract: this.DeployContract } }, - { struct: { functionCall: this.FunctionCall } }, - { struct: { transfer: this.Transfer } }, - { struct: { stake: this.Stake } }, - { struct: { addKey: this.AddKey } }, - { struct: { deleteKey: this.DeleteKey } }, - { struct: { deleteAccount: this.DeleteAccount } }, - { struct: { signedDelegate: this.SignedDelegate } } - ] - }; - Transaction = { - struct: { - signerId: "string", - publicKey: this.PublicKey, - nonce: "u64", - receiverId: "string", - blockHash: { array: { type: "u8", len: 32 } }, - actions: { array: { type: this.Action } } - } - }; - SignedTransaction = { - struct: { - transaction: this.Transaction, - signature: this.Signature - } - }; - }(); - var getBorshSchema = /* @__PURE__ */ __name(() => nearChainSchema, "getBorshSchema"); - - // ../utils/src/transaction.ts - var txToJson = /* @__PURE__ */ __name((tx) => { - return JSON.parse(JSON.stringify( - tx, - (key, value) => typeof value === "bigint" ? value.toString() : value - )); - }, "txToJson"); - var txToJsonStringified = /* @__PURE__ */ __name((tx) => { - return JSON.stringify(txToJson(tx)); - }, "txToJsonStringified"); - function mapTransaction(jsonTransaction) { - return { - signerId: jsonTransaction.signerId, - publicKey: { - ed25519Key: { - data: keyFromString(jsonTransaction.publicKey) - } - }, - nonce: BigInt(jsonTransaction.nonce), - receiverId: jsonTransaction.receiverId, - blockHash: base58_to_binary_default(jsonTransaction.blockHash), - actions: jsonTransaction.actions.map(mapAction) - }; - } - __name(mapTransaction, "mapTransaction"); - function serializeTransaction(jsonTransaction) { - console.log("fastnear: serializing transaction"); - const transaction = mapTransaction(jsonTransaction); - console.log("fastnear: mapped transaction for borsh:", transaction); - return serialize(SCHEMA.Transaction, transaction); - } - __name(serializeTransaction, "serializeTransaction"); - function serializeSignedTransaction(jsonTransaction, signature) { - console.log("fastnear: Serializing Signed Transaction", jsonTransaction); - console.log("fastnear: signature", signature); - console.log("fastnear: signature length", base58_to_binary_default(signature).length); - const mappedSignedTx = mapTransaction(jsonTransaction); - console.log("fastnear: mapped (for borsh schema) signed transaction", mappedSignedTx); - const plainSignedTransaction = { - transaction: mappedSignedTx, - signature: { - ed25519Signature: { - data: base58_to_binary_default(signature) - } - } - }; - const borshSignedTx = serialize(SCHEMA.SignedTransaction, plainSignedTransaction, true); - console.log("fastnear: borsh-serialized signed transaction:", borshSignedTx); - return borshSignedTx; - } - __name(serializeSignedTransaction, "serializeSignedTransaction"); - function mapAction(action) { - switch (action.type) { - case "CreateAccount": { - return { - createAccount: {} - }; - } - case "DeployContract": { - return { - deployContract: { - code: base64ToBytes(action.codeBase64) - } - }; - } - case "FunctionCall": { - return { - functionCall: { - methodName: action.methodName, - args: action.argsBase64 !== null && action.argsBase64 !== void 0 ? base64ToBytes(action.argsBase64) : new TextEncoder().encode(JSON.stringify(action.args)), - gas: BigInt(action.gas ?? "300000000000000"), - deposit: BigInt(action.deposit ?? "0") - } - }; - } - case "Transfer": { - return { - transfer: { - deposit: BigInt(action.deposit) - } - }; - } - case "Stake": { - return { - stake: { - stake: BigInt(action.stake), - publicKey: { - ed25519Key: { - data: keyFromString(action.publicKey) - } - } - } - }; - } - case "AddKey": { - return { - addKey: { - publicKey: { - ed25519Key: { - data: keyFromString(action.publicKey) - } - }, - accessKey: { - nonce: BigInt(action.accessKey.nonce), - permission: action.accessKey.permission === "FullAccess" ? { fullAccess: {} } : { - functionCall: { - allowance: action.accessKey.allowance ? BigInt(action.accessKey.allowance) : null, - receiverId: action.accessKey.receiverId, - methodNames: action.accessKey.methodNames - } - } - } - } - }; - } - case "DeleteKey": { - return { - deleteKey: { - publicKey: { - ed25519Key: { - data: keyFromString(action.publicKey) - } - } - } - }; - } - case "DeleteAccount": { - return { - deleteAccount: { - beneficiaryId: action.beneficiaryId - } - }; - } - case "SignedDelegate": { - return { - signedDelegate: { - delegateAction: mapAction(action.delegateAction), - signature: { - ed25519Signature: base58_to_binary_default(action.signature) - } - } - }; - } - default: { - throw new Error("Not implemented action: " + action.type); - } - } - } - __name(mapAction, "mapAction"); - var SCHEMA = getBorshSchema(); - - // ../utils/src/index.ts - var exp = { - borsh: { - serialize, - deserialize - }, - borshSchema: src_exports - }; - - // src/state.ts - var state_exports = {}; - __export(state_exports, { - DEFAULT_NETWORK_ID: () => DEFAULT_NETWORK_ID, - NETWORKS: () => NETWORKS, - WIDGET_URL: () => WIDGET_URL, - _adapter: () => _adapter, - _config: () => _config, - _state: () => _state, - _txHistory: () => _txHistory, - _unbroadcastedEvents: () => _unbroadcastedEvents, - events: () => events, - getConfig: () => getConfig, - getTxHistory: () => getTxHistory, - getWalletAdapterState: () => getWalletAdapterState, - onAdapterStateUpdate: () => onAdapterStateUpdate, - resetTxHistory: () => resetTxHistory, - setConfig: () => setConfig, - update: () => update, - updateTxHistory: () => updateTxHistory - }); - - // ../wallet-adapter/src/index.ts - var WalletAdapter = class _WalletAdapter { - static { - __name(this, "WalletAdapter"); - } - /** @type {HTMLIFrameElement} */ - #iframe = null; - /** @type {string} */ - #targetOrigin; - /** @type {string} */ - #widgetUrl; - /** @type {Map} */ - #pending = /* @__PURE__ */ new Map(); - /** @type {WalletState} */ - #state; - /** @type {Function} */ - #onStateUpdate; - /** @type {string} */ - #callbackUrl; - /** @type {string} */ - static defaultWidgetUrl = "https://wallet-adapter.fastnear.com"; - /** - * @param {WalletAdapterConfig} [config] - */ - constructor({ - widgetUrl = _WalletAdapter.defaultWidgetUrl, - targetOrigin = "*", - onStateUpdate, - lastState, - callbackUrl = window.location.href - } = {}) { - this.#targetOrigin = targetOrigin; - this.#widgetUrl = widgetUrl; - this.#onStateUpdate = onStateUpdate; - this.#callbackUrl = callbackUrl; - this.#state = lastState || {}; - window.addEventListener("message", this.#handleMessage.bind(this)); - } - /** - * Creates an iframe for wallet interaction - * @param {string} path - Path to load in iframe - * @returns {HTMLIFrameElement} - */ - #createIframe(path) { - if (this.#iframe) { - this.#iframe.remove(); - } - const url = new URL(path, this.#widgetUrl); - const iframe = document.createElement("iframe"); - iframe.src = url.toString(); - iframe.allow = "usb"; - iframe.style.border = "none"; - iframe.style.zIndex = "10000"; - iframe.style.position = "fixed"; - iframe.style.display = "block"; - iframe.style.top = "0"; - iframe.style.left = "0"; - iframe.style.width = "100%"; - iframe.style.height = "100%"; - document.body.appendChild(iframe); - this.#iframe = iframe; - return iframe; - } - /** - * Handles messages from the wallet widget - * @param {MessageEvent} event - */ - #handleMessage(event2) { - if (this.#targetOrigin !== "*" && event2.origin !== this.#targetOrigin) { - return; - } - const { id, type, action, payload } = event2.data; - if (type !== "wallet-adapter") return; - if (action === "close") { - this.#iframe?.remove(); - this.#iframe = null; - return; - } - if (payload?.state) { - this.#state = { ...this.#state, ...payload.state }; - this.#onStateUpdate?.(this.#state); - } - const resolve = this.#pending.get(id); - if (resolve) { - this.#pending.delete(id); - this.#iframe?.remove(); - this.#iframe = null; - resolve(payload); - } - } - /** - * Sends a message to the wallet widget - * @param {string} path - Path to load in iframe - * @param {string} method - Method to call - * @param {Object} params - Parameters to pass - * @returns {Promise} - */ - async #sendMessage(path, method, params) { - return new Promise((resolve) => { - const id = Math.random().toString(36).slice(2); - this.#pending.set(id, resolve); - const iframe = this.#createIframe(path); - iframe.onload = () => { - iframe.contentWindow?.postMessage( - { - type: "wallet-adapter", - method, - params: { - id, - ...params, - state: this.#state, - callbackUrl: params.callbackUrl || this.#callbackUrl - } - }, - this.#targetOrigin - ); - }; - }); - } - /** - * Get current wallet state - * @returns {WalletState} - */ - getState() { - return { ...this.#state }; - } - /** - * Set current wallet state - * @param state - */ - setState(state2) { - this.#state = state2; - } - /** - * Sign in with a NEAR wallet - * @param {SignInConfig} config - * @returns {Promise} - * - * Should be returning SignInResult - */ - async signIn(config2) { - return this.#sendMessage("/login.html", "signIn", config2); - } - /** - * Send a transaction using connected wallet - * @param {TransactionConfig} config - * @returns {Promise} - */ - async sendTransactions(config2) { - return this.#sendMessage("/send.html", "sendTransactions", config2); - } - /** - * Clean up adapter resources - */ - destroy() { - window.removeEventListener("message", this.#handleMessage); - this.#iframe?.remove(); - this.#iframe = null; - } - }; - - // src/state.ts - var WIDGET_URL = "https://js.cdn.fastnear.com"; - var DEFAULT_NETWORK_ID = "mainnet"; - var NETWORKS = { - testnet: { - networkId: "testnet", - nodeUrl: "https://rpc.testnet.fastnear.com/" - }, - mainnet: { - networkId: "mainnet", - nodeUrl: "https://rpc.mainnet.fastnear.com/" - } - }; - var _config = lsGet("config") || { - ...NETWORKS[DEFAULT_NETWORK_ID] - }; - var _state = lsGet("state") || {}; - var onAdapterStateUpdate = /* @__PURE__ */ __name((state2) => { - console.log("Adapter state update:", state2); - const { accountId: accountId2, lastWalletId, privateKey } = state2; - update({ - accountId: accountId2 || void 0, - lastWalletId: lastWalletId || void 0, - ...privateKey ? { privateKey } : {} - }); - }, "onAdapterStateUpdate"); - var getWalletAdapterState = /* @__PURE__ */ __name(() => { - return { - publicKey: _state.publicKey, - accountId: _state.accountId, - lastWalletId: _state.lastWalletId, - networkId: _config.networkId - }; - }, "getWalletAdapterState"); - var _adapter = new WalletAdapter({ - onStateUpdate: onAdapterStateUpdate, - lastState: getWalletAdapterState(), - widgetUrl: WIDGET_URL - }); - try { - _state.publicKey = _state.privateKey ? publicKeyFromPrivate(_state.privateKey) : null; - } catch (e) { - console.error("Error parsing private key:", e); - _state.privateKey = null; - lsSet("nonce", null); - } - var _txHistory = lsGet("txHistory") || {}; - var _unbroadcastedEvents = { - account: [], - tx: [] - }; - var events = { - _eventListeners: { - account: /* @__PURE__ */ new Set(), - tx: /* @__PURE__ */ new Set() - }, - notifyAccountListeners: /* @__PURE__ */ __name((accountId2) => { - if (events._eventListeners.account.size === 0) { - _unbroadcastedEvents.account.push(accountId2); - return; - } - events._eventListeners.account.forEach((callback) => { - try { - callback(accountId2); - } catch (e) { - console.error(e); - } - }); - }, "notifyAccountListeners"), - notifyTxListeners: /* @__PURE__ */ __name((tx) => { - if (events._eventListeners.tx.size === 0) { - _unbroadcastedEvents.tx.push(tx); - return; - } - events._eventListeners.tx.forEach((callback) => { - try { - callback(tx); - } catch (e) { - console.error(e); - } - }); - }, "notifyTxListeners"), - onAccount: /* @__PURE__ */ __name((callback) => { - events._eventListeners.account.add(callback); - if (_unbroadcastedEvents.account.length > 0) { - const accountEvent = _unbroadcastedEvents.account; - _unbroadcastedEvents.account = []; - accountEvent.forEach(events.notifyAccountListeners); - } - }, "onAccount"), - onTx: /* @__PURE__ */ __name((callback) => { - events._eventListeners.tx.add(callback); - if (_unbroadcastedEvents.tx.length > 0) { - const txEvent = _unbroadcastedEvents.tx; - _unbroadcastedEvents.tx = []; - txEvent.forEach(events.notifyTxListeners); - } - }, "onTx") - }; - var update = /* @__PURE__ */ __name((newState) => { - const oldState = _state; - _state = { ..._state, ...newState }; - lsSet("state", { - accountId: _state.accountId, - privateKey: _state.privateKey, - lastWalletId: _state.lastWalletId, - accessKeyContractId: _state.accessKeyContractId - }); - if (newState.hasOwnProperty("privateKey") && newState.privateKey !== oldState.privateKey) { - _state.publicKey = newState.privateKey ? publicKeyFromPrivate(newState.privateKey) : null; - lsSet("nonce", null); - } - if (newState.accountId !== oldState.accountId) { - events.notifyAccountListeners(newState.accountId); - } - if (newState.hasOwnProperty("lastWalletId") && newState.lastWalletId !== oldState.lastWalletId || newState.hasOwnProperty("accountId") && newState.accountId !== oldState.accountId || newState.hasOwnProperty("privateKey") && newState.privateKey !== oldState.privateKey) { - _adapter.setState(getWalletAdapterState()); - } - }, "update"); - var updateTxHistory = /* @__PURE__ */ __name((txStatus) => { - const txId = txStatus.txId; - _txHistory[txId] = { - ..._txHistory[txId] || {}, - ...txStatus, - updateTimestamp: Date.now() - }; - lsSet("txHistory", _txHistory); - events.notifyTxListeners(_txHistory[txId]); - }, "updateTxHistory"); - var getConfig = /* @__PURE__ */ __name(() => { - return _config; - }, "getConfig"); - var getTxHistory = /* @__PURE__ */ __name(() => { - return _txHistory; - }, "getTxHistory"); - var setConfig = /* @__PURE__ */ __name((newConf) => { - _config = { ...NETWORKS[newConf.networkId], ...newConf }; - lsSet("config", _config); - }, "setConfig"); - var resetTxHistory = /* @__PURE__ */ __name(() => { - _txHistory = {}; - lsSet("txHistory", _txHistory); - }, "resetTxHistory"); - - // src/near.ts - big_default.DP = 27; - var MaxBlockDelayMs = 1e3 * 60 * 60 * 6; - function withBlockId(params, blockId) { - if (blockId === "final" || blockId === "optimistic") { - return { ...params, finality: blockId }; - } - return blockId ? { ...params, block_id: blockId } : { ...params, finality: "optimistic" }; - } - __name(withBlockId, "withBlockId"); - async function sendRpc(method, params) { - const config2 = getConfig(); - if (!config2?.nodeUrl) { - throw new Error("fastnear: getConfig() returned invalid config: missing nodeUrl."); - } - const response = await fetch(config2.nodeUrl, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: `fastnear-${Date.now()}`, - method, - params - }) - }); - const result = await response.json(); - if (result.error) { - throw new Error(JSON.stringify(result.error)); - } - return result; - } - __name(sendRpc, "sendRpc"); - function afterTxSent(txId) { - const txHistory = getTxHistory(); - sendRpc("tx", { - tx_hash: txHistory[txId]?.txHash, - sender_account_id: txHistory[txId]?.tx?.signerId, - wait_until: "EXECUTED_OPTIMISTIC" - }).then((result) => { - const successValue = result?.result?.status?.SuccessValue; - updateTxHistory({ - txId, - status: "Executed", - result, - successValue: successValue ? tryParseJson(fromBase64(successValue)) : void 0, - finalState: true - }); - }).catch((error) => { - updateTxHistory({ - txId, - status: "ErrorAfterIncluded", - error: tryParseJson(error.message) ?? error.message, - finalState: true - }); - }); - } - __name(afterTxSent, "afterTxSent"); - async function sendTxToRpc(signedTxBase64, waitUntil, txId) { - waitUntil = waitUntil || "INCLUDED"; - try { - const sendTxRes = await sendRpc("send_tx", { - signed_tx_base64: signedTxBase64, - wait_until: waitUntil - }); - updateTxHistory({ txId, status: "Included", finalState: false }); - afterTxSent(txId); - return sendTxRes; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - updateTxHistory({ - txId, - status: "Error", - error: tryParseJson(errorMessage) ?? errorMessage, - finalState: false - }); - throw new Error(errorMessage); - } - } - __name(sendTxToRpc, "sendTxToRpc"); - function generateTxId() { - const randomPart = crypto.getRandomValues(new Uint32Array(2)).join(""); - return `tx-${Date.now()}-${parseInt(randomPart, 10).toString(36)}`; - } - __name(generateTxId, "generateTxId"); - var accountId = /* @__PURE__ */ __name(() => _state.accountId, "accountId"); - var publicKey = /* @__PURE__ */ __name(() => _state.publicKey, "publicKey"); - var config = /* @__PURE__ */ __name((newConfig) => { - const current = getConfig(); - if (newConfig) { - if (newConfig.networkId && current.networkId !== newConfig.networkId) { - setConfig(newConfig.networkId); - update({ accountId: null, privateKey: null, lastWalletId: null }); - lsSet("block", null); - resetTxHistory(); - } - setConfig({ ...getConfig(), ...newConfig }); - } - return getConfig(); - }, "config"); - var authStatus = /* @__PURE__ */ __name(() => { - if (!_state.accountId) { - return "SignedOut"; - } - return "SignedIn"; - }, "authStatus"); - var getPublicKeyForContract = /* @__PURE__ */ __name((opts) => { - return publicKey(); - }, "getPublicKeyForContract"); - var selected = /* @__PURE__ */ __name(() => { - const network = getConfig().networkId; - const nodeUrl = getConfig().nodeUrl; - const walletUrl = getConfig().walletUrl; - const helperUrl = getConfig().helperUrl; - const explorerUrl = getConfig().explorerUrl; - const account = accountId(); - const contract = _state.accessKeyContractId; - const publicKey2 = getPublicKeyForContract(); - return { - network, - nodeUrl, - walletUrl, - helperUrl, - explorerUrl, - account, - contract, - publicKey: publicKey2 - }; - }, "selected"); - var requestSignIn = /* @__PURE__ */ __name(async ({ contractId }) => { - const privateKey = privateKeyFromRandom(); - update({ accessKeyContractId: contractId, accountId: null, privateKey }); - const pubKey = publicKeyFromPrivate(privateKey); - const result = await _adapter.signIn({ - networkId: getConfig().networkId, - contractId, - publicKey: pubKey - }); - if (result.error) { - throw new Error(`Wallet error: ${result.error}`); - } - if (result.url) { - if (typeof window !== "undefined") { - setTimeout(() => { - window.location.href = result.url; - }, 100); - } - } else if (result.accountId) { - update({ accountId: result.accountId }); - } - }, "requestSignIn"); - var view = /* @__PURE__ */ __name(async ({ - contractId, - methodName, - args, - argsBase64, - blockId - }) => { - const encodedArgs = argsBase64 || (args ? toBase64(JSON.stringify(args)) : ""); - const queryResult = await sendRpc( - "query", - withBlockId( - { - request_type: "call_function", - account_id: contractId, - method_name: methodName, - args_base64: encodedArgs - }, - blockId - ) - ); - return parseJsonFromBytes(queryResult.result.result); - }, "view"); - var queryAccount = /* @__PURE__ */ __name(async ({ - accountId: accountId2, - blockId - }) => { - return sendRpc( - "query", - withBlockId({ request_type: "view_account", account_id: accountId2 }, blockId) - ); - }, "queryAccount"); - var queryBlock = /* @__PURE__ */ __name(async ({ blockId }) => { - return sendRpc("block", withBlockId({}, blockId)); - }, "queryBlock"); - var queryAccessKey = /* @__PURE__ */ __name(async ({ - accountId: accountId2, - publicKey: publicKey2, - blockId - }) => { - return sendRpc( - "query", - withBlockId( - { request_type: "view_access_key", account_id: accountId2, public_key: publicKey2 }, - blockId - ) - ); - }, "queryAccessKey"); - var queryTx = /* @__PURE__ */ __name(async ({ txHash, accountId: accountId2 }) => { - return sendRpc("tx", [txHash, accountId2]); - }, "queryTx"); - var localTxHistory = /* @__PURE__ */ __name(() => { - return getTxHistory(); - }, "localTxHistory"); - var signOut = /* @__PURE__ */ __name(() => { - update({ accountId: null, privateKey: null, contractId: null }); - setConfig(NETWORKS[DEFAULT_NETWORK_ID]); - }, "signOut"); - var sendTx = /* @__PURE__ */ __name(async ({ - receiverId, - actions: actions2, - waitUntil - }) => { - const signerId = _state.accountId; - if (!signerId) throw new Error("Must sign in"); - const publicKey2 = _state.publicKey ?? ""; - const privKey = _state.privateKey; - const txId = generateTxId(); - if (!privKey || receiverId !== _state.accessKeyContractId || !canSignWithLAK(actions2)) { - const jsonTx = { signerId, receiverId, actions: actions2 }; - updateTxHistory({ status: "Pending", txId, tx: jsonTx, finalState: false }); - const url = new URL(typeof window !== "undefined" ? window.location.href : ""); - url.searchParams.set("txIds", txId); - const existingParams = new URLSearchParams(window.location.search); - existingParams.forEach((value, key) => { - if (!url.searchParams.has(key)) { - url.searchParams.set(key, value); - } - }); - url.searchParams.delete("errorCode"); - url.searchParams.delete("errorMessage"); - try { - const result = await _adapter.sendTransactions({ - transactions: [jsonTx], - callbackUrl: url.toString() - }); - if (result.url) { - if (typeof window !== "undefined") { - setTimeout(() => { - window.location.href = result.url; - }, 100); - } - } else if (result.outcomes?.length) { - result.outcomes.forEach( - (r) => updateTxHistory({ - txId, - status: "Executed", - result: r, - txHash: r.transaction.hash, - finalState: true - }) - ); - } else if (result.rejected) { - updateTxHistory({ txId, status: "RejectedByUser", finalState: true }); - } else if (result.error) { - updateTxHistory({ - txId, - status: "Error", - error: tryParseJson(result.error), - finalState: true - }); - } - return result; - } catch (err) { - console.error("fastnear: error sending tx using adapter:", err); - updateTxHistory({ - txId, - status: "Error", - error: tryParseJson(err.message), - finalState: true - }); - return Promise.reject(err); - } - } - let nonce = lsGet("nonce"); - if (nonce == null) { - const accessKey = await queryAccessKey({ accountId: signerId, publicKey: publicKey2 }); - if (accessKey.result.error) { - throw new Error(`Access key error: ${accessKey.result.error} when attempting to get nonce for ${signerId} for public key ${publicKey2}`); - } - nonce = accessKey.result.nonce; - lsSet("nonce", nonce); - } - let lastKnownBlock = lsGet("block"); - if (!lastKnownBlock || parseFloat(lastKnownBlock.header.timestamp_nanosec) / 1e6 + MaxBlockDelayMs < Date.now()) { - const latestBlock = await queryBlock({ blockId: "final" }); - lastKnownBlock = { - header: { - hash: latestBlock.result.header.hash, - timestamp_nanosec: latestBlock.result.header.timestamp_nanosec - } - }; - lsSet("block", lastKnownBlock); - } - nonce += 1; - lsSet("nonce", nonce); - const blockHash = lastKnownBlock.header.hash; - const plainTransactionObj = { - signerId, - publicKey: publicKey2, - nonce, - receiverId, - blockHash, - actions: actions2 - }; - const txBytes = serializeTransaction(plainTransactionObj); - const txHashBytes = sha256(txBytes); - const txHash58 = binary_to_base58_default(txHashBytes); - const signatureBase58 = signHash(txHashBytes, privKey, { returnBase58: true }); - const signedTransactionBytes = serializeSignedTransaction(plainTransactionObj, signatureBase58); - const signedTxBase64 = bytesToBase64(signedTransactionBytes); - updateTxHistory({ - status: "Pending", - txId, - tx: plainTransactionObj, - signature: signatureBase58, - signedTxBase64, - txHash: txHash58, - finalState: false - }); - try { - return await sendTxToRpc(signedTxBase64, waitUntil, txId); - } catch (error) { - console.error("Error Sending Transaction:", error, plainTransactionObj, signedTxBase64); - } - }, "sendTx"); - var exp2 = { - utils: {}, - // we will map this in a moment, giving keys, for IDE hints - borsh: exp.borsh, - borshSchema: exp.borshSchema.getBorshSchema() - }; - for (const key in src_exports2) { - exp2.utils[key] = src_exports2[key]; - } - var utils = exp2.utils; - var state = {}; - for (const key in state_exports) { - state[key] = state_exports[key]; - } - var event = state["events"]; - delete state["events"]; - try { - if (typeof window !== "undefined") { - const url = new URL(window.location.href); - const accId = url.searchParams.get("account_id"); - const pubKey = url.searchParams.get("public_key"); - const errCode = url.searchParams.get("errorCode"); - const errMsg = url.searchParams.get("errorMessage"); - const decodedErrMsg = errMsg ? decodeURIComponent(errMsg) : null; - const txHashes = url.searchParams.get("transactionHashes"); - const txIds = url.searchParams.get("txIds"); - if (errCode || errMsg) { - console.warn(new Error(`Wallet raises: -code: ${errCode} -message: ${decodedErrMsg}`)); - } - if (accId && pubKey) { - if (pubKey === _state.publicKey) { - update({ accountId: accId }); - } else { - if (authStatus() === "SignedIn") { - console.warn("Public key mismatch from wallet redirect", pubKey, _state.publicKey); - } - url.searchParams.delete("public_key"); - } - } - if (txHashes || txIds) { - const hashArr = txHashes ? txHashes.split(",") : []; - const idArr = txIds ? txIds.split(",") : []; - if (idArr.length > hashArr.length) { - idArr.forEach((id) => { - updateTxHistory({ txId: id, status: "RejectedByUser", finalState: true }); - }); - } else if (idArr.length === hashArr.length) { - idArr.forEach((id, i) => { - updateTxHistory({ - txId: id, - status: "PendingGotTxHash", - txHash: hashArr[i], - finalState: false - }); - afterTxSent(id); - }); - } else { - console.error(new Error("Transaction hash mismatch from wallet redirect"), idArr, hashArr); - } - } - url.searchParams.delete("txIds"); - if (authStatus() === "SignedOut") { - url.searchParams.delete("errorCode"); - url.searchParams.delete("errorMessage"); - } - } - } catch (e) { - console.error("Error handling wallet redirect:", e); - } - var actions = { - functionCall: /* @__PURE__ */ __name(({ - methodName, - gas, - deposit, - args, - argsBase64 - }) => ({ - type: "FunctionCall", - methodName, - args, - argsBase64, - gas, - deposit - }), "functionCall"), - transfer: /* @__PURE__ */ __name((yoctoAmount) => ({ - type: "Transfer", - deposit: yoctoAmount - }), "transfer"), - stakeNEAR: /* @__PURE__ */ __name(({ amount, publicKey: publicKey2 }) => ({ - type: "Stake", - stake: amount, - publicKey: publicKey2 - }), "stakeNEAR"), - addFullAccessKey: /* @__PURE__ */ __name(({ publicKey: publicKey2 }) => ({ - type: "AddKey", - publicKey: publicKey2, - accessKey: { permission: "FullAccess" } - }), "addFullAccessKey"), - addLimitedAccessKey: /* @__PURE__ */ __name(({ - publicKey: publicKey2, - allowance, - accountId: accountId2, - methodNames - }) => ({ - type: "AddKey", - publicKey: publicKey2, - accessKey: { - permission: "FunctionCall", - allowance, - receiverId: accountId2, - methodNames - } - }), "addLimitedAccessKey"), - deleteKey: /* @__PURE__ */ __name(({ publicKey: publicKey2 }) => ({ - type: "DeleteKey", - publicKey: publicKey2 - }), "deleteKey"), - deleteAccount: /* @__PURE__ */ __name(({ beneficiaryId }) => ({ - type: "DeleteAccount", - beneficiaryId - }), "deleteAccount"), - createAccount: /* @__PURE__ */ __name(() => ({ - type: "CreateAccount" - }), "createAccount"), - deployContract: /* @__PURE__ */ __name(({ codeBase64 }) => ({ - type: "DeployContract", - codeBase64 - }), "deployContract") - }; - return __toCommonJS(src_exports3); -})(); -/*! Bundled license information: - -@noble/hashes/esm/utils.js: - (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *) - -@noble/curves/esm/abstract/utils.js: - (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) - -@noble/curves/esm/abstract/modular.js: - (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) - -@noble/curves/esm/abstract/curve.js: - (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) - -@noble/curves/esm/abstract/edwards.js: - (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) - -@noble/curves/esm/ed25519.js: - (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) -*/ - -try { - Object.defineProperty(globalThis, 'near', { - value: near, - enumerable: true, - configurable: false, - }); -} catch (error) { - console.error('Could not define global "near" object', error); - throw error; -} - -window.$$ = near.utils.convertUnit; - -//# sourceMappingURL=browser.global.js.map diff --git a/static/js-loaded-globally/nearjs/0.9.7/umd/browser.global.js.map b/static/js-loaded-globally/nearjs/0.9.7/umd/browser.global.js.map deleted file mode 100644 index d7d9e1a..0000000 --- a/static/js-loaded-globally/nearjs/0.9.7/umd/browser.global.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/index.ts","../../../../node_modules/big.js/big.mjs","../../../utils/src/index.ts","../../../../node_modules/@noble/hashes/src/_assert.ts","../../../../node_modules/@noble/hashes/src/crypto.ts","../../../../node_modules/@noble/hashes/src/utils.ts","../../../../node_modules/@noble/hashes/src/_md.ts","../../../../node_modules/@noble/hashes/src/_u64.ts","../../../../node_modules/@noble/hashes/src/sha512.ts","../../../../node_modules/@noble/curves/src/abstract/utils.ts","../../../../node_modules/@noble/curves/src/abstract/modular.ts","../../../../node_modules/@noble/curves/src/abstract/curve.ts","../../../../node_modules/@noble/curves/src/abstract/edwards.ts","../../../../node_modules/@noble/curves/src/ed25519.ts","../../../../node_modules/@noble/hashes/src/sha256.ts","../../../utils/node_modules/base58-js/base58_chars.js","../../../utils/node_modules/base58-js/base58_to_binary.js","../../../utils/node_modules/base58-js/create_base58_map.js","../../../utils/node_modules/base58-js/binary_to_base58.js","../../../../node_modules/js-base64/base64.mjs","../../../utils/src/storage.ts","../../../utils/src/misc.ts","../../../utils/src/crypto.ts","../../../../node_modules/borsh/lib/esm/types.js","../../../../node_modules/borsh/lib/esm/buffer.js","../../../../node_modules/borsh/lib/esm/utils.js","../../../../node_modules/borsh/lib/esm/serialize.js","../../../../node_modules/borsh/lib/esm/deserialize.js","../../../../node_modules/borsh/lib/esm/index.js","../../../borsh-schema/src/index.ts","../../../utils/src/transaction.ts","../../src/state.ts","../../../wallet-adapter/src/index.ts","../../src/near.ts"],"sourcesContent":["// See tsup.config.ts for additional banner/footer js\nexport * from \"./near.js\";\n","/*\r\n * big.js v6.2.2\r\n * A small, fast, easy-to-use library for arbitrary-precision decimal arithmetic.\r\n * Copyright (c) 2024 Michael Mclaughlin\r\n * https://github.com/MikeMcl/big.js/LICENCE.md\r\n */\r\n\r\n\r\n/************************************** EDITABLE DEFAULTS *****************************************/\r\n\r\n\r\n // The default values below must be integers within the stated ranges.\r\n\r\n /*\r\n * The maximum number of decimal places (DP) of the results of operations involving division:\r\n * div and sqrt, and pow with negative exponents.\r\n */\r\nvar DP = 20, // 0 to MAX_DP\r\n\r\n /*\r\n * The rounding mode (RM) used when rounding to the above decimal places.\r\n *\r\n * 0 Towards zero (i.e. truncate, no rounding). (ROUND_DOWN)\r\n * 1 To nearest neighbour. If equidistant, round up. (ROUND_HALF_UP)\r\n * 2 To nearest neighbour. If equidistant, to even. (ROUND_HALF_EVEN)\r\n * 3 Away from zero. (ROUND_UP)\r\n */\r\n RM = 1, // 0, 1, 2 or 3\r\n\r\n // The maximum value of DP and Big.DP.\r\n MAX_DP = 1E6, // 0 to 1000000\r\n\r\n // The maximum magnitude of the exponent argument to the pow method.\r\n MAX_POWER = 1E6, // 1 to 1000000\r\n\r\n /*\r\n * The negative exponent (NE) at and beneath which toString returns exponential notation.\r\n * (JavaScript numbers: -7)\r\n * -1000000 is the minimum recommended exponent value of a Big.\r\n */\r\n NE = -7, // 0 to -1000000\r\n\r\n /*\r\n * The positive exponent (PE) at and above which toString returns exponential notation.\r\n * (JavaScript numbers: 21)\r\n * 1000000 is the maximum recommended exponent value of a Big, but this limit is not enforced.\r\n */\r\n PE = 21, // 0 to 1000000\r\n\r\n /*\r\n * When true, an error will be thrown if a primitive number is passed to the Big constructor,\r\n * or if valueOf is called, or if toNumber is called on a Big which cannot be converted to a\r\n * primitive number without a loss of precision.\r\n */\r\n STRICT = false, // true or false\r\n\r\n\r\n/**************************************************************************************************/\r\n\r\n\r\n // Error messages.\r\n NAME = '[big.js] ',\r\n INVALID = NAME + 'Invalid ',\r\n INVALID_DP = INVALID + 'decimal places',\r\n INVALID_RM = INVALID + 'rounding mode',\r\n DIV_BY_ZERO = NAME + 'Division by zero',\r\n\r\n // The shared prototype object.\r\n P = {},\r\n UNDEFINED = void 0,\r\n NUMERIC = /^-?(\\d+(\\.\\d*)?|\\.\\d+)(e[+-]?\\d+)?$/i;\r\n\r\n\r\n/*\r\n * Create and return a Big constructor.\r\n */\r\nfunction _Big_() {\r\n\r\n /*\r\n * The Big constructor and exported function.\r\n * Create and return a new instance of a Big number object.\r\n *\r\n * n {number|string|Big} A numeric value.\r\n */\r\n function Big(n) {\r\n var x = this;\r\n\r\n // Enable constructor usage without new.\r\n if (!(x instanceof Big)) return n === UNDEFINED ? _Big_() : new Big(n);\r\n\r\n // Duplicate.\r\n if (n instanceof Big) {\r\n x.s = n.s;\r\n x.e = n.e;\r\n x.c = n.c.slice();\r\n } else {\r\n if (typeof n !== 'string') {\r\n if (Big.strict === true && typeof n !== 'bigint') {\r\n throw TypeError(INVALID + 'value');\r\n }\r\n\r\n // Minus zero?\r\n n = n === 0 && 1 / n < 0 ? '-0' : String(n);\r\n }\r\n\r\n parse(x, n);\r\n }\r\n\r\n // Retain a reference to this Big constructor.\r\n // Shadow Big.prototype.constructor which points to Object.\r\n x.constructor = Big;\r\n }\r\n\r\n Big.prototype = P;\r\n Big.DP = DP;\r\n Big.RM = RM;\r\n Big.NE = NE;\r\n Big.PE = PE;\r\n Big.strict = STRICT;\r\n Big.roundDown = 0;\r\n Big.roundHalfUp = 1;\r\n Big.roundHalfEven = 2;\r\n Big.roundUp = 3;\r\n\r\n return Big;\r\n}\r\n\r\n\r\n/*\r\n * Parse the number or string value passed to a Big constructor.\r\n *\r\n * x {Big} A Big number instance.\r\n * n {number|string} A numeric value.\r\n */\r\nfunction parse(x, n) {\r\n var e, i, nl;\r\n\r\n if (!NUMERIC.test(n)) {\r\n throw Error(INVALID + 'number');\r\n }\r\n\r\n // Determine sign.\r\n x.s = n.charAt(0) == '-' ? (n = n.slice(1), -1) : 1;\r\n\r\n // Decimal point?\r\n if ((e = n.indexOf('.')) > -1) n = n.replace('.', '');\r\n\r\n // Exponential form?\r\n if ((i = n.search(/e/i)) > 0) {\r\n\r\n // Determine exponent.\r\n if (e < 0) e = i;\r\n e += +n.slice(i + 1);\r\n n = n.substring(0, i);\r\n } else if (e < 0) {\r\n\r\n // Integer.\r\n e = n.length;\r\n }\r\n\r\n nl = n.length;\r\n\r\n // Determine leading zeros.\r\n for (i = 0; i < nl && n.charAt(i) == '0';) ++i;\r\n\r\n if (i == nl) {\r\n\r\n // Zero.\r\n x.c = [x.e = 0];\r\n } else {\r\n\r\n // Determine trailing zeros.\r\n for (; nl > 0 && n.charAt(--nl) == '0';);\r\n x.e = e - i - 1;\r\n x.c = [];\r\n\r\n // Convert string to array of digits without leading/trailing zeros.\r\n for (e = 0; i <= nl;) x.c[e++] = +n.charAt(i++);\r\n }\r\n\r\n return x;\r\n}\r\n\r\n\r\n/*\r\n * Round Big x to a maximum of sd significant digits using rounding mode rm.\r\n *\r\n * x {Big} The Big to round.\r\n * sd {number} Significant digits: integer, 0 to MAX_DP inclusive.\r\n * rm {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).\r\n * [more] {boolean} Whether the result of division was truncated.\r\n */\r\nfunction round(x, sd, rm, more) {\r\n var xc = x.c;\r\n\r\n if (rm === UNDEFINED) rm = x.constructor.RM;\r\n if (rm !== 0 && rm !== 1 && rm !== 2 && rm !== 3) {\r\n throw Error(INVALID_RM);\r\n }\r\n\r\n if (sd < 1) {\r\n more =\r\n rm === 3 && (more || !!xc[0]) || sd === 0 && (\r\n rm === 1 && xc[0] >= 5 ||\r\n rm === 2 && (xc[0] > 5 || xc[0] === 5 && (more || xc[1] !== UNDEFINED))\r\n );\r\n\r\n xc.length = 1;\r\n\r\n if (more) {\r\n\r\n // 1, 0.1, 0.01, 0.001, 0.0001 etc.\r\n x.e = x.e - sd + 1;\r\n xc[0] = 1;\r\n } else {\r\n\r\n // Zero.\r\n xc[0] = x.e = 0;\r\n }\r\n } else if (sd < xc.length) {\r\n\r\n // xc[sd] is the digit after the digit that may be rounded up.\r\n more =\r\n rm === 1 && xc[sd] >= 5 ||\r\n rm === 2 && (xc[sd] > 5 || xc[sd] === 5 &&\r\n (more || xc[sd + 1] !== UNDEFINED || xc[sd - 1] & 1)) ||\r\n rm === 3 && (more || !!xc[0]);\r\n\r\n // Remove any digits after the required precision.\r\n xc.length = sd;\r\n\r\n // Round up?\r\n if (more) {\r\n\r\n // Rounding up may mean the previous digit has to be rounded up.\r\n for (; ++xc[--sd] > 9;) {\r\n xc[sd] = 0;\r\n if (sd === 0) {\r\n ++x.e;\r\n xc.unshift(1);\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // Remove trailing zeros.\r\n for (sd = xc.length; !xc[--sd];) xc.pop();\r\n }\r\n\r\n return x;\r\n}\r\n\r\n\r\n/*\r\n * Return a string representing the value of Big x in normal or exponential notation.\r\n * Handles P.toExponential, P.toFixed, P.toJSON, P.toPrecision, P.toString and P.valueOf.\r\n */\r\nfunction stringify(x, doExponential, isNonzero) {\r\n var e = x.e,\r\n s = x.c.join(''),\r\n n = s.length;\r\n\r\n // Exponential notation?\r\n if (doExponential) {\r\n s = s.charAt(0) + (n > 1 ? '.' + s.slice(1) : '') + (e < 0 ? 'e' : 'e+') + e;\r\n\r\n // Normal notation.\r\n } else if (e < 0) {\r\n for (; ++e;) s = '0' + s;\r\n s = '0.' + s;\r\n } else if (e > 0) {\r\n if (++e > n) {\r\n for (e -= n; e--;) s += '0';\r\n } else if (e < n) {\r\n s = s.slice(0, e) + '.' + s.slice(e);\r\n }\r\n } else if (n > 1) {\r\n s = s.charAt(0) + '.' + s.slice(1);\r\n }\r\n\r\n return x.s < 0 && isNonzero ? '-' + s : s;\r\n}\r\n\r\n\r\n// Prototype/instance methods\r\n\r\n\r\n/*\r\n * Return a new Big whose value is the absolute value of this Big.\r\n */\r\nP.abs = function () {\r\n var x = new this.constructor(this);\r\n x.s = 1;\r\n return x;\r\n};\r\n\r\n\r\n/*\r\n * Return 1 if the value of this Big is greater than the value of Big y,\r\n * -1 if the value of this Big is less than the value of Big y, or\r\n * 0 if they have the same value.\r\n */\r\nP.cmp = function (y) {\r\n var isneg,\r\n x = this,\r\n xc = x.c,\r\n yc = (y = new x.constructor(y)).c,\r\n i = x.s,\r\n j = y.s,\r\n k = x.e,\r\n l = y.e;\r\n\r\n // Either zero?\r\n if (!xc[0] || !yc[0]) return !xc[0] ? !yc[0] ? 0 : -j : i;\r\n\r\n // Signs differ?\r\n if (i != j) return i;\r\n\r\n isneg = i < 0;\r\n\r\n // Compare exponents.\r\n if (k != l) return k > l ^ isneg ? 1 : -1;\r\n\r\n j = (k = xc.length) < (l = yc.length) ? k : l;\r\n\r\n // Compare digit by digit.\r\n for (i = -1; ++i < j;) {\r\n if (xc[i] != yc[i]) return xc[i] > yc[i] ^ isneg ? 1 : -1;\r\n }\r\n\r\n // Compare lengths.\r\n return k == l ? 0 : k > l ^ isneg ? 1 : -1;\r\n};\r\n\r\n\r\n/*\r\n * Return a new Big whose value is the value of this Big divided by the value of Big y, rounded,\r\n * if necessary, to a maximum of Big.DP decimal places using rounding mode Big.RM.\r\n */\r\nP.div = function (y) {\r\n var x = this,\r\n Big = x.constructor,\r\n a = x.c, // dividend\r\n b = (y = new Big(y)).c, // divisor\r\n k = x.s == y.s ? 1 : -1,\r\n dp = Big.DP;\r\n\r\n if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {\r\n throw Error(INVALID_DP);\r\n }\r\n\r\n // Divisor is zero?\r\n if (!b[0]) {\r\n throw Error(DIV_BY_ZERO);\r\n }\r\n\r\n // Dividend is 0? Return +-0.\r\n if (!a[0]) {\r\n y.s = k;\r\n y.c = [y.e = 0];\r\n return y;\r\n }\r\n\r\n var bl, bt, n, cmp, ri,\r\n bz = b.slice(),\r\n ai = bl = b.length,\r\n al = a.length,\r\n r = a.slice(0, bl), // remainder\r\n rl = r.length,\r\n q = y, // quotient\r\n qc = q.c = [],\r\n qi = 0,\r\n p = dp + (q.e = x.e - y.e) + 1; // precision of the result\r\n\r\n q.s = k;\r\n k = p < 0 ? 0 : p;\r\n\r\n // Create version of divisor with leading zero.\r\n bz.unshift(0);\r\n\r\n // Add zeros to make remainder as long as divisor.\r\n for (; rl++ < bl;) r.push(0);\r\n\r\n do {\r\n\r\n // n is how many times the divisor goes into current remainder.\r\n for (n = 0; n < 10; n++) {\r\n\r\n // Compare divisor and remainder.\r\n if (bl != (rl = r.length)) {\r\n cmp = bl > rl ? 1 : -1;\r\n } else {\r\n for (ri = -1, cmp = 0; ++ri < bl;) {\r\n if (b[ri] != r[ri]) {\r\n cmp = b[ri] > r[ri] ? 1 : -1;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // If divisor < remainder, subtract divisor from remainder.\r\n if (cmp < 0) {\r\n\r\n // Remainder can't be more than 1 digit longer than divisor.\r\n // Equalise lengths using divisor with extra leading zero?\r\n for (bt = rl == bl ? b : bz; rl;) {\r\n if (r[--rl] < bt[rl]) {\r\n ri = rl;\r\n for (; ri && !r[--ri];) r[ri] = 9;\r\n --r[ri];\r\n r[rl] += 10;\r\n }\r\n r[rl] -= bt[rl];\r\n }\r\n\r\n for (; !r[0];) r.shift();\r\n } else {\r\n break;\r\n }\r\n }\r\n\r\n // Add the digit n to the result array.\r\n qc[qi++] = cmp ? n : ++n;\r\n\r\n // Update the remainder.\r\n if (r[0] && cmp) r[rl] = a[ai] || 0;\r\n else r = [a[ai]];\r\n\r\n } while ((ai++ < al || r[0] !== UNDEFINED) && k--);\r\n\r\n // Leading zero? Do not remove if result is simply zero (qi == 1).\r\n if (!qc[0] && qi != 1) {\r\n\r\n // There can't be more than one zero.\r\n qc.shift();\r\n q.e--;\r\n p--;\r\n }\r\n\r\n // Round?\r\n if (qi > p) round(q, p, Big.RM, r[0] !== UNDEFINED);\r\n\r\n return q;\r\n};\r\n\r\n\r\n/*\r\n * Return true if the value of this Big is equal to the value of Big y, otherwise return false.\r\n */\r\nP.eq = function (y) {\r\n return this.cmp(y) === 0;\r\n};\r\n\r\n\r\n/*\r\n * Return true if the value of this Big is greater than the value of Big y, otherwise return\r\n * false.\r\n */\r\nP.gt = function (y) {\r\n return this.cmp(y) > 0;\r\n};\r\n\r\n\r\n/*\r\n * Return true if the value of this Big is greater than or equal to the value of Big y, otherwise\r\n * return false.\r\n */\r\nP.gte = function (y) {\r\n return this.cmp(y) > -1;\r\n};\r\n\r\n\r\n/*\r\n * Return true if the value of this Big is less than the value of Big y, otherwise return false.\r\n */\r\nP.lt = function (y) {\r\n return this.cmp(y) < 0;\r\n};\r\n\r\n\r\n/*\r\n * Return true if the value of this Big is less than or equal to the value of Big y, otherwise\r\n * return false.\r\n */\r\nP.lte = function (y) {\r\n return this.cmp(y) < 1;\r\n};\r\n\r\n\r\n/*\r\n * Return a new Big whose value is the value of this Big minus the value of Big y.\r\n */\r\nP.minus = P.sub = function (y) {\r\n var i, j, t, xlty,\r\n x = this,\r\n Big = x.constructor,\r\n a = x.s,\r\n b = (y = new Big(y)).s;\r\n\r\n // Signs differ?\r\n if (a != b) {\r\n y.s = -b;\r\n return x.plus(y);\r\n }\r\n\r\n var xc = x.c.slice(),\r\n xe = x.e,\r\n yc = y.c,\r\n ye = y.e;\r\n\r\n // Either zero?\r\n if (!xc[0] || !yc[0]) {\r\n if (yc[0]) {\r\n y.s = -b;\r\n } else if (xc[0]) {\r\n y = new Big(x);\r\n } else {\r\n y.s = 1;\r\n }\r\n return y;\r\n }\r\n\r\n // Determine which is the bigger number. Prepend zeros to equalise exponents.\r\n if (a = xe - ye) {\r\n\r\n if (xlty = a < 0) {\r\n a = -a;\r\n t = xc;\r\n } else {\r\n ye = xe;\r\n t = yc;\r\n }\r\n\r\n t.reverse();\r\n for (b = a; b--;) t.push(0);\r\n t.reverse();\r\n } else {\r\n\r\n // Exponents equal. Check digit by digit.\r\n j = ((xlty = xc.length < yc.length) ? xc : yc).length;\r\n\r\n for (a = b = 0; b < j; b++) {\r\n if (xc[b] != yc[b]) {\r\n xlty = xc[b] < yc[b];\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // x < y? Point xc to the array of the bigger number.\r\n if (xlty) {\r\n t = xc;\r\n xc = yc;\r\n yc = t;\r\n y.s = -y.s;\r\n }\r\n\r\n /*\r\n * Append zeros to xc if shorter. No need to add zeros to yc if shorter as subtraction only\r\n * needs to start at yc.length.\r\n */\r\n if ((b = (j = yc.length) - (i = xc.length)) > 0) for (; b--;) xc[i++] = 0;\r\n\r\n // Subtract yc from xc.\r\n for (b = i; j > a;) {\r\n if (xc[--j] < yc[j]) {\r\n for (i = j; i && !xc[--i];) xc[i] = 9;\r\n --xc[i];\r\n xc[j] += 10;\r\n }\r\n\r\n xc[j] -= yc[j];\r\n }\r\n\r\n // Remove trailing zeros.\r\n for (; xc[--b] === 0;) xc.pop();\r\n\r\n // Remove leading zeros and adjust exponent accordingly.\r\n for (; xc[0] === 0;) {\r\n xc.shift();\r\n --ye;\r\n }\r\n\r\n if (!xc[0]) {\r\n\r\n // n - n = +0\r\n y.s = 1;\r\n\r\n // Result must be zero.\r\n xc = [ye = 0];\r\n }\r\n\r\n y.c = xc;\r\n y.e = ye;\r\n\r\n return y;\r\n};\r\n\r\n\r\n/*\r\n * Return a new Big whose value is the value of this Big modulo the value of Big y.\r\n */\r\nP.mod = function (y) {\r\n var ygtx,\r\n x = this,\r\n Big = x.constructor,\r\n a = x.s,\r\n b = (y = new Big(y)).s;\r\n\r\n if (!y.c[0]) {\r\n throw Error(DIV_BY_ZERO);\r\n }\r\n\r\n x.s = y.s = 1;\r\n ygtx = y.cmp(x) == 1;\r\n x.s = a;\r\n y.s = b;\r\n\r\n if (ygtx) return new Big(x);\r\n\r\n a = Big.DP;\r\n b = Big.RM;\r\n Big.DP = Big.RM = 0;\r\n x = x.div(y);\r\n Big.DP = a;\r\n Big.RM = b;\r\n\r\n return this.minus(x.times(y));\r\n};\r\n\r\n\r\n/*\r\n * Return a new Big whose value is the value of this Big negated.\r\n */\r\nP.neg = function () {\r\n var x = new this.constructor(this);\r\n x.s = -x.s;\r\n return x;\r\n};\r\n\r\n\r\n/*\r\n * Return a new Big whose value is the value of this Big plus the value of Big y.\r\n */\r\nP.plus = P.add = function (y) {\r\n var e, k, t,\r\n x = this,\r\n Big = x.constructor;\r\n\r\n y = new Big(y);\r\n\r\n // Signs differ?\r\n if (x.s != y.s) {\r\n y.s = -y.s;\r\n return x.minus(y);\r\n }\r\n\r\n var xe = x.e,\r\n xc = x.c,\r\n ye = y.e,\r\n yc = y.c;\r\n\r\n // Either zero?\r\n if (!xc[0] || !yc[0]) {\r\n if (!yc[0]) {\r\n if (xc[0]) {\r\n y = new Big(x);\r\n } else {\r\n y.s = x.s;\r\n }\r\n }\r\n return y;\r\n }\r\n\r\n xc = xc.slice();\r\n\r\n // Prepend zeros to equalise exponents.\r\n // Note: reverse faster than unshifts.\r\n if (e = xe - ye) {\r\n if (e > 0) {\r\n ye = xe;\r\n t = yc;\r\n } else {\r\n e = -e;\r\n t = xc;\r\n }\r\n\r\n t.reverse();\r\n for (; e--;) t.push(0);\r\n t.reverse();\r\n }\r\n\r\n // Point xc to the longer array.\r\n if (xc.length - yc.length < 0) {\r\n t = yc;\r\n yc = xc;\r\n xc = t;\r\n }\r\n\r\n e = yc.length;\r\n\r\n // Only start adding at yc.length - 1 as the further digits of xc can be left as they are.\r\n for (k = 0; e; xc[e] %= 10) k = (xc[--e] = xc[e] + yc[e] + k) / 10 | 0;\r\n\r\n // No need to check for zero, as +x + +y != 0 && -x + -y != 0\r\n\r\n if (k) {\r\n xc.unshift(k);\r\n ++ye;\r\n }\r\n\r\n // Remove trailing zeros.\r\n for (e = xc.length; xc[--e] === 0;) xc.pop();\r\n\r\n y.c = xc;\r\n y.e = ye;\r\n\r\n return y;\r\n};\r\n\r\n\r\n/*\r\n * Return a Big whose value is the value of this Big raised to the power n.\r\n * If n is negative, round to a maximum of Big.DP decimal places using rounding\r\n * mode Big.RM.\r\n *\r\n * n {number} Integer, -MAX_POWER to MAX_POWER inclusive.\r\n */\r\nP.pow = function (n) {\r\n var x = this,\r\n one = new x.constructor('1'),\r\n y = one,\r\n isneg = n < 0;\r\n\r\n if (n !== ~~n || n < -MAX_POWER || n > MAX_POWER) {\r\n throw Error(INVALID + 'exponent');\r\n }\r\n\r\n if (isneg) n = -n;\r\n\r\n for (;;) {\r\n if (n & 1) y = y.times(x);\r\n n >>= 1;\r\n if (!n) break;\r\n x = x.times(x);\r\n }\r\n\r\n return isneg ? one.div(y) : y;\r\n};\r\n\r\n\r\n/*\r\n * Return a new Big whose value is the value of this Big rounded to a maximum precision of sd\r\n * significant digits using rounding mode rm, or Big.RM if rm is not specified.\r\n *\r\n * sd {number} Significant digits: integer, 1 to MAX_DP inclusive.\r\n * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).\r\n */\r\nP.prec = function (sd, rm) {\r\n if (sd !== ~~sd || sd < 1 || sd > MAX_DP) {\r\n throw Error(INVALID + 'precision');\r\n }\r\n return round(new this.constructor(this), sd, rm);\r\n};\r\n\r\n\r\n/*\r\n * Return a new Big whose value is the value of this Big rounded to a maximum of dp decimal places\r\n * using rounding mode rm, or Big.RM if rm is not specified.\r\n * If dp is negative, round to an integer which is a multiple of 10**-dp.\r\n * If dp is not specified, round to 0 decimal places.\r\n *\r\n * dp? {number} Integer, -MAX_DP to MAX_DP inclusive.\r\n * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).\r\n */\r\nP.round = function (dp, rm) {\r\n if (dp === UNDEFINED) dp = 0;\r\n else if (dp !== ~~dp || dp < -MAX_DP || dp > MAX_DP) {\r\n throw Error(INVALID_DP);\r\n }\r\n return round(new this.constructor(this), dp + this.e + 1, rm);\r\n};\r\n\r\n\r\n/*\r\n * Return a new Big whose value is the square root of the value of this Big, rounded, if\r\n * necessary, to a maximum of Big.DP decimal places using rounding mode Big.RM.\r\n */\r\nP.sqrt = function () {\r\n var r, c, t,\r\n x = this,\r\n Big = x.constructor,\r\n s = x.s,\r\n e = x.e,\r\n half = new Big('0.5');\r\n\r\n // Zero?\r\n if (!x.c[0]) return new Big(x);\r\n\r\n // Negative?\r\n if (s < 0) {\r\n throw Error(NAME + 'No square root');\r\n }\r\n\r\n // Estimate.\r\n s = Math.sqrt(+stringify(x, true, true));\r\n\r\n // Math.sqrt underflow/overflow?\r\n // Re-estimate: pass x coefficient to Math.sqrt as integer, then adjust the result exponent.\r\n if (s === 0 || s === 1 / 0) {\r\n c = x.c.join('');\r\n if (!(c.length + e & 1)) c += '0';\r\n s = Math.sqrt(c);\r\n e = ((e + 1) / 2 | 0) - (e < 0 || e & 1);\r\n r = new Big((s == 1 / 0 ? '5e' : (s = s.toExponential()).slice(0, s.indexOf('e') + 1)) + e);\r\n } else {\r\n r = new Big(s + '');\r\n }\r\n\r\n e = r.e + (Big.DP += 4);\r\n\r\n // Newton-Raphson iteration.\r\n do {\r\n t = r;\r\n r = half.times(t.plus(x.div(t)));\r\n } while (t.c.slice(0, e).join('') !== r.c.slice(0, e).join(''));\r\n\r\n return round(r, (Big.DP -= 4) + r.e + 1, Big.RM);\r\n};\r\n\r\n\r\n/*\r\n * Return a new Big whose value is the value of this Big times the value of Big y.\r\n */\r\nP.times = P.mul = function (y) {\r\n var c,\r\n x = this,\r\n Big = x.constructor,\r\n xc = x.c,\r\n yc = (y = new Big(y)).c,\r\n a = xc.length,\r\n b = yc.length,\r\n i = x.e,\r\n j = y.e;\r\n\r\n // Determine sign of result.\r\n y.s = x.s == y.s ? 1 : -1;\r\n\r\n // Return signed 0 if either 0.\r\n if (!xc[0] || !yc[0]) {\r\n y.c = [y.e = 0];\r\n return y;\r\n }\r\n\r\n // Initialise exponent of result as x.e + y.e.\r\n y.e = i + j;\r\n\r\n // If array xc has fewer digits than yc, swap xc and yc, and lengths.\r\n if (a < b) {\r\n c = xc;\r\n xc = yc;\r\n yc = c;\r\n j = a;\r\n a = b;\r\n b = j;\r\n }\r\n\r\n // Initialise coefficient array of result with zeros.\r\n for (c = new Array(j = a + b); j--;) c[j] = 0;\r\n\r\n // Multiply.\r\n\r\n // i is initially xc.length.\r\n for (i = b; i--;) {\r\n b = 0;\r\n\r\n // a is yc.length.\r\n for (j = a + i; j > i;) {\r\n\r\n // Current sum of products at this digit position, plus carry.\r\n b = c[j] + yc[i] * xc[j - i - 1] + b;\r\n c[j--] = b % 10;\r\n\r\n // carry\r\n b = b / 10 | 0;\r\n }\r\n\r\n c[j] = b;\r\n }\r\n\r\n // Increment result exponent if there is a final carry, otherwise remove leading zero.\r\n if (b) ++y.e;\r\n else c.shift();\r\n\r\n // Remove trailing zeros.\r\n for (i = c.length; !c[--i];) c.pop();\r\n y.c = c;\r\n\r\n return y;\r\n};\r\n\r\n\r\n/*\r\n * Return a string representing the value of this Big in exponential notation rounded to dp fixed\r\n * decimal places using rounding mode rm, or Big.RM if rm is not specified.\r\n *\r\n * dp? {number} Decimal places: integer, 0 to MAX_DP inclusive.\r\n * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).\r\n */\r\nP.toExponential = function (dp, rm) {\r\n var x = this,\r\n n = x.c[0];\r\n\r\n if (dp !== UNDEFINED) {\r\n if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {\r\n throw Error(INVALID_DP);\r\n }\r\n x = round(new x.constructor(x), ++dp, rm);\r\n for (; x.c.length < dp;) x.c.push(0);\r\n }\r\n\r\n return stringify(x, true, !!n);\r\n};\r\n\r\n\r\n/*\r\n * Return a string representing the value of this Big in normal notation rounded to dp fixed\r\n * decimal places using rounding mode rm, or Big.RM if rm is not specified.\r\n *\r\n * dp? {number} Decimal places: integer, 0 to MAX_DP inclusive.\r\n * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).\r\n *\r\n * (-0).toFixed(0) is '0', but (-0.1).toFixed(0) is '-0'.\r\n * (-0).toFixed(1) is '0.0', but (-0.01).toFixed(1) is '-0.0'.\r\n */\r\nP.toFixed = function (dp, rm) {\r\n var x = this,\r\n n = x.c[0];\r\n\r\n if (dp !== UNDEFINED) {\r\n if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {\r\n throw Error(INVALID_DP);\r\n }\r\n x = round(new x.constructor(x), dp + x.e + 1, rm);\r\n\r\n // x.e may have changed if the value is rounded up.\r\n for (dp = dp + x.e + 1; x.c.length < dp;) x.c.push(0);\r\n }\r\n\r\n return stringify(x, false, !!n);\r\n};\r\n\r\n\r\n/*\r\n * Return a string representing the value of this Big.\r\n * Return exponential notation if this Big has a positive exponent equal to or greater than\r\n * Big.PE, or a negative exponent equal to or less than Big.NE.\r\n * Omit the sign for negative zero.\r\n */\r\nP[Symbol.for('nodejs.util.inspect.custom')] = P.toJSON = P.toString = function () {\r\n var x = this,\r\n Big = x.constructor;\r\n return stringify(x, x.e <= Big.NE || x.e >= Big.PE, !!x.c[0]);\r\n};\r\n\r\n\r\n/*\r\n * Return the value of this Big as a primitve number.\r\n */\r\nP.toNumber = function () {\r\n var n = +stringify(this, true, true);\r\n if (this.constructor.strict === true && !this.eq(n.toString())) {\r\n throw Error(NAME + 'Imprecise conversion');\r\n }\r\n return n;\r\n};\r\n\r\n\r\n/*\r\n * Return a string representing the value of this Big rounded to sd significant digits using\r\n * rounding mode rm, or Big.RM if rm is not specified.\r\n * Use exponential notation if sd is less than the number of digits necessary to represent\r\n * the integer part of the value in normal notation.\r\n *\r\n * sd {number} Significant digits: integer, 1 to MAX_DP inclusive.\r\n * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).\r\n */\r\nP.toPrecision = function (sd, rm) {\r\n var x = this,\r\n Big = x.constructor,\r\n n = x.c[0];\r\n\r\n if (sd !== UNDEFINED) {\r\n if (sd !== ~~sd || sd < 1 || sd > MAX_DP) {\r\n throw Error(INVALID + 'precision');\r\n }\r\n x = round(new Big(x), sd, rm);\r\n for (; x.c.length < sd;) x.c.push(0);\r\n }\r\n\r\n return stringify(x, sd <= x.e || x.e <= Big.NE || x.e >= Big.PE, !!n);\r\n};\r\n\r\n\r\n/*\r\n * Return a string representing the value of this Big.\r\n * Return exponential notation if this Big has a positive exponent equal to or greater than\r\n * Big.PE, or a negative exponent equal to or less than Big.NE.\r\n * Include the sign for negative zero.\r\n */\r\nP.valueOf = function () {\r\n var x = this,\r\n Big = x.constructor;\r\n if (Big.strict === true) {\r\n throw Error(NAME + 'valueOf disallowed');\r\n }\r\n return stringify(x, x.e <= Big.NE || x.e >= Big.PE, true);\r\n};\r\n\r\n\r\n// Export\r\n\r\n\r\nexport var Big = _Big_();\r\n\r\n/// \r\nexport default Big;\r\n","export * from \"./crypto.js\";\nexport * from \"./transaction.js\";\nexport * from \"./misc.js\";\nexport * from \"./storage.js\";\n\nimport { serialize, deserialize } from \"borsh\";\nimport * as borshSchema from \"@fastnear/borsh-schema\";\n\n// exports (or re-exports as well)\nconst exp = {\n borsh: {\n serialize,\n deserialize\n },\n borshSchema,\n}\n\nexport { exp }\n","/**\n * Internal assertion helpers.\n * @module\n */\n\n/** Asserts something is positive integer. */\nfunction anumber(n: number): void {\n if (!Number.isSafeInteger(n) || n < 0) throw new Error('positive integer expected, got ' + n);\n}\n\n/** Is number an Uint8Array? Copied from utils for perf. */\nfunction isBytes(a: unknown): a is Uint8Array {\n return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');\n}\n\n/** Asserts something is Uint8Array. */\nfunction abytes(b: Uint8Array | undefined, ...lengths: number[]): void {\n if (!isBytes(b)) throw new Error('Uint8Array expected');\n if (lengths.length > 0 && !lengths.includes(b.length))\n throw new Error('Uint8Array expected of length ' + lengths + ', got length=' + b.length);\n}\n\n/** Hash interface. */\nexport type Hash = {\n (data: Uint8Array): Uint8Array;\n blockLen: number;\n outputLen: number;\n create: any;\n};\n\n/** Asserts something is hash */\nfunction ahash(h: Hash): void {\n if (typeof h !== 'function' || typeof h.create !== 'function')\n throw new Error('Hash should be wrapped by utils.wrapConstructor');\n anumber(h.outputLen);\n anumber(h.blockLen);\n}\n\n/** Asserts a hash instance has not been destroyed / finished */\nfunction aexists(instance: any, checkFinished = true): void {\n if (instance.destroyed) throw new Error('Hash instance has been destroyed');\n if (checkFinished && instance.finished) throw new Error('Hash#digest() has already been called');\n}\n\n/** Asserts output is properly-sized byte array */\nfunction aoutput(out: any, instance: any): void {\n abytes(out);\n const min = instance.outputLen;\n if (out.length < min) {\n throw new Error('digestInto() expects output buffer of length at least ' + min);\n }\n}\n\nexport { anumber, abytes, ahash, aexists, aoutput };\n","/**\n * Internal webcrypto alias.\n * We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.\n * See utils.ts for details.\n * @module\n */\ndeclare const globalThis: Record | undefined;\nexport const crypto: any =\n typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;\n","/**\n * Utilities for hex, bytes, CSPRNG.\n * @module\n */\n/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */\n\n// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.\n// node.js versions earlier than v19 don't declare it in global scope.\n// For node.js, package.json#exports field mapping rewrites import\n// from `crypto` to `cryptoNode`, which imports native module.\n// Makes the utils un-importable in browsers without a bundler.\n// Once node.js 18 is deprecated (2025-04-30), we can just drop the import.\nimport { crypto } from '@noble/hashes/crypto';\nimport { abytes } from './_assert.js';\n// export { isBytes } from './_assert.js';\n// We can't reuse isBytes from _assert, because somehow this causes huge perf issues\nexport function isBytes(a: unknown): a is Uint8Array {\n return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');\n}\n\n// prettier-ignore\nexport type TypedArray = Int8Array | Uint8ClampedArray | Uint8Array |\n Uint16Array | Int16Array | Uint32Array | Int32Array;\n\n// Cast array to different type\nexport function u8(arr: TypedArray): Uint8Array {\n return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);\n}\nexport function u32(arr: TypedArray): Uint32Array {\n return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));\n}\n\n// Cast array to view\nexport function createView(arr: TypedArray): DataView {\n return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);\n}\n\n/** The rotate right (circular right shift) operation for uint32 */\nexport function rotr(word: number, shift: number): number {\n return (word << (32 - shift)) | (word >>> shift);\n}\n/** The rotate left (circular left shift) operation for uint32 */\nexport function rotl(word: number, shift: number): number {\n return (word << shift) | ((word >>> (32 - shift)) >>> 0);\n}\n\n/** Is current platform little-endian? Most are. Big-Endian platform: IBM */\nexport const isLE: boolean = /* @__PURE__ */ (() =>\n new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();\n// The byte swap operation for uint32\nexport function byteSwap(word: number): number {\n return (\n ((word << 24) & 0xff000000) |\n ((word << 8) & 0xff0000) |\n ((word >>> 8) & 0xff00) |\n ((word >>> 24) & 0xff)\n );\n}\n/** Conditionally byte swap if on a big-endian platform */\nexport const byteSwapIfBE: (n: number) => number = isLE\n ? (n: number) => n\n : (n: number) => byteSwap(n);\n\n/** In place byte swap for Uint32Array */\nexport function byteSwap32(arr: Uint32Array): void {\n for (let i = 0; i < arr.length; i++) {\n arr[i] = byteSwap(arr[i]);\n }\n}\n\n// Array where index 0xf0 (240) is mapped to string 'f0'\nconst hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>\n i.toString(16).padStart(2, '0')\n);\n/**\n * Convert byte array to hex string.\n * @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'\n */\nexport function bytesToHex(bytes: Uint8Array): string {\n abytes(bytes);\n // pre-caching improves the speed 6x\n let hex = '';\n for (let i = 0; i < bytes.length; i++) {\n hex += hexes[bytes[i]];\n }\n return hex;\n}\n\n// We use optimized technique to convert hex string to byte array\nconst asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 } as const;\nfunction asciiToBase16(ch: number): number | undefined {\n if (ch >= asciis._0 && ch <= asciis._9) return ch - asciis._0; // '2' => 50-48\n if (ch >= asciis.A && ch <= asciis.F) return ch - (asciis.A - 10); // 'B' => 66-(65-10)\n if (ch >= asciis.a && ch <= asciis.f) return ch - (asciis.a - 10); // 'b' => 98-(97-10)\n return;\n}\n\n/**\n * Convert hex string to byte array.\n * @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])\n */\nexport function hexToBytes(hex: string): Uint8Array {\n if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);\n const hl = hex.length;\n const al = hl / 2;\n if (hl % 2) throw new Error('hex string expected, got unpadded hex of length ' + hl);\n const array = new Uint8Array(al);\n for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {\n const n1 = asciiToBase16(hex.charCodeAt(hi));\n const n2 = asciiToBase16(hex.charCodeAt(hi + 1));\n if (n1 === undefined || n2 === undefined) {\n const char = hex[hi] + hex[hi + 1];\n throw new Error('hex string expected, got non-hex character \"' + char + '\" at index ' + hi);\n }\n array[ai] = n1 * 16 + n2; // multiply first octet, e.g. 'a3' => 10*16+3 => 160 + 3 => 163\n }\n return array;\n}\n\n/**\n * There is no setImmediate in browser and setTimeout is slow.\n * Call of async fn will return Promise, which will be fullfiled only on\n * next scheduler queue processing step and this is exactly what we need.\n */\nexport const nextTick = async (): Promise => {};\n\n/** Returns control to thread each 'tick' ms to avoid blocking. */\nexport async function asyncLoop(\n iters: number,\n tick: number,\n cb: (i: number) => void\n): Promise {\n let ts = Date.now();\n for (let i = 0; i < iters; i++) {\n cb(i);\n // Date.now() is not monotonic, so in case if clock goes backwards we return return control too\n const diff = Date.now() - ts;\n if (diff >= 0 && diff < tick) continue;\n await nextTick();\n ts += diff;\n }\n}\n\n// Global symbols in both browsers and Node.js since v11\n// See https://github.com/microsoft/TypeScript/issues/31535\ndeclare const TextEncoder: any;\n\n/**\n * Convert JS string to byte array.\n * @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])\n */\nexport function utf8ToBytes(str: string): Uint8Array {\n if (typeof str !== 'string') throw new Error('utf8ToBytes expected string, got ' + typeof str);\n return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809\n}\n\n/** Accepted input of hash functions. Strings are converted to byte arrays. */\nexport type Input = Uint8Array | string;\n/**\n * Normalizes (non-hex) string or Uint8Array to Uint8Array.\n * Warning: when Uint8Array is passed, it would NOT get copied.\n * Keep in mind for future mutable operations.\n */\nexport function toBytes(data: Input): Uint8Array {\n if (typeof data === 'string') data = utf8ToBytes(data);\n abytes(data);\n return data;\n}\n\n/**\n * Copies several Uint8Arrays into one.\n */\nexport function concatBytes(...arrays: Uint8Array[]): Uint8Array {\n let sum = 0;\n for (let i = 0; i < arrays.length; i++) {\n const a = arrays[i];\n abytes(a);\n sum += a.length;\n }\n const res = new Uint8Array(sum);\n for (let i = 0, pad = 0; i < arrays.length; i++) {\n const a = arrays[i];\n res.set(a, pad);\n pad += a.length;\n }\n return res;\n}\n\n/** For runtime check if class implements interface */\nexport abstract class Hash> {\n abstract blockLen: number; // Bytes per block\n abstract outputLen: number; // Bytes in output\n abstract update(buf: Input): this;\n // Writes digest into buf\n abstract digestInto(buf: Uint8Array): void;\n abstract digest(): Uint8Array;\n /**\n * Resets internal state. Makes Hash instance unusable.\n * Reset is impossible for keyed hashes if key is consumed into state. If digest is not consumed\n * by user, they will need to manually call `destroy()` when zeroing is necessary.\n */\n abstract destroy(): void;\n /**\n * Clones hash instance. Unsafe: doesn't check whether `to` is valid. Can be used as `clone()`\n * when no options are passed.\n * Reasons to use `_cloneInto` instead of clone: 1) performance 2) reuse instance => all internal\n * buffers are overwritten => causes buffer overwrite which is used for digest in some cases.\n * There are no guarantees for clean-up because it's impossible in JS.\n */\n abstract _cloneInto(to?: T): T;\n // Safe version that clones internal state\n clone(): T {\n return this._cloneInto();\n }\n}\n\n/**\n * XOF: streaming API to read digest in chunks.\n * Same as 'squeeze' in keccak/k12 and 'seek' in blake3, but more generic name.\n * When hash used in XOF mode it is up to user to call '.destroy' afterwards, since we cannot\n * destroy state, next call can require more bytes.\n */\nexport type HashXOF> = Hash & {\n xof(bytes: number): Uint8Array; // Read 'bytes' bytes from digest stream\n xofInto(buf: Uint8Array): Uint8Array; // read buf.length bytes from digest stream into buf\n};\n\ntype EmptyObj = {};\nexport function checkOpts(\n defaults: T1,\n opts?: T2\n): T1 & T2 {\n if (opts !== undefined && {}.toString.call(opts) !== '[object Object]')\n throw new Error('Options should be object or undefined');\n const merged = Object.assign(defaults, opts);\n return merged as T1 & T2;\n}\n\n/** Hash function */\nexport type CHash = ReturnType;\n/** Hash function with output */\nexport type CHashO = ReturnType;\n/** XOF with output */\nexport type CHashXO = ReturnType;\n\n/** Wraps hash function, creating an interface on top of it */\nexport function wrapConstructor>(\n hashCons: () => Hash\n): {\n (msg: Input): Uint8Array;\n outputLen: number;\n blockLen: number;\n create(): Hash;\n} {\n const hashC = (msg: Input): Uint8Array => hashCons().update(toBytes(msg)).digest();\n const tmp = hashCons();\n hashC.outputLen = tmp.outputLen;\n hashC.blockLen = tmp.blockLen;\n hashC.create = () => hashCons();\n return hashC;\n}\n\nexport function wrapConstructorWithOpts, T extends Object>(\n hashCons: (opts?: T) => Hash\n): {\n (msg: Input, opts?: T): Uint8Array;\n outputLen: number;\n blockLen: number;\n create(opts: T): Hash;\n} {\n const hashC = (msg: Input, opts?: T): Uint8Array => hashCons(opts).update(toBytes(msg)).digest();\n const tmp = hashCons({} as T);\n hashC.outputLen = tmp.outputLen;\n hashC.blockLen = tmp.blockLen;\n hashC.create = (opts: T) => hashCons(opts);\n return hashC;\n}\n\nexport function wrapXOFConstructorWithOpts, T extends Object>(\n hashCons: (opts?: T) => HashXOF\n): {\n (msg: Input, opts?: T): Uint8Array;\n outputLen: number;\n blockLen: number;\n create(opts: T): HashXOF;\n} {\n const hashC = (msg: Input, opts?: T): Uint8Array => hashCons(opts).update(toBytes(msg)).digest();\n const tmp = hashCons({} as T);\n hashC.outputLen = tmp.outputLen;\n hashC.blockLen = tmp.blockLen;\n hashC.create = (opts: T) => hashCons(opts);\n return hashC;\n}\n\n/** Cryptographically secure PRNG. Uses internal OS-level `crypto.getRandomValues`. */\nexport function randomBytes(bytesLength = 32): Uint8Array {\n if (crypto && typeof crypto.getRandomValues === 'function') {\n return crypto.getRandomValues(new Uint8Array(bytesLength));\n }\n // Legacy Node.js compatibility\n if (crypto && typeof crypto.randomBytes === 'function') {\n return crypto.randomBytes(bytesLength);\n }\n throw new Error('crypto.getRandomValues must be defined');\n}\n","/**\n * Internal Merkle-Damgard hash utils.\n * @module\n */\nimport { aexists, aoutput } from './_assert.js';\nimport { type Input, Hash, createView, toBytes } from './utils.js';\n\n/** Polyfill for Safari 14. https://caniuse.com/mdn-javascript_builtins_dataview_setbiguint64 */\nexport function setBigUint64(\n view: DataView,\n byteOffset: number,\n value: bigint,\n isLE: boolean\n): void {\n if (typeof view.setBigUint64 === 'function') return view.setBigUint64(byteOffset, value, isLE);\n const _32n = BigInt(32);\n const _u32_max = BigInt(0xffffffff);\n const wh = Number((value >> _32n) & _u32_max);\n const wl = Number(value & _u32_max);\n const h = isLE ? 4 : 0;\n const l = isLE ? 0 : 4;\n view.setUint32(byteOffset + h, wh, isLE);\n view.setUint32(byteOffset + l, wl, isLE);\n}\n\n/** Choice: a ? b : c */\nexport function Chi(a: number, b: number, c: number): number {\n return (a & b) ^ (~a & c);\n}\n\n/** Majority function, true if any two inputs is true. */\nexport function Maj(a: number, b: number, c: number): number {\n return (a & b) ^ (a & c) ^ (b & c);\n}\n\n/**\n * Merkle-Damgard hash construction base class.\n * Could be used to create MD5, RIPEMD, SHA1, SHA2.\n */\nexport abstract class HashMD> extends Hash {\n protected abstract process(buf: DataView, offset: number): void;\n protected abstract get(): number[];\n protected abstract set(...args: number[]): void;\n abstract destroy(): void;\n protected abstract roundClean(): void;\n // For partial updates less than block size\n protected buffer: Uint8Array;\n protected view: DataView;\n protected finished = false;\n protected length = 0;\n protected pos = 0;\n protected destroyed = false;\n\n constructor(\n readonly blockLen: number,\n public outputLen: number,\n readonly padOffset: number,\n readonly isLE: boolean\n ) {\n super();\n this.buffer = new Uint8Array(blockLen);\n this.view = createView(this.buffer);\n }\n update(data: Input): this {\n aexists(this);\n const { view, buffer, blockLen } = this;\n data = toBytes(data);\n const len = data.length;\n for (let pos = 0; pos < len; ) {\n const take = Math.min(blockLen - this.pos, len - pos);\n // Fast path: we have at least one block in input, cast it to view and process\n if (take === blockLen) {\n const dataView = createView(data);\n for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos);\n continue;\n }\n buffer.set(data.subarray(pos, pos + take), this.pos);\n this.pos += take;\n pos += take;\n if (this.pos === blockLen) {\n this.process(view, 0);\n this.pos = 0;\n }\n }\n this.length += data.length;\n this.roundClean();\n return this;\n }\n digestInto(out: Uint8Array): void {\n aexists(this);\n aoutput(out, this);\n this.finished = true;\n // Padding\n // We can avoid allocation of buffer for padding completely if it\n // was previously not allocated here. But it won't change performance.\n const { buffer, view, blockLen, isLE } = this;\n let { pos } = this;\n // append the bit '1' to the message\n buffer[pos++] = 0b10000000;\n this.buffer.subarray(pos).fill(0);\n // we have less than padOffset left in buffer, so we cannot put length in\n // current block, need process it and pad again\n if (this.padOffset > blockLen - pos) {\n this.process(view, 0);\n pos = 0;\n }\n // Pad until full block byte with zeros\n for (let i = pos; i < blockLen; i++) buffer[i] = 0;\n // Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that\n // You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.\n // So we just write lowest 64 bits of that value.\n setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);\n this.process(view, 0);\n const oview = createView(out);\n const len = this.outputLen;\n // NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT\n if (len % 4) throw new Error('_sha2: outputLen should be aligned to 32bit');\n const outLen = len / 4;\n const state = this.get();\n if (outLen > state.length) throw new Error('_sha2: outputLen bigger than state');\n for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);\n }\n digest(): Uint8Array {\n const { buffer, outputLen } = this;\n this.digestInto(buffer);\n const res = buffer.slice(0, outputLen);\n this.destroy();\n return res;\n }\n _cloneInto(to?: T): T {\n to ||= new (this.constructor as any)() as T;\n to.set(...this.get());\n const { blockLen, buffer, length, finished, destroyed, pos } = this;\n to.length = length;\n to.pos = pos;\n to.finished = finished;\n to.destroyed = destroyed;\n if (length % blockLen) to.buffer.set(buffer);\n return to;\n }\n}\n","/**\n * Internal helpers for u64. BigUint64Array is too slow as per 2025, so we implement it using Uint32Array.\n * @todo re-check https://issues.chromium.org/issues/42212588\n * @module\n */\nconst U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);\nconst _32n = /* @__PURE__ */ BigInt(32);\n\nfunction fromBig(\n n: bigint,\n le = false\n): {\n h: number;\n l: number;\n} {\n if (le) return { h: Number(n & U32_MASK64), l: Number((n >> _32n) & U32_MASK64) };\n return { h: Number((n >> _32n) & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };\n}\n\nfunction split(lst: bigint[], le = false): Uint32Array[] {\n let Ah = new Uint32Array(lst.length);\n let Al = new Uint32Array(lst.length);\n for (let i = 0; i < lst.length; i++) {\n const { h, l } = fromBig(lst[i], le);\n [Ah[i], Al[i]] = [h, l];\n }\n return [Ah, Al];\n}\n\nconst toBig = (h: number, l: number): bigint => (BigInt(h >>> 0) << _32n) | BigInt(l >>> 0);\n// for Shift in [0, 32)\nconst shrSH = (h: number, _l: number, s: number): number => h >>> s;\nconst shrSL = (h: number, l: number, s: number): number => (h << (32 - s)) | (l >>> s);\n// Right rotate for Shift in [1, 32)\nconst rotrSH = (h: number, l: number, s: number): number => (h >>> s) | (l << (32 - s));\nconst rotrSL = (h: number, l: number, s: number): number => (h << (32 - s)) | (l >>> s);\n// Right rotate for Shift in (32, 64), NOTE: 32 is special case.\nconst rotrBH = (h: number, l: number, s: number): number => (h << (64 - s)) | (l >>> (s - 32));\nconst rotrBL = (h: number, l: number, s: number): number => (h >>> (s - 32)) | (l << (64 - s));\n// Right rotate for shift===32 (just swaps l&h)\nconst rotr32H = (_h: number, l: number): number => l;\nconst rotr32L = (h: number, _l: number): number => h;\n// Left rotate for Shift in [1, 32)\nconst rotlSH = (h: number, l: number, s: number): number => (h << s) | (l >>> (32 - s));\nconst rotlSL = (h: number, l: number, s: number): number => (l << s) | (h >>> (32 - s));\n// Left rotate for Shift in (32, 64), NOTE: 32 is special case.\nconst rotlBH = (h: number, l: number, s: number): number => (l << (s - 32)) | (h >>> (64 - s));\nconst rotlBL = (h: number, l: number, s: number): number => (h << (s - 32)) | (l >>> (64 - s));\n\n// JS uses 32-bit signed integers for bitwise operations which means we cannot\n// simple take carry out of low bit sum by shift, we need to use division.\nfunction add(\n Ah: number,\n Al: number,\n Bh: number,\n Bl: number\n): {\n h: number;\n l: number;\n} {\n const l = (Al >>> 0) + (Bl >>> 0);\n return { h: (Ah + Bh + ((l / 2 ** 32) | 0)) | 0, l: l | 0 };\n}\n// Addition with more than 2 elements\nconst add3L = (Al: number, Bl: number, Cl: number): number => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0);\nconst add3H = (low: number, Ah: number, Bh: number, Ch: number): number =>\n (Ah + Bh + Ch + ((low / 2 ** 32) | 0)) | 0;\nconst add4L = (Al: number, Bl: number, Cl: number, Dl: number): number =>\n (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0);\nconst add4H = (low: number, Ah: number, Bh: number, Ch: number, Dh: number): number =>\n (Ah + Bh + Ch + Dh + ((low / 2 ** 32) | 0)) | 0;\nconst add5L = (Al: number, Bl: number, Cl: number, Dl: number, El: number): number =>\n (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);\nconst add5H = (low: number, Ah: number, Bh: number, Ch: number, Dh: number, Eh: number): number =>\n (Ah + Bh + Ch + Dh + Eh + ((low / 2 ** 32) | 0)) | 0;\n\n// prettier-ignore\nexport {\n fromBig, split, toBig,\n shrSH, shrSL,\n rotrSH, rotrSL, rotrBH, rotrBL,\n rotr32H, rotr32L,\n rotlSH, rotlSL, rotlBH, rotlBL,\n add, add3L, add3H, add4L, add4H, add5H, add5L,\n};\n// prettier-ignore\nconst u64: { fromBig: typeof fromBig; split: typeof split; toBig: (h: number, l: number) => bigint; shrSH: (h: number, _l: number, s: number) => number; shrSL: (h: number, l: number, s: number) => number; rotrSH: (h: number, l: number, s: number) => number; rotrSL: (h: number, l: number, s: number) => number; rotrBH: (h: number, l: number, s: number) => number; rotrBL: (h: number, l: number, s: number) => number; rotr32H: (_h: number, l: number) => number; rotr32L: (h: number, _l: number) => number; rotlSH: (h: number, l: number, s: number) => number; rotlSL: (h: number, l: number, s: number) => number; rotlBH: (h: number, l: number, s: number) => number; rotlBL: (h: number, l: number, s: number) => number; add: typeof add; add3L: (Al: number, Bl: number, Cl: number) => number; add3H: (low: number, Ah: number, Bh: number, Ch: number) => number; add4L: (Al: number, Bl: number, Cl: number, Dl: number) => number; add4H: (low: number, Ah: number, Bh: number, Ch: number, Dh: number) => number; add5H: (low: number, Ah: number, Bh: number, Ch: number, Dh: number, Eh: number) => number; add5L: (Al: number, Bl: number, Cl: number, Dl: number, El: number) => number; } = {\n fromBig, split, toBig,\n shrSH, shrSL,\n rotrSH, rotrSL, rotrBH, rotrBL,\n rotr32H, rotr32L,\n rotlSH, rotlSL, rotlBH, rotlBL,\n add, add3L, add3H, add4L, add4H, add5H, add5L,\n};\nexport default u64;\n","/**\n * SHA2-512 a.k.a. sha512 and sha384. It is slower than sha256 in js because u64 operations are slow.\n *\n * Check out [RFC 4634](https://datatracker.ietf.org/doc/html/rfc4634) and\n * [the paper on truncated SHA512/256](https://eprint.iacr.org/2010/548.pdf).\n * @module\n */\nimport { HashMD } from './_md.js';\nimport u64 from './_u64.js';\nimport { type CHash, wrapConstructor } from './utils.js';\n\n// Round contants (first 32 bits of the fractional parts of the cube roots of the first 80 primes 2..409):\n// prettier-ignore\nconst [SHA512_Kh, SHA512_Kl] = /* @__PURE__ */ (() => u64.split([\n '0x428a2f98d728ae22', '0x7137449123ef65cd', '0xb5c0fbcfec4d3b2f', '0xe9b5dba58189dbbc',\n '0x3956c25bf348b538', '0x59f111f1b605d019', '0x923f82a4af194f9b', '0xab1c5ed5da6d8118',\n '0xd807aa98a3030242', '0x12835b0145706fbe', '0x243185be4ee4b28c', '0x550c7dc3d5ffb4e2',\n '0x72be5d74f27b896f', '0x80deb1fe3b1696b1', '0x9bdc06a725c71235', '0xc19bf174cf692694',\n '0xe49b69c19ef14ad2', '0xefbe4786384f25e3', '0x0fc19dc68b8cd5b5', '0x240ca1cc77ac9c65',\n '0x2de92c6f592b0275', '0x4a7484aa6ea6e483', '0x5cb0a9dcbd41fbd4', '0x76f988da831153b5',\n '0x983e5152ee66dfab', '0xa831c66d2db43210', '0xb00327c898fb213f', '0xbf597fc7beef0ee4',\n '0xc6e00bf33da88fc2', '0xd5a79147930aa725', '0x06ca6351e003826f', '0x142929670a0e6e70',\n '0x27b70a8546d22ffc', '0x2e1b21385c26c926', '0x4d2c6dfc5ac42aed', '0x53380d139d95b3df',\n '0x650a73548baf63de', '0x766a0abb3c77b2a8', '0x81c2c92e47edaee6', '0x92722c851482353b',\n '0xa2bfe8a14cf10364', '0xa81a664bbc423001', '0xc24b8b70d0f89791', '0xc76c51a30654be30',\n '0xd192e819d6ef5218', '0xd69906245565a910', '0xf40e35855771202a', '0x106aa07032bbd1b8',\n '0x19a4c116b8d2d0c8', '0x1e376c085141ab53', '0x2748774cdf8eeb99', '0x34b0bcb5e19b48a8',\n '0x391c0cb3c5c95a63', '0x4ed8aa4ae3418acb', '0x5b9cca4f7763e373', '0x682e6ff3d6b2b8a3',\n '0x748f82ee5defb2fc', '0x78a5636f43172f60', '0x84c87814a1f0ab72', '0x8cc702081a6439ec',\n '0x90befffa23631e28', '0xa4506cebde82bde9', '0xbef9a3f7b2c67915', '0xc67178f2e372532b',\n '0xca273eceea26619c', '0xd186b8c721c0c207', '0xeada7dd6cde0eb1e', '0xf57d4f7fee6ed178',\n '0x06f067aa72176fba', '0x0a637dc5a2c898a6', '0x113f9804bef90dae', '0x1b710b35131c471b',\n '0x28db77f523047d84', '0x32caab7b40c72493', '0x3c9ebe0a15c9bebc', '0x431d67c49c100d4c',\n '0x4cc5d4becb3e42b6', '0x597f299cfc657e2a', '0x5fcb6fab3ad6faec', '0x6c44198c4a475817'\n].map(n => BigInt(n))))();\n\n// Temporary buffer, not used to store anything between runs\nconst SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);\nconst SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);\nexport class SHA512 extends HashMD {\n // We cannot use array here since array allows indexing by variable which means optimizer/compiler cannot use registers.\n // Also looks cleaner and easier to verify with spec.\n // Initial state (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):\n // h -- high 32 bits, l -- low 32 bits\n protected Ah: number = 0x6a09e667 | 0;\n protected Al: number = 0xf3bcc908 | 0;\n protected Bh: number = 0xbb67ae85 | 0;\n protected Bl: number = 0x84caa73b | 0;\n protected Ch: number = 0x3c6ef372 | 0;\n protected Cl: number = 0xfe94f82b | 0;\n protected Dh: number = 0xa54ff53a | 0;\n protected Dl: number = 0x5f1d36f1 | 0;\n protected Eh: number = 0x510e527f | 0;\n protected El: number = 0xade682d1 | 0;\n protected Fh: number = 0x9b05688c | 0;\n protected Fl: number = 0x2b3e6c1f | 0;\n protected Gh: number = 0x1f83d9ab | 0;\n protected Gl: number = 0xfb41bd6b | 0;\n protected Hh: number = 0x5be0cd19 | 0;\n protected Hl: number = 0x137e2179 | 0;\n\n constructor() {\n super(128, 64, 16, false);\n }\n // prettier-ignore\n protected get(): [\n number, number, number, number, number, number, number, number,\n number, number, number, number, number, number, number, number\n ] {\n const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;\n return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl];\n }\n // prettier-ignore\n protected set(\n Ah: number, Al: number, Bh: number, Bl: number, Ch: number, Cl: number, Dh: number, Dl: number,\n Eh: number, El: number, Fh: number, Fl: number, Gh: number, Gl: number, Hh: number, Hl: number\n ): void {\n this.Ah = Ah | 0;\n this.Al = Al | 0;\n this.Bh = Bh | 0;\n this.Bl = Bl | 0;\n this.Ch = Ch | 0;\n this.Cl = Cl | 0;\n this.Dh = Dh | 0;\n this.Dl = Dl | 0;\n this.Eh = Eh | 0;\n this.El = El | 0;\n this.Fh = Fh | 0;\n this.Fl = Fl | 0;\n this.Gh = Gh | 0;\n this.Gl = Gl | 0;\n this.Hh = Hh | 0;\n this.Hl = Hl | 0;\n }\n protected process(view: DataView, offset: number): void {\n // Extend the first 16 words into the remaining 64 words w[16..79] of the message schedule array\n for (let i = 0; i < 16; i++, offset += 4) {\n SHA512_W_H[i] = view.getUint32(offset);\n SHA512_W_L[i] = view.getUint32((offset += 4));\n }\n for (let i = 16; i < 80; i++) {\n // s0 := (w[i-15] rightrotate 1) xor (w[i-15] rightrotate 8) xor (w[i-15] rightshift 7)\n const W15h = SHA512_W_H[i - 15] | 0;\n const W15l = SHA512_W_L[i - 15] | 0;\n const s0h = u64.rotrSH(W15h, W15l, 1) ^ u64.rotrSH(W15h, W15l, 8) ^ u64.shrSH(W15h, W15l, 7);\n const s0l = u64.rotrSL(W15h, W15l, 1) ^ u64.rotrSL(W15h, W15l, 8) ^ u64.shrSL(W15h, W15l, 7);\n // s1 := (w[i-2] rightrotate 19) xor (w[i-2] rightrotate 61) xor (w[i-2] rightshift 6)\n const W2h = SHA512_W_H[i - 2] | 0;\n const W2l = SHA512_W_L[i - 2] | 0;\n const s1h = u64.rotrSH(W2h, W2l, 19) ^ u64.rotrBH(W2h, W2l, 61) ^ u64.shrSH(W2h, W2l, 6);\n const s1l = u64.rotrSL(W2h, W2l, 19) ^ u64.rotrBL(W2h, W2l, 61) ^ u64.shrSL(W2h, W2l, 6);\n // SHA256_W[i] = s0 + s1 + SHA256_W[i - 7] + SHA256_W[i - 16];\n const SUMl = u64.add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]);\n const SUMh = u64.add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]);\n SHA512_W_H[i] = SUMh | 0;\n SHA512_W_L[i] = SUMl | 0;\n }\n let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;\n // Compression function main loop, 80 rounds\n for (let i = 0; i < 80; i++) {\n // S1 := (e rightrotate 14) xor (e rightrotate 18) xor (e rightrotate 41)\n const sigma1h = u64.rotrSH(Eh, El, 14) ^ u64.rotrSH(Eh, El, 18) ^ u64.rotrBH(Eh, El, 41);\n const sigma1l = u64.rotrSL(Eh, El, 14) ^ u64.rotrSL(Eh, El, 18) ^ u64.rotrBL(Eh, El, 41);\n //const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;\n const CHIh = (Eh & Fh) ^ (~Eh & Gh);\n const CHIl = (El & Fl) ^ (~El & Gl);\n // T1 = H + sigma1 + Chi(E, F, G) + SHA512_K[i] + SHA512_W[i]\n // prettier-ignore\n const T1ll = u64.add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);\n const T1h = u64.add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);\n const T1l = T1ll | 0;\n // S0 := (a rightrotate 28) xor (a rightrotate 34) xor (a rightrotate 39)\n const sigma0h = u64.rotrSH(Ah, Al, 28) ^ u64.rotrBH(Ah, Al, 34) ^ u64.rotrBH(Ah, Al, 39);\n const sigma0l = u64.rotrSL(Ah, Al, 28) ^ u64.rotrBL(Ah, Al, 34) ^ u64.rotrBL(Ah, Al, 39);\n const MAJh = (Ah & Bh) ^ (Ah & Ch) ^ (Bh & Ch);\n const MAJl = (Al & Bl) ^ (Al & Cl) ^ (Bl & Cl);\n Hh = Gh | 0;\n Hl = Gl | 0;\n Gh = Fh | 0;\n Gl = Fl | 0;\n Fh = Eh | 0;\n Fl = El | 0;\n ({ h: Eh, l: El } = u64.add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));\n Dh = Ch | 0;\n Dl = Cl | 0;\n Ch = Bh | 0;\n Cl = Bl | 0;\n Bh = Ah | 0;\n Bl = Al | 0;\n const All = u64.add3L(T1l, sigma0l, MAJl);\n Ah = u64.add3H(All, T1h, sigma0h, MAJh);\n Al = All | 0;\n }\n // Add the compressed chunk to the current hash value\n ({ h: Ah, l: Al } = u64.add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));\n ({ h: Bh, l: Bl } = u64.add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));\n ({ h: Ch, l: Cl } = u64.add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));\n ({ h: Dh, l: Dl } = u64.add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));\n ({ h: Eh, l: El } = u64.add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));\n ({ h: Fh, l: Fl } = u64.add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));\n ({ h: Gh, l: Gl } = u64.add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));\n ({ h: Hh, l: Hl } = u64.add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));\n this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);\n }\n protected roundClean(): void {\n SHA512_W_H.fill(0);\n SHA512_W_L.fill(0);\n }\n destroy(): void {\n this.buffer.fill(0);\n this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n }\n}\n\nexport class SHA512_224 extends SHA512 {\n // h -- high 32 bits, l -- low 32 bits\n protected Ah: number = 0x8c3d37c8 | 0;\n protected Al: number = 0x19544da2 | 0;\n protected Bh: number = 0x73e19966 | 0;\n protected Bl: number = 0x89dcd4d6 | 0;\n protected Ch: number = 0x1dfab7ae | 0;\n protected Cl: number = 0x32ff9c82 | 0;\n protected Dh: number = 0x679dd514 | 0;\n protected Dl: number = 0x582f9fcf | 0;\n protected Eh: number = 0x0f6d2b69 | 0;\n protected El: number = 0x7bd44da8 | 0;\n protected Fh: number = 0x77e36f73 | 0;\n protected Fl: number = 0x04c48942 | 0;\n protected Gh: number = 0x3f9d85a8 | 0;\n protected Gl: number = 0x6a1d36c8 | 0;\n protected Hh: number = 0x1112e6ad | 0;\n protected Hl: number = 0x91d692a1 | 0;\n\n constructor() {\n super();\n this.outputLen = 28;\n }\n}\n\nexport class SHA512_256 extends SHA512 {\n // h -- high 32 bits, l -- low 32 bits\n protected Ah: number = 0x22312194 | 0;\n protected Al: number = 0xfc2bf72c | 0;\n protected Bh: number = 0x9f555fa3 | 0;\n protected Bl: number = 0xc84c64c2 | 0;\n protected Ch: number = 0x2393b86b | 0;\n protected Cl: number = 0x6f53b151 | 0;\n protected Dh: number = 0x96387719 | 0;\n protected Dl: number = 0x5940eabd | 0;\n protected Eh: number = 0x96283ee2 | 0;\n protected El: number = 0xa88effe3 | 0;\n protected Fh: number = 0xbe5e1e25 | 0;\n protected Fl: number = 0x53863992 | 0;\n protected Gh: number = 0x2b0199fc | 0;\n protected Gl: number = 0x2c85b8aa | 0;\n protected Hh: number = 0x0eb72ddc | 0;\n protected Hl: number = 0x81c52ca2 | 0;\n\n constructor() {\n super();\n this.outputLen = 32;\n }\n}\n\nexport class SHA384 extends SHA512 {\n // h -- high 32 bits, l -- low 32 bits\n protected Ah: number = 0xcbbb9d5d | 0;\n protected Al: number = 0xc1059ed8 | 0;\n protected Bh: number = 0x629a292a | 0;\n protected Bl: number = 0x367cd507 | 0;\n protected Ch: number = 0x9159015a | 0;\n protected Cl: number = 0x3070dd17 | 0;\n protected Dh: number = 0x152fecd8 | 0;\n protected Dl: number = 0xf70e5939 | 0;\n protected Eh: number = 0x67332667 | 0;\n protected El: number = 0xffc00b31 | 0;\n protected Fh: number = 0x8eb44a87 | 0;\n protected Fl: number = 0x68581511 | 0;\n protected Gh: number = 0xdb0c2e0d | 0;\n protected Gl: number = 0x64f98fa7 | 0;\n protected Hh: number = 0x47b5481d | 0;\n protected Hl: number = 0xbefa4fa4 | 0;\n\n constructor() {\n super();\n this.outputLen = 48;\n }\n}\n\n/** SHA2-512 hash function. */\nexport const sha512: CHash = /* @__PURE__ */ wrapConstructor(() => new SHA512());\n/** SHA2-512/224 \"truncated\" hash function, with improved resistance to length extension attacks. */\nexport const sha512_224: CHash = /* @__PURE__ */ wrapConstructor(() => new SHA512_224());\n/** SHA2-512/256 \"truncated\" hash function, with improved resistance to length extension attacks. */\nexport const sha512_256: CHash = /* @__PURE__ */ wrapConstructor(() => new SHA512_256());\n/** SHA2-384 hash function. */\nexport const sha384: CHash = /* @__PURE__ */ wrapConstructor(() => new SHA384());\n","/**\n * Hex, bytes and number utilities.\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\n\n// 100 lines of code in the file are duplicated from noble-hashes (utils).\n// This is OK: `abstract` directory does not use noble-hashes.\n// User may opt-in into using different hashing library. This way, noble-hashes\n// won't be included into their bundle.\nconst _0n = /* @__PURE__ */ BigInt(0);\nconst _1n = /* @__PURE__ */ BigInt(1);\nconst _2n = /* @__PURE__ */ BigInt(2);\nexport type Hex = Uint8Array | string; // hex strings are accepted for simplicity\nexport type PrivKey = Hex | bigint; // bigints are accepted to ease learning curve\nexport type CHash = {\n (message: Uint8Array | string): Uint8Array;\n blockLen: number;\n outputLen: number;\n create(opts?: { dkLen?: number }): any; // For shake\n};\nexport type FHash = (message: Uint8Array | string) => Uint8Array;\n\nexport function isBytes(a: unknown): a is Uint8Array {\n return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');\n}\n\nexport function abytes(item: unknown): void {\n if (!isBytes(item)) throw new Error('Uint8Array expected');\n}\n\nexport function abool(title: string, value: boolean): void {\n if (typeof value !== 'boolean') throw new Error(title + ' boolean expected, got ' + value);\n}\n\n// Array where index 0xf0 (240) is mapped to string 'f0'\nconst hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>\n i.toString(16).padStart(2, '0')\n);\n/**\n * @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'\n */\nexport function bytesToHex(bytes: Uint8Array): string {\n abytes(bytes);\n // pre-caching improves the speed 6x\n let hex = '';\n for (let i = 0; i < bytes.length; i++) {\n hex += hexes[bytes[i]];\n }\n return hex;\n}\n\nexport function numberToHexUnpadded(num: number | bigint): string {\n const hex = num.toString(16);\n return hex.length & 1 ? '0' + hex : hex;\n}\n\nexport function hexToNumber(hex: string): bigint {\n if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);\n return hex === '' ? _0n : BigInt('0x' + hex); // Big Endian\n}\n\n// We use optimized technique to convert hex string to byte array\nconst asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 } as const;\nfunction asciiToBase16(ch: number): number | undefined {\n if (ch >= asciis._0 && ch <= asciis._9) return ch - asciis._0; // '2' => 50-48\n if (ch >= asciis.A && ch <= asciis.F) return ch - (asciis.A - 10); // 'B' => 66-(65-10)\n if (ch >= asciis.a && ch <= asciis.f) return ch - (asciis.a - 10); // 'b' => 98-(97-10)\n return;\n}\n\n/**\n * @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])\n */\nexport function hexToBytes(hex: string): Uint8Array {\n if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);\n const hl = hex.length;\n const al = hl / 2;\n if (hl % 2) throw new Error('hex string expected, got unpadded hex of length ' + hl);\n const array = new Uint8Array(al);\n for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {\n const n1 = asciiToBase16(hex.charCodeAt(hi));\n const n2 = asciiToBase16(hex.charCodeAt(hi + 1));\n if (n1 === undefined || n2 === undefined) {\n const char = hex[hi] + hex[hi + 1];\n throw new Error('hex string expected, got non-hex character \"' + char + '\" at index ' + hi);\n }\n array[ai] = n1 * 16 + n2; // multiply first octet, e.g. 'a3' => 10*16+3 => 160 + 3 => 163\n }\n return array;\n}\n\n// BE: Big Endian, LE: Little Endian\nexport function bytesToNumberBE(bytes: Uint8Array): bigint {\n return hexToNumber(bytesToHex(bytes));\n}\nexport function bytesToNumberLE(bytes: Uint8Array): bigint {\n abytes(bytes);\n return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));\n}\n\nexport function numberToBytesBE(n: number | bigint, len: number): Uint8Array {\n return hexToBytes(n.toString(16).padStart(len * 2, '0'));\n}\nexport function numberToBytesLE(n: number | bigint, len: number): Uint8Array {\n return numberToBytesBE(n, len).reverse();\n}\n// Unpadded, rarely used\nexport function numberToVarBytesBE(n: number | bigint): Uint8Array {\n return hexToBytes(numberToHexUnpadded(n));\n}\n\n/**\n * Takes hex string or Uint8Array, converts to Uint8Array.\n * Validates output length.\n * Will throw error for other types.\n * @param title descriptive title for an error e.g. 'private key'\n * @param hex hex string or Uint8Array\n * @param expectedLength optional, will compare to result array's length\n * @returns\n */\nexport function ensureBytes(title: string, hex: Hex, expectedLength?: number): Uint8Array {\n let res: Uint8Array;\n if (typeof hex === 'string') {\n try {\n res = hexToBytes(hex);\n } catch (e) {\n throw new Error(title + ' must be hex string or Uint8Array, cause: ' + e);\n }\n } else if (isBytes(hex)) {\n // Uint8Array.from() instead of hash.slice() because node.js Buffer\n // is instance of Uint8Array, and its slice() creates **mutable** copy\n res = Uint8Array.from(hex);\n } else {\n throw new Error(title + ' must be hex string or Uint8Array');\n }\n const len = res.length;\n if (typeof expectedLength === 'number' && len !== expectedLength)\n throw new Error(title + ' of length ' + expectedLength + ' expected, got ' + len);\n return res;\n}\n\n/**\n * Copies several Uint8Arrays into one.\n */\nexport function concatBytes(...arrays: Uint8Array[]): Uint8Array {\n let sum = 0;\n for (let i = 0; i < arrays.length; i++) {\n const a = arrays[i];\n abytes(a);\n sum += a.length;\n }\n const res = new Uint8Array(sum);\n for (let i = 0, pad = 0; i < arrays.length; i++) {\n const a = arrays[i];\n res.set(a, pad);\n pad += a.length;\n }\n return res;\n}\n\n// Compares 2 u8a-s in kinda constant time\nexport function equalBytes(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];\n return diff === 0;\n}\n\n// Global symbols in both browsers and Node.js since v11\n// See https://github.com/microsoft/TypeScript/issues/31535\ndeclare const TextEncoder: any;\n\n/**\n * @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])\n */\nexport function utf8ToBytes(str: string): Uint8Array {\n if (typeof str !== 'string') throw new Error('string expected');\n return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809\n}\n\n// Is positive bigint\nconst isPosBig = (n: bigint) => typeof n === 'bigint' && _0n <= n;\n\nexport function inRange(n: bigint, min: bigint, max: bigint): boolean {\n return isPosBig(n) && isPosBig(min) && isPosBig(max) && min <= n && n < max;\n}\n\n/**\n * Asserts min <= n < max. NOTE: It's < max and not <= max.\n * @example\n * aInRange('x', x, 1n, 256n); // would assume x is in (1n..255n)\n */\nexport function aInRange(title: string, n: bigint, min: bigint, max: bigint): void {\n // Why min <= n < max and not a (min < n < max) OR b (min <= n <= max)?\n // consider P=256n, min=0n, max=P\n // - a for min=0 would require -1: `inRange('x', x, -1n, P)`\n // - b would commonly require subtraction: `inRange('x', x, 0n, P - 1n)`\n // - our way is the cleanest: `inRange('x', x, 0n, P)\n if (!inRange(n, min, max))\n throw new Error('expected valid ' + title + ': ' + min + ' <= n < ' + max + ', got ' + n);\n}\n\n// Bit operations\n\n/**\n * Calculates amount of bits in a bigint.\n * Same as `n.toString(2).length`\n */\nexport function bitLen(n: bigint): number {\n let len;\n for (len = 0; n > _0n; n >>= _1n, len += 1);\n return len;\n}\n\n/**\n * Gets single bit at position.\n * NOTE: first bit position is 0 (same as arrays)\n * Same as `!!+Array.from(n.toString(2)).reverse()[pos]`\n */\nexport function bitGet(n: bigint, pos: number): bigint {\n return (n >> BigInt(pos)) & _1n;\n}\n\n/**\n * Sets single bit at position.\n */\nexport function bitSet(n: bigint, pos: number, value: boolean): bigint {\n return n | ((value ? _1n : _0n) << BigInt(pos));\n}\n\n/**\n * Calculate mask for N bits. Not using ** operator with bigints because of old engines.\n * Same as BigInt(`0b${Array(i).fill('1').join('')}`)\n */\nexport const bitMask = (n: number): bigint => (_2n << BigInt(n - 1)) - _1n;\n\n// DRBG\n\nconst u8n = (data?: any) => new Uint8Array(data); // creates Uint8Array\nconst u8fr = (arr: any) => Uint8Array.from(arr); // another shortcut\ntype Pred = (v: Uint8Array) => T | undefined;\n/**\n * Minimal HMAC-DRBG from NIST 800-90 for RFC6979 sigs.\n * @returns function that will call DRBG until 2nd arg returns something meaningful\n * @example\n * const drbg = createHmacDRBG(32, 32, hmac);\n * drbg(seed, bytesToKey); // bytesToKey must return Key or undefined\n */\nexport function createHmacDrbg(\n hashLen: number,\n qByteLen: number,\n hmacFn: (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array\n): (seed: Uint8Array, predicate: Pred) => T {\n if (typeof hashLen !== 'number' || hashLen < 2) throw new Error('hashLen must be a number');\n if (typeof qByteLen !== 'number' || qByteLen < 2) throw new Error('qByteLen must be a number');\n if (typeof hmacFn !== 'function') throw new Error('hmacFn must be a function');\n // Step B, Step C: set hashLen to 8*ceil(hlen/8)\n let v = u8n(hashLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.\n let k = u8n(hashLen); // Steps B and C of RFC6979 3.2: set hashLen, in our case always same\n let i = 0; // Iterations counter, will throw when over 1000\n const reset = () => {\n v.fill(1);\n k.fill(0);\n i = 0;\n };\n const h = (...b: Uint8Array[]) => hmacFn(k, v, ...b); // hmac(k)(v, ...values)\n const reseed = (seed = u8n()) => {\n // HMAC-DRBG reseed() function. Steps D-G\n k = h(u8fr([0x00]), seed); // k = hmac(k || v || 0x00 || seed)\n v = h(); // v = hmac(k || v)\n if (seed.length === 0) return;\n k = h(u8fr([0x01]), seed); // k = hmac(k || v || 0x01 || seed)\n v = h(); // v = hmac(k || v)\n };\n const gen = () => {\n // HMAC-DRBG generate() function\n if (i++ >= 1000) throw new Error('drbg: tried 1000 values');\n let len = 0;\n const out: Uint8Array[] = [];\n while (len < qByteLen) {\n v = h();\n const sl = v.slice();\n out.push(sl);\n len += v.length;\n }\n return concatBytes(...out);\n };\n const genUntil = (seed: Uint8Array, pred: Pred): T => {\n reset();\n reseed(seed); // Steps D-G\n let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1]\n while (!(res = pred(gen()))) reseed();\n reset();\n return res;\n };\n return genUntil;\n}\n\n// Validating curves and fields\n\nconst validatorFns = {\n bigint: (val: any): boolean => typeof val === 'bigint',\n function: (val: any): boolean => typeof val === 'function',\n boolean: (val: any): boolean => typeof val === 'boolean',\n string: (val: any): boolean => typeof val === 'string',\n stringOrUint8Array: (val: any): boolean => typeof val === 'string' || isBytes(val),\n isSafeInteger: (val: any): boolean => Number.isSafeInteger(val),\n array: (val: any): boolean => Array.isArray(val),\n field: (val: any, object: any): any => (object as any).Fp.isValid(val),\n hash: (val: any): boolean => typeof val === 'function' && Number.isSafeInteger(val.outputLen),\n} as const;\ntype Validator = keyof typeof validatorFns;\ntype ValMap> = { [K in keyof T]?: Validator };\n// type Record = { [P in K]: T; }\n\nexport function validateObject>(\n object: T,\n validators: ValMap,\n optValidators: ValMap = {}\n): T {\n const checkField = (fieldName: keyof T, type: Validator, isOptional: boolean) => {\n const checkVal = validatorFns[type];\n if (typeof checkVal !== 'function') throw new Error('invalid validator function');\n\n const val = object[fieldName as keyof typeof object];\n if (isOptional && val === undefined) return;\n if (!checkVal(val, object)) {\n throw new Error(\n 'param ' + String(fieldName) + ' is invalid. Expected ' + type + ', got ' + val\n );\n }\n };\n for (const [fieldName, type] of Object.entries(validators)) checkField(fieldName, type!, false);\n for (const [fieldName, type] of Object.entries(optValidators)) checkField(fieldName, type!, true);\n return object;\n}\n// validate type tests\n// const o: { a: number; b: number; c: number } = { a: 1, b: 5, c: 6 };\n// const z0 = validateObject(o, { a: 'isSafeInteger' }, { c: 'bigint' }); // Ok!\n// // Should fail type-check\n// const z1 = validateObject(o, { a: 'tmp' }, { c: 'zz' });\n// const z2 = validateObject(o, { a: 'isSafeInteger' }, { c: 'zz' });\n// const z3 = validateObject(o, { test: 'boolean', z: 'bug' });\n// const z4 = validateObject(o, { a: 'boolean', z: 'bug' });\n\n/**\n * throws not implemented error\n */\nexport const notImplemented = (): never => {\n throw new Error('not implemented');\n};\n\n/**\n * Memoizes (caches) computation result.\n * Uses WeakMap: the value is going auto-cleaned by GC after last reference is removed.\n */\nexport function memoized(\n fn: (arg: T, ...args: O) => R\n): (arg: T, ...args: O) => R {\n const map = new WeakMap();\n return (arg: T, ...args: O): R => {\n const val = map.get(arg);\n if (val !== undefined) return val;\n const computed = fn(arg, ...args);\n map.set(arg, computed);\n return computed;\n };\n}\n","/**\n * Utils for modular division and finite fields.\n * A finite field over 11 is integer number operations `mod 11`.\n * There is no division: it is replaced by modular multiplicative inverse.\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport {\n bitMask,\n bytesToNumberBE,\n bytesToNumberLE,\n ensureBytes,\n numberToBytesBE,\n numberToBytesLE,\n validateObject,\n} from './utils.js';\n\n// prettier-ignore\nconst _0n = BigInt(0), _1n = BigInt(1), _2n = /* @__PURE__ */ BigInt(2), _3n = /* @__PURE__ */ BigInt(3);\n// prettier-ignore\nconst _4n = /* @__PURE__ */ BigInt(4), _5n = /* @__PURE__ */ BigInt(5), _8n = /* @__PURE__ */ BigInt(8);\n// prettier-ignore\nconst _9n =/* @__PURE__ */ BigInt(9), _16n = /* @__PURE__ */ BigInt(16);\n\n// Calculates a modulo b\nexport function mod(a: bigint, b: bigint): bigint {\n const result = a % b;\n return result >= _0n ? result : b + result;\n}\n/**\n * Efficiently raise num to power and do modular division.\n * Unsafe in some contexts: uses ladder, so can expose bigint bits.\n * @todo use field version && remove\n * @example\n * pow(2n, 6n, 11n) // 64n % 11n == 9n\n */\nexport function pow(num: bigint, power: bigint, modulo: bigint): bigint {\n if (power < _0n) throw new Error('invalid exponent, negatives unsupported');\n if (modulo <= _0n) throw new Error('invalid modulus');\n if (modulo === _1n) return _0n;\n let res = _1n;\n while (power > _0n) {\n if (power & _1n) res = (res * num) % modulo;\n num = (num * num) % modulo;\n power >>= _1n;\n }\n return res;\n}\n\n/** Does `x^(2^power)` mod p. `pow2(30, 4)` == `30^(2^4)` */\nexport function pow2(x: bigint, power: bigint, modulo: bigint): bigint {\n let res = x;\n while (power-- > _0n) {\n res *= res;\n res %= modulo;\n }\n return res;\n}\n\n/**\n * Inverses number over modulo.\n * Implemented using [Euclidean GCD](https://brilliant.org/wiki/extended-euclidean-algorithm/).\n */\nexport function invert(number: bigint, modulo: bigint): bigint {\n if (number === _0n) throw new Error('invert: expected non-zero number');\n if (modulo <= _0n) throw new Error('invert: expected positive modulus, got ' + modulo);\n // Fermat's little theorem \"CT-like\" version inv(n) = n^(m-2) mod m is 30x slower.\n let a = mod(number, modulo);\n let b = modulo;\n // prettier-ignore\n let x = _0n, y = _1n, u = _1n, v = _0n;\n while (a !== _0n) {\n // JIT applies optimization if those two lines follow each other\n const q = b / a;\n const r = b % a;\n const m = x - u * q;\n const n = y - v * q;\n // prettier-ignore\n b = a, a = r, x = u, y = v, u = m, v = n;\n }\n const gcd = b;\n if (gcd !== _1n) throw new Error('invert: does not exist');\n return mod(x, modulo);\n}\n\n/**\n * Tonelli-Shanks square root search algorithm.\n * 1. https://eprint.iacr.org/2012/685.pdf (page 12)\n * 2. Square Roots from 1; 24, 51, 10 to Dan Shanks\n * Will start an infinite loop if field order P is not prime.\n * @param P field order\n * @returns function that takes field Fp (created from P) and number n\n */\nexport function tonelliShanks(P: bigint): (Fp: IField, n: T) => T {\n // Legendre constant: used to calculate Legendre symbol (a | p),\n // which denotes the value of a^((p-1)/2) (mod p).\n // (a | p) ≡ 1 if a is a square (mod p)\n // (a | p) ≡ -1 if a is not a square (mod p)\n // (a | p) ≡ 0 if a ≡ 0 (mod p)\n const legendreC = (P - _1n) / _2n;\n\n let Q: bigint, S: number, Z: bigint;\n // Step 1: By factoring out powers of 2 from p - 1,\n // find q and s such that p - 1 = q*(2^s) with q odd\n for (Q = P - _1n, S = 0; Q % _2n === _0n; Q /= _2n, S++);\n\n // Step 2: Select a non-square z such that (z | p) ≡ -1 and set c ≡ zq\n for (Z = _2n; Z < P && pow(Z, legendreC, P) !== P - _1n; Z++) {\n // Crash instead of infinity loop, we cannot reasonable count until P.\n if (Z > 1000) throw new Error('Cannot find square root: likely non-prime P');\n }\n\n // Fast-path\n if (S === 1) {\n const p1div4 = (P + _1n) / _4n;\n return function tonelliFast(Fp: IField, n: T) {\n const root = Fp.pow(n, p1div4);\n if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');\n return root;\n };\n }\n\n // Slow-path\n const Q1div2 = (Q + _1n) / _2n;\n return function tonelliSlow(Fp: IField, n: T): T {\n // Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1\n if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE)) throw new Error('Cannot find square root');\n let r = S;\n // TODO: will fail at Fp2/etc\n let g = Fp.pow(Fp.mul(Fp.ONE, Z), Q); // will update both x and b\n let x = Fp.pow(n, Q1div2); // first guess at the square root\n let b = Fp.pow(n, Q); // first guess at the fudge factor\n\n while (!Fp.eql(b, Fp.ONE)) {\n if (Fp.eql(b, Fp.ZERO)) return Fp.ZERO; // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm (4. If t = 0, return r = 0)\n // Find m such b^(2^m)==1\n let m = 1;\n for (let t2 = Fp.sqr(b); m < r; m++) {\n if (Fp.eql(t2, Fp.ONE)) break;\n t2 = Fp.sqr(t2); // t2 *= t2\n }\n // NOTE: r-m-1 can be bigger than 32, need to convert to bigint before shift, otherwise there will be overflow\n const ge = Fp.pow(g, _1n << BigInt(r - m - 1)); // ge = 2^(r-m-1)\n g = Fp.sqr(ge); // g = ge * ge\n x = Fp.mul(x, ge); // x *= ge\n b = Fp.mul(b, g); // b *= g\n r = m;\n }\n return x;\n };\n}\n\n/**\n * Square root for a finite field. It will try to check if optimizations are applicable and fall back to 4:\n *\n * 1. P ≡ 3 (mod 4)\n * 2. P ≡ 5 (mod 8)\n * 3. P ≡ 9 (mod 16)\n * 4. Tonelli-Shanks algorithm\n *\n * Different algorithms can give different roots, it is up to user to decide which one they want.\n * For example there is FpSqrtOdd/FpSqrtEven to choice root based on oddness (used for hash-to-curve).\n */\nexport function FpSqrt(P: bigint): (Fp: IField, n: T) => T {\n // P ≡ 3 (mod 4)\n // √n = n^((P+1)/4)\n if (P % _4n === _3n) {\n // Not all roots possible!\n // const ORDER =\n // 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn;\n // const NUM = 72057594037927816n;\n const p1div4 = (P + _1n) / _4n;\n return function sqrt3mod4(Fp: IField, n: T) {\n const root = Fp.pow(n, p1div4);\n // Throw if root**2 != n\n if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');\n return root;\n };\n }\n\n // Atkin algorithm for q ≡ 5 (mod 8), https://eprint.iacr.org/2012/685.pdf (page 10)\n if (P % _8n === _5n) {\n const c1 = (P - _5n) / _8n;\n return function sqrt5mod8(Fp: IField, n: T) {\n const n2 = Fp.mul(n, _2n);\n const v = Fp.pow(n2, c1);\n const nv = Fp.mul(n, v);\n const i = Fp.mul(Fp.mul(nv, _2n), v);\n const root = Fp.mul(nv, Fp.sub(i, Fp.ONE));\n if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');\n return root;\n };\n }\n\n // P ≡ 9 (mod 16)\n if (P % _16n === _9n) {\n // NOTE: tonelli is too slow for bls-Fp2 calculations even on start\n // Means we cannot use sqrt for constants at all!\n //\n // const c1 = Fp.sqrt(Fp.negate(Fp.ONE)); // 1. c1 = sqrt(-1) in F, i.e., (c1^2) == -1 in F\n // const c2 = Fp.sqrt(c1); // 2. c2 = sqrt(c1) in F, i.e., (c2^2) == c1 in F\n // const c3 = Fp.sqrt(Fp.negate(c1)); // 3. c3 = sqrt(-c1) in F, i.e., (c3^2) == -c1 in F\n // const c4 = (P + _7n) / _16n; // 4. c4 = (q + 7) / 16 # Integer arithmetic\n // sqrt = (x) => {\n // let tv1 = Fp.pow(x, c4); // 1. tv1 = x^c4\n // let tv2 = Fp.mul(c1, tv1); // 2. tv2 = c1 * tv1\n // const tv3 = Fp.mul(c2, tv1); // 3. tv3 = c2 * tv1\n // let tv4 = Fp.mul(c3, tv1); // 4. tv4 = c3 * tv1\n // const e1 = Fp.equals(Fp.square(tv2), x); // 5. e1 = (tv2^2) == x\n // const e2 = Fp.equals(Fp.square(tv3), x); // 6. e2 = (tv3^2) == x\n // tv1 = Fp.cmov(tv1, tv2, e1); // 7. tv1 = CMOV(tv1, tv2, e1) # Select tv2 if (tv2^2) == x\n // tv2 = Fp.cmov(tv4, tv3, e2); // 8. tv2 = CMOV(tv4, tv3, e2) # Select tv3 if (tv3^2) == x\n // const e3 = Fp.equals(Fp.square(tv2), x); // 9. e3 = (tv2^2) == x\n // return Fp.cmov(tv1, tv2, e3); // 10. z = CMOV(tv1, tv2, e3) # Select the sqrt from tv1 and tv2\n // }\n }\n // Other cases: Tonelli-Shanks algorithm\n return tonelliShanks(P);\n}\n\n// Little-endian check for first LE bit (last BE bit);\nexport const isNegativeLE = (num: bigint, modulo: bigint): boolean =>\n (mod(num, modulo) & _1n) === _1n;\n\n/** Field is not always over prime: for example, Fp2 has ORDER(q)=p^m. */\nexport interface IField {\n ORDER: bigint;\n isLE: boolean;\n BYTES: number;\n BITS: number;\n MASK: bigint;\n ZERO: T;\n ONE: T;\n // 1-arg\n create: (num: T) => T;\n isValid: (num: T) => boolean;\n is0: (num: T) => boolean;\n neg(num: T): T;\n inv(num: T): T;\n sqrt(num: T): T;\n sqr(num: T): T;\n // 2-args\n eql(lhs: T, rhs: T): boolean;\n add(lhs: T, rhs: T): T;\n sub(lhs: T, rhs: T): T;\n mul(lhs: T, rhs: T | bigint): T;\n pow(lhs: T, power: bigint): T;\n div(lhs: T, rhs: T | bigint): T;\n // N for NonNormalized (for now)\n addN(lhs: T, rhs: T): T;\n subN(lhs: T, rhs: T): T;\n mulN(lhs: T, rhs: T | bigint): T;\n sqrN(num: T): T;\n\n // Optional\n // Should be same as sgn0 function in\n // [RFC9380](https://www.rfc-editor.org/rfc/rfc9380#section-4.1).\n // NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway.\n isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2\n // legendre?(num: T): T;\n pow(lhs: T, power: bigint): T;\n invertBatch: (lst: T[]) => T[];\n toBytes(num: T): Uint8Array;\n fromBytes(bytes: Uint8Array): T;\n // If c is False, CMOV returns a, otherwise it returns b.\n cmov(a: T, b: T, c: boolean): T;\n}\n// prettier-ignore\nconst FIELD_FIELDS = [\n 'create', 'isValid', 'is0', 'neg', 'inv', 'sqrt', 'sqr',\n 'eql', 'add', 'sub', 'mul', 'pow', 'div',\n 'addN', 'subN', 'mulN', 'sqrN'\n] as const;\nexport function validateField(field: IField): IField {\n const initial = {\n ORDER: 'bigint',\n MASK: 'bigint',\n BYTES: 'isSafeInteger',\n BITS: 'isSafeInteger',\n } as Record;\n const opts = FIELD_FIELDS.reduce((map, val: string) => {\n map[val] = 'function';\n return map;\n }, initial);\n return validateObject(field, opts);\n}\n\n// Generic field functions\n\n/**\n * Same as `pow` but for Fp: non-constant-time.\n * Unsafe in some contexts: uses ladder, so can expose bigint bits.\n */\nexport function FpPow(f: IField, num: T, power: bigint): T {\n // Should have same speed as pow for bigints\n // TODO: benchmark!\n if (power < _0n) throw new Error('invalid exponent, negatives unsupported');\n if (power === _0n) return f.ONE;\n if (power === _1n) return num;\n let p = f.ONE;\n let d = num;\n while (power > _0n) {\n if (power & _1n) p = f.mul(p, d);\n d = f.sqr(d);\n power >>= _1n;\n }\n return p;\n}\n\n/**\n * Efficiently invert an array of Field elements.\n * `inv(0)` will return `undefined` here: make sure to throw an error.\n */\nexport function FpInvertBatch(f: IField, nums: T[]): T[] {\n const tmp = new Array(nums.length);\n // Walk from first to last, multiply them by each other MOD p\n const lastMultiplied = nums.reduce((acc, num, i) => {\n if (f.is0(num)) return acc;\n tmp[i] = acc;\n return f.mul(acc, num);\n }, f.ONE);\n // Invert last element\n const inverted = f.inv(lastMultiplied);\n // Walk from last to first, multiply them by inverted each other MOD p\n nums.reduceRight((acc, num, i) => {\n if (f.is0(num)) return acc;\n tmp[i] = f.mul(acc, tmp[i]);\n return f.mul(acc, num);\n }, inverted);\n return tmp;\n}\n\nexport function FpDiv(f: IField, lhs: T, rhs: T | bigint): T {\n return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.inv(rhs));\n}\n\n/**\n * Legendre symbol.\n * * (a | p) ≡ 1 if a is a square (mod p), quadratic residue\n * * (a | p) ≡ -1 if a is not a square (mod p), quadratic non residue\n * * (a | p) ≡ 0 if a ≡ 0 (mod p)\n */\nexport function FpLegendre(order: bigint): (f: IField, x: T) => T {\n const legendreConst = (order - _1n) / _2n; // Integer arithmetic\n return (f: IField, x: T): T => f.pow(x, legendreConst);\n}\n\n// This function returns True whenever the value x is a square in the field F.\nexport function FpIsSquare(f: IField): (x: T) => boolean {\n const legendre = FpLegendre(f.ORDER);\n return (x: T): boolean => {\n const p = legendre(f, x);\n return f.eql(p, f.ZERO) || f.eql(p, f.ONE);\n };\n}\n\n// CURVE.n lengths\nexport function nLength(\n n: bigint,\n nBitLength?: number\n): {\n nBitLength: number;\n nByteLength: number;\n} {\n // Bit size, byte size of CURVE.n\n const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;\n const nByteLength = Math.ceil(_nBitLength / 8);\n return { nBitLength: _nBitLength, nByteLength };\n}\n\ntype FpField = IField & Required, 'isOdd'>>;\n/**\n * Initializes a finite field over prime.\n * Major performance optimizations:\n * * a) denormalized operations like mulN instead of mul\n * * b) same object shape: never add or remove keys\n * * c) Object.freeze\n * Fragile: always run a benchmark on a change.\n * Security note: operations don't check 'isValid' for all elements for performance reasons,\n * it is caller responsibility to check this.\n * This is low-level code, please make sure you know what you're doing.\n * @param ORDER prime positive bigint\n * @param bitLen how many bits the field consumes\n * @param isLE (def: false) if encoding / decoding should be in little-endian\n * @param redef optional faster redefinitions of sqrt and other methods\n */\nexport function Field(\n ORDER: bigint,\n bitLen?: number,\n isLE = false,\n redef: Partial> = {}\n): Readonly {\n if (ORDER <= _0n) throw new Error('invalid field: expected ORDER > 0, got ' + ORDER);\n const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);\n if (BYTES > 2048) throw new Error('invalid field: expected ORDER of <= 2048 bytes');\n let sqrtP: ReturnType; // cached sqrtP\n const f: Readonly = Object.freeze({\n ORDER,\n isLE,\n BITS,\n BYTES,\n MASK: bitMask(BITS),\n ZERO: _0n,\n ONE: _1n,\n create: (num) => mod(num, ORDER),\n isValid: (num) => {\n if (typeof num !== 'bigint')\n throw new Error('invalid field element: expected bigint, got ' + typeof num);\n return _0n <= num && num < ORDER; // 0 is valid element, but it's not invertible\n },\n is0: (num) => num === _0n,\n isOdd: (num) => (num & _1n) === _1n,\n neg: (num) => mod(-num, ORDER),\n eql: (lhs, rhs) => lhs === rhs,\n\n sqr: (num) => mod(num * num, ORDER),\n add: (lhs, rhs) => mod(lhs + rhs, ORDER),\n sub: (lhs, rhs) => mod(lhs - rhs, ORDER),\n mul: (lhs, rhs) => mod(lhs * rhs, ORDER),\n pow: (num, power) => FpPow(f, num, power),\n div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER),\n\n // Same as above, but doesn't normalize\n sqrN: (num) => num * num,\n addN: (lhs, rhs) => lhs + rhs,\n subN: (lhs, rhs) => lhs - rhs,\n mulN: (lhs, rhs) => lhs * rhs,\n\n inv: (num) => invert(num, ORDER),\n sqrt:\n redef.sqrt ||\n ((n) => {\n if (!sqrtP) sqrtP = FpSqrt(ORDER);\n return sqrtP(f, n);\n }),\n invertBatch: (lst) => FpInvertBatch(f, lst),\n // TODO: do we really need constant cmov?\n // We don't have const-time bigints anyway, so probably will be not very useful\n cmov: (a, b, c) => (c ? b : a),\n toBytes: (num) => (isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES)),\n fromBytes: (bytes) => {\n if (bytes.length !== BYTES)\n throw new Error('Field.fromBytes: expected ' + BYTES + ' bytes, got ' + bytes.length);\n return isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);\n },\n } as FpField);\n return Object.freeze(f);\n}\n\nexport function FpSqrtOdd(Fp: IField, elm: T): T {\n if (!Fp.isOdd) throw new Error(\"Field doesn't have isOdd\");\n const root = Fp.sqrt(elm);\n return Fp.isOdd(root) ? root : Fp.neg(root);\n}\n\nexport function FpSqrtEven(Fp: IField, elm: T): T {\n if (!Fp.isOdd) throw new Error(\"Field doesn't have isOdd\");\n const root = Fp.sqrt(elm);\n return Fp.isOdd(root) ? Fp.neg(root) : root;\n}\n\n/**\n * \"Constant-time\" private key generation utility.\n * Same as mapKeyToField, but accepts less bytes (40 instead of 48 for 32-byte field).\n * Which makes it slightly more biased, less secure.\n * @deprecated use `mapKeyToField` instead\n */\nexport function hashToPrivateScalar(\n hash: string | Uint8Array,\n groupOrder: bigint,\n isLE = false\n): bigint {\n hash = ensureBytes('privateHash', hash);\n const hashLen = hash.length;\n const minLen = nLength(groupOrder).nByteLength + 8;\n if (minLen < 24 || hashLen < minLen || hashLen > 1024)\n throw new Error(\n 'hashToPrivateScalar: expected ' + minLen + '-1024 bytes of input, got ' + hashLen\n );\n const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);\n return mod(num, groupOrder - _1n) + _1n;\n}\n\n/**\n * Returns total number of bytes consumed by the field element.\n * For example, 32 bytes for usual 256-bit weierstrass curve.\n * @param fieldOrder number of field elements, usually CURVE.n\n * @returns byte length of field\n */\nexport function getFieldBytesLength(fieldOrder: bigint): number {\n if (typeof fieldOrder !== 'bigint') throw new Error('field order must be bigint');\n const bitLength = fieldOrder.toString(2).length;\n return Math.ceil(bitLength / 8);\n}\n\n/**\n * Returns minimal amount of bytes that can be safely reduced\n * by field order.\n * Should be 2^-128 for 128-bit curve such as P256.\n * @param fieldOrder number of field elements, usually CURVE.n\n * @returns byte length of target hash\n */\nexport function getMinHashLength(fieldOrder: bigint): number {\n const length = getFieldBytesLength(fieldOrder);\n return length + Math.ceil(length / 2);\n}\n\n/**\n * \"Constant-time\" private key generation utility.\n * Can take (n + n/2) or more bytes of uniform input e.g. from CSPRNG or KDF\n * and convert them into private scalar, with the modulo bias being negligible.\n * Needs at least 48 bytes of input for 32-byte private key.\n * https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/\n * FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final\n * RFC 9380, https://www.rfc-editor.org/rfc/rfc9380#section-5\n * @param hash hash output from SHA3 or a similar function\n * @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n)\n * @param isLE interpret hash bytes as LE num\n * @returns valid private scalar\n */\nexport function mapHashToField(key: Uint8Array, fieldOrder: bigint, isLE = false): Uint8Array {\n const len = key.length;\n const fieldLen = getFieldBytesLength(fieldOrder);\n const minLen = getMinHashLength(fieldOrder);\n // No small numbers: need to understand bias story. No huge numbers: easier to detect JS timings.\n if (len < 16 || len < minLen || len > 1024)\n throw new Error('expected ' + minLen + '-1024 bytes of input, got ' + len);\n const num = isLE ? bytesToNumberLE(key) : bytesToNumberBE(key);\n // `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0\n const reduced = mod(num, fieldOrder - _1n) + _1n;\n return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);\n}\n","/**\n * Methods for elliptic curve multiplication by scalars.\n * Contains wNAF, pippenger\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport { type IField, nLength, validateField } from './modular.js';\nimport { bitLen, validateObject } from './utils.js';\n\nconst _0n = BigInt(0);\nconst _1n = BigInt(1);\n\nexport type AffinePoint = {\n x: T;\n y: T;\n} & { z?: never; t?: never };\n\nexport interface Group> {\n double(): T;\n negate(): T;\n add(other: T): T;\n subtract(other: T): T;\n equals(other: T): boolean;\n multiply(scalar: bigint): T;\n}\n\nexport type GroupConstructor = {\n BASE: T;\n ZERO: T;\n};\nexport type Mapper = (i: T[]) => T[];\n\nfunction constTimeNegate>(condition: boolean, item: T): T {\n const neg = item.negate();\n return condition ? neg : item;\n}\n\nfunction validateW(W: number, bits: number) {\n if (!Number.isSafeInteger(W) || W <= 0 || W > bits)\n throw new Error('invalid window size, expected [1..' + bits + '], got W=' + W);\n}\n\nfunction calcWOpts(W: number, bits: number) {\n validateW(W, bits);\n const windows = Math.ceil(bits / W) + 1; // +1, because\n const windowSize = 2 ** (W - 1); // -1 because we skip zero\n return { windows, windowSize };\n}\n\nfunction validateMSMPoints(points: any[], c: any) {\n if (!Array.isArray(points)) throw new Error('array expected');\n points.forEach((p, i) => {\n if (!(p instanceof c)) throw new Error('invalid point at index ' + i);\n });\n}\nfunction validateMSMScalars(scalars: any[], field: any) {\n if (!Array.isArray(scalars)) throw new Error('array of scalars expected');\n scalars.forEach((s, i) => {\n if (!field.isValid(s)) throw new Error('invalid scalar at index ' + i);\n });\n}\n\n// Since points in different groups cannot be equal (different object constructor),\n// we can have single place to store precomputes\nconst pointPrecomputes = new WeakMap();\nconst pointWindowSizes = new WeakMap(); // This allows use make points immutable (nothing changes inside)\n\nfunction getW(P: any): number {\n return pointWindowSizes.get(P) || 1;\n}\n\nexport type IWNAF> = {\n constTimeNegate: >(condition: boolean, item: T) => T;\n hasPrecomputes(elm: T): boolean;\n unsafeLadder(elm: T, n: bigint, p?: T): T;\n precomputeWindow(elm: T, W: number): Group[];\n wNAF(W: number, precomputes: T[], n: bigint): { p: T; f: T };\n wNAFUnsafe(W: number, precomputes: T[], n: bigint, acc?: T): T;\n getPrecomputes(W: number, P: T, transform: Mapper): T[];\n wNAFCached(P: T, n: bigint, transform: Mapper): { p: T; f: T };\n wNAFCachedUnsafe(P: T, n: bigint, transform: Mapper, prev?: T): T;\n setWindowSize(P: T, W: number): void;\n};\n\n/**\n * Elliptic curve multiplication of Point by scalar. Fragile.\n * Scalars should always be less than curve order: this should be checked inside of a curve itself.\n * Creates precomputation tables for fast multiplication:\n * - private scalar is split by fixed size windows of W bits\n * - every window point is collected from window's table & added to accumulator\n * - since windows are different, same point inside tables won't be accessed more than once per calc\n * - each multiplication is 'Math.ceil(CURVE_ORDER / 𝑊) + 1' point additions (fixed for any scalar)\n * - +1 window is neccessary for wNAF\n * - wNAF reduces table size: 2x less memory + 2x faster generation, but 10% slower multiplication\n *\n * @todo Research returning 2d JS array of windows, instead of a single window.\n * This would allow windows to be in different memory locations\n */\nexport function wNAF>(c: GroupConstructor, bits: number): IWNAF {\n return {\n constTimeNegate,\n\n hasPrecomputes(elm: T) {\n return getW(elm) !== 1;\n },\n\n // non-const time multiplication ladder\n unsafeLadder(elm: T, n: bigint, p = c.ZERO) {\n let d: T = elm;\n while (n > _0n) {\n if (n & _1n) p = p.add(d);\n d = d.double();\n n >>= _1n;\n }\n return p;\n },\n\n /**\n * Creates a wNAF precomputation window. Used for caching.\n * Default window size is set by `utils.precompute()` and is equal to 8.\n * Number of precomputed points depends on the curve size:\n * 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where:\n * - 𝑊 is the window size\n * - 𝑛 is the bitlength of the curve order.\n * For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.\n * @param elm Point instance\n * @param W window size\n * @returns precomputed point tables flattened to a single array\n */\n precomputeWindow(elm: T, W: number): Group[] {\n const { windows, windowSize } = calcWOpts(W, bits);\n const points: T[] = [];\n let p: T = elm;\n let base = p;\n for (let window = 0; window < windows; window++) {\n base = p;\n points.push(base);\n // =1, because we skip zero\n for (let i = 1; i < windowSize; i++) {\n base = base.add(p);\n points.push(base);\n }\n p = base.double();\n }\n return points;\n },\n\n /**\n * Implements ec multiplication using precomputed tables and w-ary non-adjacent form.\n * @param W window size\n * @param precomputes precomputed tables\n * @param n scalar (we don't check here, but should be less than curve order)\n * @returns real and fake (for const-time) points\n */\n wNAF(W: number, precomputes: T[], n: bigint): { p: T; f: T } {\n // TODO: maybe check that scalar is less than group order? wNAF behavious is undefined otherwise\n // But need to carefully remove other checks before wNAF. ORDER == bits here\n const { windows, windowSize } = calcWOpts(W, bits);\n\n let p = c.ZERO;\n let f = c.BASE;\n\n const mask = BigInt(2 ** W - 1); // Create mask with W ones: 0b1111 for W=4 etc.\n const maxNumber = 2 ** W;\n const shiftBy = BigInt(W);\n\n for (let window = 0; window < windows; window++) {\n const offset = window * windowSize;\n // Extract W bits.\n let wbits = Number(n & mask);\n\n // Shift number by W bits.\n n >>= shiftBy;\n\n // If the bits are bigger than max size, we'll split those.\n // +224 => 256 - 32\n if (wbits > windowSize) {\n wbits -= maxNumber;\n n += _1n;\n }\n\n // This code was first written with assumption that 'f' and 'p' will never be infinity point:\n // since each addition is multiplied by 2 ** W, it cannot cancel each other. However,\n // there is negate now: it is possible that negated element from low value\n // would be the same as high element, which will create carry into next window.\n // It's not obvious how this can fail, but still worth investigating later.\n\n // Check if we're onto Zero point.\n // Add random point inside current window to f.\n const offset1 = offset;\n const offset2 = offset + Math.abs(wbits) - 1; // -1 because we skip zero\n const cond1 = window % 2 !== 0;\n const cond2 = wbits < 0;\n if (wbits === 0) {\n // The most important part for const-time getPublicKey\n f = f.add(constTimeNegate(cond1, precomputes[offset1]));\n } else {\n p = p.add(constTimeNegate(cond2, precomputes[offset2]));\n }\n }\n // JIT-compiler should not eliminate f here, since it will later be used in normalizeZ()\n // Even if the variable is still unused, there are some checks which will\n // throw an exception, so compiler needs to prove they won't happen, which is hard.\n // At this point there is a way to F be infinity-point even if p is not,\n // which makes it less const-time: around 1 bigint multiply.\n return { p, f };\n },\n\n /**\n * Implements ec unsafe (non const-time) multiplication using precomputed tables and w-ary non-adjacent form.\n * @param W window size\n * @param precomputes precomputed tables\n * @param n scalar (we don't check here, but should be less than curve order)\n * @param acc accumulator point to add result of multiplication\n * @returns point\n */\n wNAFUnsafe(W: number, precomputes: T[], n: bigint, acc: T = c.ZERO): T {\n const { windows, windowSize } = calcWOpts(W, bits);\n const mask = BigInt(2 ** W - 1); // Create mask with W ones: 0b1111 for W=4 etc.\n const maxNumber = 2 ** W;\n const shiftBy = BigInt(W);\n for (let window = 0; window < windows; window++) {\n const offset = window * windowSize;\n if (n === _0n) break; // No need to go over empty scalar\n // Extract W bits.\n let wbits = Number(n & mask);\n // Shift number by W bits.\n n >>= shiftBy;\n // If the bits are bigger than max size, we'll split those.\n // +224 => 256 - 32\n if (wbits > windowSize) {\n wbits -= maxNumber;\n n += _1n;\n }\n if (wbits === 0) continue;\n let curr = precomputes[offset + Math.abs(wbits) - 1]; // -1 because we skip zero\n if (wbits < 0) curr = curr.negate();\n // NOTE: by re-using acc, we can save a lot of additions in case of MSM\n acc = acc.add(curr);\n }\n return acc;\n },\n\n getPrecomputes(W: number, P: T, transform: Mapper): T[] {\n // Calculate precomputes on a first run, reuse them after\n let comp = pointPrecomputes.get(P);\n if (!comp) {\n comp = this.precomputeWindow(P, W) as T[];\n if (W !== 1) pointPrecomputes.set(P, transform(comp));\n }\n return comp;\n },\n\n wNAFCached(P: T, n: bigint, transform: Mapper): { p: T; f: T } {\n const W = getW(P);\n return this.wNAF(W, this.getPrecomputes(W, P, transform), n);\n },\n\n wNAFCachedUnsafe(P: T, n: bigint, transform: Mapper, prev?: T): T {\n const W = getW(P);\n if (W === 1) return this.unsafeLadder(P, n, prev); // For W=1 ladder is ~x2 faster\n return this.wNAFUnsafe(W, this.getPrecomputes(W, P, transform), n, prev);\n },\n\n // We calculate precomputes for elliptic curve point multiplication\n // using windowed method. This specifies window size and\n // stores precomputed values. Usually only base point would be precomputed.\n\n setWindowSize(P: T, W: number) {\n validateW(W, bits);\n pointWindowSizes.set(P, W);\n pointPrecomputes.delete(P);\n },\n };\n}\n\n/**\n * Pippenger algorithm for multi-scalar multiplication (MSM, Pa + Qb + Rc + ...).\n * 30x faster vs naive addition on L=4096, 10x faster with precomputes.\n * For N=254bit, L=1, it does: 1024 ADD + 254 DBL. For L=5: 1536 ADD + 254 DBL.\n * Algorithmically constant-time (for same L), even when 1 point + scalar, or when scalar = 0.\n * @param c Curve Point constructor\n * @param fieldN field over CURVE.N - important that it's not over CURVE.P\n * @param points array of L curve points\n * @param scalars array of L scalars (aka private keys / bigints)\n */\nexport function pippenger>(\n c: GroupConstructor,\n fieldN: IField,\n points: T[],\n scalars: bigint[]\n): T {\n // If we split scalars by some window (let's say 8 bits), every chunk will only\n // take 256 buckets even if there are 4096 scalars, also re-uses double.\n // TODO:\n // - https://eprint.iacr.org/2024/750.pdf\n // - https://tches.iacr.org/index.php/TCHES/article/view/10287\n // 0 is accepted in scalars\n validateMSMPoints(points, c);\n validateMSMScalars(scalars, fieldN);\n if (points.length !== scalars.length)\n throw new Error('arrays of points and scalars must have equal length');\n const zero = c.ZERO;\n const wbits = bitLen(BigInt(points.length));\n const windowSize = wbits > 12 ? wbits - 3 : wbits > 4 ? wbits - 2 : wbits ? 2 : 1; // in bits\n const MASK = (1 << windowSize) - 1;\n const buckets = new Array(MASK + 1).fill(zero); // +1 for zero array\n const lastBits = Math.floor((fieldN.BITS - 1) / windowSize) * windowSize;\n let sum = zero;\n for (let i = lastBits; i >= 0; i -= windowSize) {\n buckets.fill(zero);\n for (let j = 0; j < scalars.length; j++) {\n const scalar = scalars[j];\n const wbits = Number((scalar >> BigInt(i)) & BigInt(MASK));\n buckets[wbits] = buckets[wbits].add(points[j]);\n }\n let resI = zero; // not using this will do small speed-up, but will lose ct\n // Skip first bucket, because it is zero\n for (let j = buckets.length - 1, sumI = zero; j > 0; j--) {\n sumI = sumI.add(buckets[j]);\n resI = resI.add(sumI);\n }\n sum = sum.add(resI);\n if (i !== 0) for (let j = 0; j < windowSize; j++) sum = sum.double();\n }\n return sum as T;\n}\n/**\n * Precomputed multi-scalar multiplication (MSM, Pa + Qb + Rc + ...).\n * @param c Curve Point constructor\n * @param fieldN field over CURVE.N - important that it's not over CURVE.P\n * @param points array of L curve points\n * @returns function which multiplies points with scaars\n */\nexport function precomputeMSMUnsafe>(\n c: GroupConstructor,\n fieldN: IField,\n points: T[],\n windowSize: number\n): (scalars: bigint[]) => T {\n /**\n * Performance Analysis of Window-based Precomputation\n *\n * Base Case (256-bit scalar, 8-bit window):\n * - Standard precomputation requires:\n * - 31 additions per scalar × 256 scalars = 7,936 ops\n * - Plus 255 summary additions = 8,191 total ops\n * Note: Summary additions can be optimized via accumulator\n *\n * Chunked Precomputation Analysis:\n * - Using 32 chunks requires:\n * - 255 additions per chunk\n * - 256 doublings\n * - Total: (255 × 32) + 256 = 8,416 ops\n *\n * Memory Usage Comparison:\n * Window Size | Standard Points | Chunked Points\n * ------------|-----------------|---------------\n * 4-bit | 520 | 15\n * 8-bit | 4,224 | 255\n * 10-bit | 13,824 | 1,023\n * 16-bit | 557,056 | 65,535\n *\n * Key Advantages:\n * 1. Enables larger window sizes due to reduced memory overhead\n * 2. More efficient for smaller scalar counts:\n * - 16 chunks: (16 × 255) + 256 = 4,336 ops\n * - ~2x faster than standard 8,191 ops\n *\n * Limitations:\n * - Not suitable for plain precomputes (requires 256 constant doublings)\n * - Performance degrades with larger scalar counts:\n * - Optimal for ~256 scalars\n * - Less efficient for 4096+ scalars (Pippenger preferred)\n */\n validateW(windowSize, fieldN.BITS);\n validateMSMPoints(points, c);\n const zero = c.ZERO;\n const tableSize = 2 ** windowSize - 1; // table size (without zero)\n const chunks = Math.ceil(fieldN.BITS / windowSize); // chunks of item\n const MASK = BigInt((1 << windowSize) - 1);\n const tables = points.map((p: T) => {\n const res = [];\n for (let i = 0, acc = p; i < tableSize; i++) {\n res.push(acc);\n acc = acc.add(p);\n }\n return res;\n });\n return (scalars: bigint[]): T => {\n validateMSMScalars(scalars, fieldN);\n if (scalars.length > points.length)\n throw new Error('array of scalars must be smaller than array of points');\n let res = zero;\n for (let i = 0; i < chunks; i++) {\n // No need to double if accumulator is still zero.\n if (res !== zero) for (let j = 0; j < windowSize; j++) res = res.double();\n const shiftBy = BigInt(chunks * windowSize - (i + 1) * windowSize);\n for (let j = 0; j < scalars.length; j++) {\n const n = scalars[j];\n const curr = Number((n >> shiftBy) & MASK);\n if (!curr) continue; // skip zero scalars chunks\n res = res.add(tables[j][curr - 1]);\n }\n }\n return res;\n };\n}\n\n/**\n * Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.\n * Though generator can be different (Fp2 / Fp6 for BLS).\n */\nexport type BasicCurve = {\n Fp: IField; // Field over which we'll do calculations (Fp)\n n: bigint; // Curve order, total count of valid points in the field\n nBitLength?: number; // bit length of curve order\n nByteLength?: number; // byte length of curve order\n h: bigint; // cofactor. we can assign default=1, but users will just ignore it w/o validation\n hEff?: bigint; // Number to multiply to clear cofactor\n Gx: T; // base point X coordinate\n Gy: T; // base point Y coordinate\n allowInfinityPoint?: boolean; // bls12-381 requires it. ZERO point is valid, but invalid pubkey\n};\n\nexport function validateBasic(\n curve: BasicCurve & T\n): Readonly<\n {\n readonly nBitLength: number;\n readonly nByteLength: number;\n } & BasicCurve &\n T & {\n p: bigint;\n }\n> {\n validateField(curve.Fp);\n validateObject(\n curve,\n {\n n: 'bigint',\n h: 'bigint',\n Gx: 'field',\n Gy: 'field',\n },\n {\n nBitLength: 'isSafeInteger',\n nByteLength: 'isSafeInteger',\n }\n );\n // Set defaults\n return Object.freeze({\n ...nLength(curve.n, curve.nBitLength),\n ...curve,\n ...{ p: curve.Fp.ORDER },\n } as const);\n}\n","/**\n * Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y².\n * For design rationale of types / exports, see weierstrass module documentation.\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport {\n type AffinePoint,\n type BasicCurve,\n type Group,\n type GroupConstructor,\n pippenger,\n validateBasic,\n wNAF,\n} from './curve.js';\nimport { Field, mod } from './modular.js';\nimport * as ut from './utils.js';\nimport { abool, ensureBytes, type FHash, type Hex, memoized } from './utils.js';\n\n// Be friendly to bad ECMAScript parsers by not using bigint literals\n// prettier-ignore\nconst _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _8n = BigInt(8);\n\n/** Edwards curves must declare params a & d. */\nexport type CurveType = BasicCurve & {\n a: bigint; // curve param a\n d: bigint; // curve param d\n hash: FHash; // Hashing\n randomBytes: (bytesLength?: number) => Uint8Array; // CSPRNG\n adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; // clears bits to get valid field elemtn\n domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; // Used for hashing\n uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; // Ratio √(u/v)\n prehash?: FHash; // RFC 8032 pre-hashing of messages to sign() / verify()\n mapToCurve?: (scalar: bigint[]) => AffinePoint; // for hash-to-curve standard\n};\n\nexport type CurveTypeWithLength = Readonly;\n\n// verification rule is either zip215 or rfc8032 / nist186-5. Consult fromHex:\nconst VERIFY_DEFAULT = { zip215: true };\n\nfunction validateOpts(curve: CurveType): CurveTypeWithLength {\n const opts = validateBasic(curve);\n ut.validateObject(\n curve,\n {\n hash: 'function',\n a: 'bigint',\n d: 'bigint',\n randomBytes: 'function',\n },\n {\n adjustScalarBytes: 'function',\n domain: 'function',\n uvRatio: 'function',\n mapToCurve: 'function',\n }\n );\n // Set defaults\n return Object.freeze({ ...opts } as const);\n}\n\n/** Instance of Extended Point with coordinates in X, Y, Z, T. */\nexport interface ExtPointType extends Group {\n readonly ex: bigint;\n readonly ey: bigint;\n readonly ez: bigint;\n readonly et: bigint;\n get x(): bigint;\n get y(): bigint;\n assertValidity(): void;\n multiply(scalar: bigint): ExtPointType;\n multiplyUnsafe(scalar: bigint): ExtPointType;\n isSmallOrder(): boolean;\n isTorsionFree(): boolean;\n clearCofactor(): ExtPointType;\n toAffine(iz?: bigint): AffinePoint;\n toRawBytes(isCompressed?: boolean): Uint8Array;\n toHex(isCompressed?: boolean): string;\n _setWindowSize(windowSize: number): void;\n}\n/** Static methods of Extended Point with coordinates in X, Y, Z, T. */\nexport interface ExtPointConstructor extends GroupConstructor {\n new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType;\n fromAffine(p: AffinePoint): ExtPointType;\n fromHex(hex: Hex): ExtPointType;\n fromPrivateKey(privateKey: Hex): ExtPointType;\n msm(points: ExtPointType[], scalars: bigint[]): ExtPointType;\n}\n\n/**\n * Edwards Curve interface.\n * Main methods: `getPublicKey(priv)`, `sign(msg, priv)`, `verify(sig, msg, pub)`.\n */\nexport type CurveFn = {\n CURVE: ReturnType;\n getPublicKey: (privateKey: Hex) => Uint8Array;\n sign: (message: Hex, privateKey: Hex, options?: { context?: Hex }) => Uint8Array;\n verify: (\n sig: Hex,\n message: Hex,\n publicKey: Hex,\n options?: { context?: Hex; zip215: boolean }\n ) => boolean;\n ExtendedPoint: ExtPointConstructor;\n utils: {\n randomPrivateKey: () => Uint8Array;\n getExtendedPublicKey: (key: Hex) => {\n head: Uint8Array;\n prefix: Uint8Array;\n scalar: bigint;\n point: ExtPointType;\n pointBytes: Uint8Array;\n };\n precompute: (windowSize?: number, point?: ExtPointType) => ExtPointType;\n };\n};\n\n/**\n * Creates Twisted Edwards curve with EdDSA signatures.\n * @example\n * import { Field } from '@noble/curves/abstract/modular';\n * // Before that, define BigInt-s: a, d, p, n, Gx, Gy, h\n * const curve = twistedEdwards({ a, d, Fp: Field(p), n, Gx, Gy, h })\n */\nexport function twistedEdwards(curveDef: CurveType): CurveFn {\n const CURVE = validateOpts(curveDef) as ReturnType;\n const {\n Fp,\n n: CURVE_ORDER,\n prehash: prehash,\n hash: cHash,\n randomBytes,\n nByteLength,\n h: cofactor,\n } = CURVE;\n // Important:\n // There are some places where Fp.BYTES is used instead of nByteLength.\n // So far, everything has been tested with curves of Fp.BYTES == nByteLength.\n // TODO: test and find curves which behave otherwise.\n const MASK = _2n << (BigInt(nByteLength * 8) - _1n);\n const modP = Fp.create; // Function overrides\n const Fn = Field(CURVE.n, CURVE.nBitLength);\n\n // sqrt(u/v)\n const uvRatio =\n CURVE.uvRatio ||\n ((u: bigint, v: bigint) => {\n try {\n return { isValid: true, value: Fp.sqrt(u * Fp.inv(v)) };\n } catch (e) {\n return { isValid: false, value: _0n };\n }\n });\n const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes); // NOOP\n const domain =\n CURVE.domain ||\n ((data: Uint8Array, ctx: Uint8Array, phflag: boolean) => {\n abool('phflag', phflag);\n if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported');\n return data;\n }); // NOOP\n // 0 <= n < MASK\n // Coordinates larger than Fp.ORDER are allowed for zip215\n function aCoordinate(title: string, n: bigint) {\n ut.aInRange('coordinate ' + title, n, _0n, MASK);\n }\n\n function assertPoint(other: unknown) {\n if (!(other instanceof Point)) throw new Error('ExtendedPoint expected');\n }\n // Converts Extended point to default (x, y) coordinates.\n // Can accept precomputed Z^-1 - for example, from invertBatch.\n const toAffineMemo = memoized((p: Point, iz?: bigint): AffinePoint => {\n const { ex: x, ey: y, ez: z } = p;\n const is0 = p.is0();\n if (iz == null) iz = is0 ? _8n : (Fp.inv(z) as bigint); // 8 was chosen arbitrarily\n const ax = modP(x * iz);\n const ay = modP(y * iz);\n const zz = modP(z * iz);\n if (is0) return { x: _0n, y: _1n };\n if (zz !== _1n) throw new Error('invZ was invalid');\n return { x: ax, y: ay };\n });\n const assertValidMemo = memoized((p: Point) => {\n const { a, d } = CURVE;\n if (p.is0()) throw new Error('bad point: ZERO'); // TODO: optimize, with vars below?\n // Equation in affine coordinates: ax² + y² = 1 + dx²y²\n // Equation in projective coordinates (X/Z, Y/Z, Z): (aX² + Y²)Z² = Z⁴ + dX²Y²\n const { ex: X, ey: Y, ez: Z, et: T } = p;\n const X2 = modP(X * X); // X²\n const Y2 = modP(Y * Y); // Y²\n const Z2 = modP(Z * Z); // Z²\n const Z4 = modP(Z2 * Z2); // Z⁴\n const aX2 = modP(X2 * a); // aX²\n const left = modP(Z2 * modP(aX2 + Y2)); // (aX² + Y²)Z²\n const right = modP(Z4 + modP(d * modP(X2 * Y2))); // Z⁴ + dX²Y²\n if (left !== right) throw new Error('bad point: equation left != right (1)');\n // In Extended coordinates we also have T, which is x*y=T/Z: check X*Y == Z*T\n const XY = modP(X * Y);\n const ZT = modP(Z * T);\n if (XY !== ZT) throw new Error('bad point: equation left != right (2)');\n return true;\n });\n\n // Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy).\n // https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates\n class Point implements ExtPointType {\n static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy));\n static readonly ZERO = new Point(_0n, _1n, _1n, _0n); // 0, 1, 1, 0\n\n constructor(\n readonly ex: bigint,\n readonly ey: bigint,\n readonly ez: bigint,\n readonly et: bigint\n ) {\n aCoordinate('x', ex);\n aCoordinate('y', ey);\n aCoordinate('z', ez);\n aCoordinate('t', et);\n Object.freeze(this);\n }\n\n get x(): bigint {\n return this.toAffine().x;\n }\n get y(): bigint {\n return this.toAffine().y;\n }\n\n static fromAffine(p: AffinePoint): Point {\n if (p instanceof Point) throw new Error('extended point not allowed');\n const { x, y } = p || {};\n aCoordinate('x', x);\n aCoordinate('y', y);\n return new Point(x, y, _1n, modP(x * y));\n }\n static normalizeZ(points: Point[]): Point[] {\n const toInv = Fp.invertBatch(points.map((p) => p.ez));\n return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine);\n }\n // Multiscalar Multiplication\n static msm(points: Point[], scalars: bigint[]): Point {\n return pippenger(Point, Fn, points, scalars);\n }\n\n // \"Private method\", don't use it directly\n _setWindowSize(windowSize: number) {\n wnaf.setWindowSize(this, windowSize);\n }\n // Not required for fromHex(), which always creates valid points.\n // Could be useful for fromAffine().\n assertValidity(): void {\n assertValidMemo(this);\n }\n\n // Compare one point to another.\n equals(other: Point): boolean {\n assertPoint(other);\n const { ex: X1, ey: Y1, ez: Z1 } = this;\n const { ex: X2, ey: Y2, ez: Z2 } = other;\n const X1Z2 = modP(X1 * Z2);\n const X2Z1 = modP(X2 * Z1);\n const Y1Z2 = modP(Y1 * Z2);\n const Y2Z1 = modP(Y2 * Z1);\n return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;\n }\n\n is0(): boolean {\n return this.equals(Point.ZERO);\n }\n\n negate(): Point {\n // Flips point sign to a negative one (-x, y in affine coords)\n return new Point(modP(-this.ex), this.ey, this.ez, modP(-this.et));\n }\n\n // Fast algo for doubling Extended Point.\n // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd\n // Cost: 4M + 4S + 1*a + 6add + 1*2.\n double(): Point {\n const { a } = CURVE;\n const { ex: X1, ey: Y1, ez: Z1 } = this;\n const A = modP(X1 * X1); // A = X12\n const B = modP(Y1 * Y1); // B = Y12\n const C = modP(_2n * modP(Z1 * Z1)); // C = 2*Z12\n const D = modP(a * A); // D = a*A\n const x1y1 = X1 + Y1;\n const E = modP(modP(x1y1 * x1y1) - A - B); // E = (X1+Y1)2-A-B\n const G = D + B; // G = D+B\n const F = G - C; // F = G-C\n const H = D - B; // H = D-B\n const X3 = modP(E * F); // X3 = E*F\n const Y3 = modP(G * H); // Y3 = G*H\n const T3 = modP(E * H); // T3 = E*H\n const Z3 = modP(F * G); // Z3 = F*G\n return new Point(X3, Y3, Z3, T3);\n }\n\n // Fast algo for adding 2 Extended Points.\n // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd\n // Cost: 9M + 1*a + 1*d + 7add.\n add(other: Point) {\n assertPoint(other);\n const { a, d } = CURVE;\n const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;\n const { ex: X2, ey: Y2, ez: Z2, et: T2 } = other;\n // Faster algo for adding 2 Extended Points when curve's a=-1.\n // http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-4\n // Cost: 8M + 8add + 2*2.\n // Note: It does not check whether the `other` point is valid.\n if (a === BigInt(-1)) {\n const A = modP((Y1 - X1) * (Y2 + X2));\n const B = modP((Y1 + X1) * (Y2 - X2));\n const F = modP(B - A);\n if (F === _0n) return this.double(); // Same point. Tests say it doesn't affect timing\n const C = modP(Z1 * _2n * T2);\n const D = modP(T1 * _2n * Z2);\n const E = D + C;\n const G = B + A;\n const H = D - C;\n const X3 = modP(E * F);\n const Y3 = modP(G * H);\n const T3 = modP(E * H);\n const Z3 = modP(F * G);\n return new Point(X3, Y3, Z3, T3);\n }\n const A = modP(X1 * X2); // A = X1*X2\n const B = modP(Y1 * Y2); // B = Y1*Y2\n const C = modP(T1 * d * T2); // C = T1*d*T2\n const D = modP(Z1 * Z2); // D = Z1*Z2\n const E = modP((X1 + Y1) * (X2 + Y2) - A - B); // E = (X1+Y1)*(X2+Y2)-A-B\n const F = D - C; // F = D-C\n const G = D + C; // G = D+C\n const H = modP(B - a * A); // H = B-a*A\n const X3 = modP(E * F); // X3 = E*F\n const Y3 = modP(G * H); // Y3 = G*H\n const T3 = modP(E * H); // T3 = E*H\n const Z3 = modP(F * G); // Z3 = F*G\n\n return new Point(X3, Y3, Z3, T3);\n }\n\n subtract(other: Point): Point {\n return this.add(other.negate());\n }\n\n private wNAF(n: bigint): { p: Point; f: Point } {\n return wnaf.wNAFCached(this, n, Point.normalizeZ);\n }\n\n // Constant-time multiplication.\n multiply(scalar: bigint): Point {\n const n = scalar;\n ut.aInRange('scalar', n, _1n, CURVE_ORDER); // 1 <= scalar < L\n const { p, f } = this.wNAF(n);\n return Point.normalizeZ([p, f])[0];\n }\n\n // Non-constant-time multiplication. Uses double-and-add algorithm.\n // It's faster, but should only be used when you don't care about\n // an exposed private key e.g. sig verification.\n // Does NOT allow scalars higher than CURVE.n.\n // Accepts optional accumulator to merge with multiply (important for sparse scalars)\n multiplyUnsafe(scalar: bigint, acc = Point.ZERO): Point {\n const n = scalar;\n ut.aInRange('scalar', n, _0n, CURVE_ORDER); // 0 <= scalar < L\n if (n === _0n) return I;\n if (this.is0() || n === _1n) return this;\n return wnaf.wNAFCachedUnsafe(this, n, Point.normalizeZ, acc);\n }\n\n // Checks if point is of small order.\n // If you add something to small order point, you will have \"dirty\"\n // point with torsion component.\n // Multiplies point by cofactor and checks if the result is 0.\n isSmallOrder(): boolean {\n return this.multiplyUnsafe(cofactor).is0();\n }\n\n // Multiplies point by curve order and checks if the result is 0.\n // Returns `false` is the point is dirty.\n isTorsionFree(): boolean {\n return wnaf.unsafeLadder(this, CURVE_ORDER).is0();\n }\n\n // Converts Extended point to default (x, y) coordinates.\n // Can accept precomputed Z^-1 - for example, from invertBatch.\n toAffine(iz?: bigint): AffinePoint {\n return toAffineMemo(this, iz);\n }\n\n clearCofactor(): Point {\n const { h: cofactor } = CURVE;\n if (cofactor === _1n) return this;\n return this.multiplyUnsafe(cofactor);\n }\n\n // Converts hash string or Uint8Array to Point.\n // Uses algo from RFC8032 5.1.3.\n static fromHex(hex: Hex, zip215 = false): Point {\n const { d, a } = CURVE;\n const len = Fp.BYTES;\n hex = ensureBytes('pointHex', hex, len); // copy hex to a new array\n abool('zip215', zip215);\n const normed = hex.slice(); // copy again, we'll manipulate it\n const lastByte = hex[len - 1]; // select last byte\n normed[len - 1] = lastByte & ~0x80; // clear last bit\n const y = ut.bytesToNumberLE(normed);\n\n // zip215=true is good for consensus-critical apps. =false follows RFC8032 / NIST186-5.\n // RFC8032 prohibits >= p, but ZIP215 doesn't\n // zip215=true: 0 <= y < MASK (2^256 for ed25519)\n // zip215=false: 0 <= y < P (2^255-19 for ed25519)\n const max = zip215 ? MASK : Fp.ORDER;\n ut.aInRange('pointHex.y', y, _0n, max);\n\n // Ed25519: x² = (y²-1)/(dy²+1) mod p. Ed448: x² = (y²-1)/(dy²-1) mod p. Generic case:\n // ax²+y²=1+dx²y² => y²-1=dx²y²-ax² => y²-1=x²(dy²-a) => x²=(y²-1)/(dy²-a)\n const y2 = modP(y * y); // denominator is always non-0 mod p.\n const u = modP(y2 - _1n); // u = y² - 1\n const v = modP(d * y2 - a); // v = d y² + 1.\n let { isValid, value: x } = uvRatio(u, v); // √(u/v)\n if (!isValid) throw new Error('Point.fromHex: invalid y coordinate');\n const isXOdd = (x & _1n) === _1n; // There are 2 square roots. Use x_0 bit to select proper\n const isLastByteOdd = (lastByte & 0x80) !== 0; // x_0, last bit\n if (!zip215 && x === _0n && isLastByteOdd)\n // if x=0 and x_0 = 1, fail\n throw new Error('Point.fromHex: x=0 and x_0=1');\n if (isLastByteOdd !== isXOdd) x = modP(-x); // if x_0 != x mod 2, set x = p-x\n return Point.fromAffine({ x, y });\n }\n static fromPrivateKey(privKey: Hex) {\n return getExtendedPublicKey(privKey).point;\n }\n toRawBytes(): Uint8Array {\n const { x, y } = this.toAffine();\n const bytes = ut.numberToBytesLE(y, Fp.BYTES); // each y has 2 x values (x, -y)\n bytes[bytes.length - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y\n return bytes; // and use the last byte to encode sign of x\n }\n toHex(): string {\n return ut.bytesToHex(this.toRawBytes()); // Same as toRawBytes, but returns string.\n }\n }\n const { BASE: G, ZERO: I } = Point;\n const wnaf = wNAF(Point, nByteLength * 8);\n\n function modN(a: bigint) {\n return mod(a, CURVE_ORDER);\n }\n // Little-endian SHA512 with modulo n\n function modN_LE(hash: Uint8Array): bigint {\n return modN(ut.bytesToNumberLE(hash));\n }\n\n /** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */\n function getExtendedPublicKey(key: Hex) {\n const len = Fp.BYTES;\n key = ensureBytes('private key', key, len);\n // Hash private key with curve's hash function to produce uniformingly random input\n // Check byte lengths: ensure(64, h(ensure(32, key)))\n const hashed = ensureBytes('hashed private key', cHash(key), 2 * len);\n const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE\n const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6)\n const scalar = modN_LE(head); // The actual private scalar\n const point = G.multiply(scalar); // Point on Edwards curve aka public key\n const pointBytes = point.toRawBytes(); // Uint8Array representation\n return { head, prefix, scalar, point, pointBytes };\n }\n\n // Calculates EdDSA pub key. RFC8032 5.1.5. Privkey is hashed. Use first half with 3 bits cleared\n function getPublicKey(privKey: Hex): Uint8Array {\n return getExtendedPublicKey(privKey).pointBytes;\n }\n\n // int('LE', SHA512(dom2(F, C) || msgs)) mod N\n function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) {\n const msg = ut.concatBytes(...msgs);\n return modN_LE(cHash(domain(msg, ensureBytes('context', context), !!prehash)));\n }\n\n /** Signs message with privateKey. RFC8032 5.1.6 */\n function sign(msg: Hex, privKey: Hex, options: { context?: Hex } = {}): Uint8Array {\n msg = ensureBytes('message', msg);\n if (prehash) msg = prehash(msg); // for ed25519ph etc.\n const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey);\n const r = hashDomainToScalar(options.context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)\n const R = G.multiply(r).toRawBytes(); // R = rG\n const k = hashDomainToScalar(options.context, R, pointBytes, msg); // R || A || PH(M)\n const s = modN(r + k * scalar); // S = (r + k * s) mod L\n ut.aInRange('signature.s', s, _0n, CURVE_ORDER); // 0 <= s < l\n const res = ut.concatBytes(R, ut.numberToBytesLE(s, Fp.BYTES));\n return ensureBytes('result', res, Fp.BYTES * 2); // 64-byte signature\n }\n\n const verifyOpts: { context?: Hex; zip215?: boolean } = VERIFY_DEFAULT;\n\n /**\n * Verifies EdDSA signature against message and public key. RFC8032 5.1.7.\n * An extended group equation is checked.\n */\n function verify(sig: Hex, msg: Hex, publicKey: Hex, options = verifyOpts): boolean {\n const { context, zip215 } = options;\n const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7.\n sig = ensureBytes('signature', sig, 2 * len); // An extended group equation is checked.\n msg = ensureBytes('message', msg);\n publicKey = ensureBytes('publicKey', publicKey, len);\n if (zip215 !== undefined) abool('zip215', zip215);\n if (prehash) msg = prehash(msg); // for ed25519ph, etc\n\n const s = ut.bytesToNumberLE(sig.slice(len, 2 * len));\n let A, R, SB;\n try {\n // zip215=true is good for consensus-critical apps. =false follows RFC8032 / NIST186-5.\n // zip215=true: 0 <= y < MASK (2^256 for ed25519)\n // zip215=false: 0 <= y < P (2^255-19 for ed25519)\n A = Point.fromHex(publicKey, zip215);\n R = Point.fromHex(sig.slice(0, len), zip215);\n SB = G.multiplyUnsafe(s); // 0 <= s < l is done inside\n } catch (error) {\n return false;\n }\n if (!zip215 && A.isSmallOrder()) return false;\n\n const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);\n const RkA = R.add(A.multiplyUnsafe(k));\n // Extended group equation\n // [8][S]B = [8]R + [8][k]A'\n return RkA.subtract(SB).clearCofactor().equals(Point.ZERO);\n }\n\n G._setWindowSize(8); // Enable precomputes. Slows down first publicKey computation by 20ms.\n\n const utils = {\n getExtendedPublicKey,\n // ed25519 private keys are uniform 32b. No need to check for modulo bias, like in secp256k1.\n randomPrivateKey: (): Uint8Array => randomBytes(Fp.BYTES),\n\n /**\n * We're doing scalar multiplication (used in getPublicKey etc) with precomputed BASE_POINT\n * values. This slows down first getPublicKey() by milliseconds (see Speed section),\n * but allows to speed-up subsequent getPublicKey() calls up to 20x.\n * @param windowSize 2, 4, 8, 16\n */\n precompute(windowSize = 8, point: ExtPointType = Point.BASE): ExtPointType {\n point._setWindowSize(windowSize);\n point.multiply(BigInt(3));\n return point;\n },\n };\n\n return {\n CURVE,\n getPublicKey,\n sign,\n verify,\n ExtendedPoint: Point,\n utils,\n };\n}\n","/**\n * ed25519 Twisted Edwards curve with following addons:\n * - X25519 ECDH\n * - Ristretto cofactor elimination\n * - Elligator hash-to-group / point indistinguishability\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport { sha512 } from '@noble/hashes/sha512';\nimport { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';\nimport { type AffinePoint, type Group, pippenger } from './abstract/curve.js';\nimport { type CurveFn, type ExtPointType, twistedEdwards } from './abstract/edwards.js';\nimport {\n createHasher,\n expand_message_xmd,\n type htfBasicOpts,\n type HTFMethod,\n} from './abstract/hash-to-curve.js';\nimport { Field, FpSqrtEven, isNegativeLE, mod, pow2 } from './abstract/modular.js';\nimport { montgomery, type CurveFn as XCurveFn } from './abstract/montgomery.js';\nimport {\n bytesToHex,\n bytesToNumberLE,\n ensureBytes,\n equalBytes,\n type Hex,\n numberToBytesLE,\n} from './abstract/utils.js';\n\nconst ED25519_P = BigInt(\n '57896044618658097711785492504343953926634992332820282019728792003956564819949'\n);\n// √(-1) aka √(a) aka 2^((p-1)/4)\nconst ED25519_SQRT_M1 = /* @__PURE__ */ BigInt(\n '19681161376707505956807079304988542015446066515923890162744021073123829784752'\n);\n\n// prettier-ignore\nconst _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3);\n// prettier-ignore\nconst _5n = BigInt(5), _8n = BigInt(8);\n\nfunction ed25519_pow_2_252_3(x: bigint) {\n // prettier-ignore\n const _10n = BigInt(10), _20n = BigInt(20), _40n = BigInt(40), _80n = BigInt(80);\n const P = ED25519_P;\n const x2 = (x * x) % P;\n const b2 = (x2 * x) % P; // x^3, 11\n const b4 = (pow2(b2, _2n, P) * b2) % P; // x^15, 1111\n const b5 = (pow2(b4, _1n, P) * x) % P; // x^31\n const b10 = (pow2(b5, _5n, P) * b5) % P;\n const b20 = (pow2(b10, _10n, P) * b10) % P;\n const b40 = (pow2(b20, _20n, P) * b20) % P;\n const b80 = (pow2(b40, _40n, P) * b40) % P;\n const b160 = (pow2(b80, _80n, P) * b80) % P;\n const b240 = (pow2(b160, _80n, P) * b80) % P;\n const b250 = (pow2(b240, _10n, P) * b10) % P;\n const pow_p_5_8 = (pow2(b250, _2n, P) * x) % P;\n // ^ To pow to (p+3)/8, multiply it by x.\n return { pow_p_5_8, b2 };\n}\n\nfunction adjustScalarBytes(bytes: Uint8Array): Uint8Array {\n // Section 5: For X25519, in order to decode 32 random bytes as an integer scalar,\n // set the three least significant bits of the first byte\n bytes[0] &= 248; // 0b1111_1000\n // and the most significant bit of the last to zero,\n bytes[31] &= 127; // 0b0111_1111\n // set the second most significant bit of the last byte to 1\n bytes[31] |= 64; // 0b0100_0000\n return bytes;\n}\n\n// sqrt(u/v)\nfunction uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } {\n const P = ED25519_P;\n const v3 = mod(v * v * v, P); // v³\n const v7 = mod(v3 * v3 * v, P); // v⁷\n // (p+3)/8 and (p-5)/8\n const pow = ed25519_pow_2_252_3(u * v7).pow_p_5_8;\n let x = mod(u * v3 * pow, P); // (uv³)(uv⁷)^(p-5)/8\n const vx2 = mod(v * x * x, P); // vx²\n const root1 = x; // First root candidate\n const root2 = mod(x * ED25519_SQRT_M1, P); // Second root candidate\n const useRoot1 = vx2 === u; // If vx² = u (mod p), x is a square root\n const useRoot2 = vx2 === mod(-u, P); // If vx² = -u, set x <-- x * 2^((p-1)/4)\n const noRoot = vx2 === mod(-u * ED25519_SQRT_M1, P); // There is no valid root, vx² = -u√(-1)\n if (useRoot1) x = root1;\n if (useRoot2 || noRoot) x = root2; // We return root2 anyway, for const-time\n if (isNegativeLE(x, P)) x = mod(-x, P);\n return { isValid: useRoot1 || useRoot2, value: x };\n}\n\n// Just in case\nexport const ED25519_TORSION_SUBGROUP: string[] = [\n '0100000000000000000000000000000000000000000000000000000000000000',\n 'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a',\n '0000000000000000000000000000000000000000000000000000000000000080',\n '26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05',\n 'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',\n '26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85',\n '0000000000000000000000000000000000000000000000000000000000000000',\n 'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa',\n];\n\nconst Fp = /* @__PURE__ */ (() => Field(ED25519_P, undefined, true))();\n\nconst ed25519Defaults = /* @__PURE__ */ (() =>\n ({\n // Param: a\n a: BigInt(-1), // Fp.create(-1) is proper; our way still works and is faster\n // d is equal to -121665/121666 over finite field.\n // Negative number is P - number, and division is invert(number, P)\n d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'),\n // Finite field 𝔽p over which we'll do calculations; 2n**255n - 19n\n Fp,\n // Subgroup order: how many points curve has\n // 2n**252n + 27742317777372353535851937790883648493n;\n n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'),\n // Cofactor\n h: _8n,\n // Base point (x, y) aka generator point\n Gx: BigInt('15112221349535400772501151409588531511454012693041857206046113283949847762202'),\n Gy: BigInt('46316835694926478169428394003475163141307993866256225615783033603165251855960'),\n hash: sha512,\n randomBytes,\n adjustScalarBytes,\n // dom2\n // Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.\n // Constant-time, u/√v\n uvRatio,\n }) as const)();\n\n/**\n * ed25519 curve with EdDSA signatures.\n * @example\n * import { ed25519 } from '@noble/curves/ed25519';\n * const priv = ed25519.utils.randomPrivateKey();\n * const pub = ed25519.getPublicKey(priv);\n * const msg = new TextEncoder().encode('hello');\n * const sig = ed25519.sign(msg, priv);\n * ed25519.verify(sig, msg, pub); // Default mode: follows ZIP215\n * ed25519.verify(sig, msg, pub, { zip215: false }); // RFC8032 / FIPS 186-5\n */\nexport const ed25519: CurveFn = /* @__PURE__ */ (() => twistedEdwards(ed25519Defaults))();\n\nfunction ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {\n if (ctx.length > 255) throw new Error('Context is too big');\n return concatBytes(\n utf8ToBytes('SigEd25519 no Ed25519 collisions'),\n new Uint8Array([phflag ? 1 : 0, ctx.length]),\n ctx,\n data\n );\n}\n\nexport const ed25519ctx: CurveFn = /* @__PURE__ */ (() =>\n twistedEdwards({\n ...ed25519Defaults,\n domain: ed25519_domain,\n }))();\nexport const ed25519ph: CurveFn = /* @__PURE__ */ (() =>\n twistedEdwards(\n Object.assign({}, ed25519Defaults, {\n domain: ed25519_domain,\n prehash: sha512,\n })\n ))();\n\n/**\n * ECDH using curve25519 aka x25519.\n * @example\n * import { x25519 } from '@noble/curves/ed25519';\n * const priv = 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4';\n * const pub = 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c';\n * x25519.getSharedSecret(priv, pub) === x25519.scalarMult(priv, pub); // aliases\n * x25519.getPublicKey(priv) === x25519.scalarMultBase(priv);\n * x25519.getPublicKey(x25519.utils.randomPrivateKey());\n */\nexport const x25519: XCurveFn = /* @__PURE__ */ (() =>\n montgomery({\n P: ED25519_P,\n a: BigInt(486662),\n montgomeryBits: 255, // n is 253 bits\n nByteLength: 32,\n Gu: BigInt(9),\n powPminus2: (x: bigint): bigint => {\n const P = ED25519_P;\n // x^(p-2) aka x^(2^255-21)\n const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x);\n return mod(pow2(pow_p_5_8, _3n, P) * b2, P);\n },\n adjustScalarBytes,\n randomBytes,\n }))();\n\n/**\n * Converts ed25519 public key to x25519 public key. Uses formula:\n * * `(u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x)`\n * * `(x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))`\n * @example\n * const someonesPub = ed25519.getPublicKey(ed25519.utils.randomPrivateKey());\n * const aPriv = x25519.utils.randomPrivateKey();\n * x25519.getSharedSecret(aPriv, edwardsToMontgomeryPub(someonesPub))\n */\nexport function edwardsToMontgomeryPub(edwardsPub: Hex): Uint8Array {\n const { y } = ed25519.ExtendedPoint.fromHex(edwardsPub);\n const _1n = BigInt(1);\n return Fp.toBytes(Fp.create((_1n + y) * Fp.inv(_1n - y)));\n}\nexport const edwardsToMontgomery: typeof edwardsToMontgomeryPub = edwardsToMontgomeryPub; // deprecated\n\n/**\n * Converts ed25519 secret key to x25519 secret key.\n * @example\n * const someonesPub = x25519.getPublicKey(x25519.utils.randomPrivateKey());\n * const aPriv = ed25519.utils.randomPrivateKey();\n * x25519.getSharedSecret(edwardsToMontgomeryPriv(aPriv), someonesPub)\n */\nexport function edwardsToMontgomeryPriv(edwardsPriv: Uint8Array): Uint8Array {\n const hashed = ed25519Defaults.hash(edwardsPriv.subarray(0, 32));\n return ed25519Defaults.adjustScalarBytes(hashed).subarray(0, 32);\n}\n\n// Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator)\n// NOTE: very important part is usage of FpSqrtEven for ELL2_C1_EDWARDS, since\n// SageMath returns different root first and everything falls apart\n\nconst ELL2_C1 = /* @__PURE__ */ (() => (Fp.ORDER + _3n) / _8n)(); // 1. c1 = (q + 3) / 8 # Integer arithmetic\nconst ELL2_C2 = /* @__PURE__ */ (() => Fp.pow(_2n, ELL2_C1))(); // 2. c2 = 2^c1\nconst ELL2_C3 = /* @__PURE__ */ (() => Fp.sqrt(Fp.neg(Fp.ONE)))(); // 3. c3 = sqrt(-1)\n\n// prettier-ignore\nfunction map_to_curve_elligator2_curve25519(u: bigint) {\n const ELL2_C4 = (Fp.ORDER - _5n) / _8n; // 4. c4 = (q - 5) / 8 # Integer arithmetic\n const ELL2_J = BigInt(486662);\n\n let tv1 = Fp.sqr(u); // 1. tv1 = u^2\n tv1 = Fp.mul(tv1, _2n); // 2. tv1 = 2 * tv1\n let xd = Fp.add(tv1, Fp.ONE); // 3. xd = tv1 + 1 # Nonzero: -1 is square (mod p), tv1 is not\n let x1n = Fp.neg(ELL2_J); // 4. x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2)\n let tv2 = Fp.sqr(xd); // 5. tv2 = xd^2\n let gxd = Fp.mul(tv2, xd); // 6. gxd = tv2 * xd # gxd = xd^3\n let gx1 = Fp.mul(tv1, ELL2_J);// 7. gx1 = J * tv1 # x1n + J * xd\n gx1 = Fp.mul(gx1, x1n); // 8. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd\n gx1 = Fp.add(gx1, tv2); // 9. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2\n gx1 = Fp.mul(gx1, x1n); // 10. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2\n let tv3 = Fp.sqr(gxd); // 11. tv3 = gxd^2\n tv2 = Fp.sqr(tv3); // 12. tv2 = tv3^2 # gxd^4\n tv3 = Fp.mul(tv3, gxd); // 13. tv3 = tv3 * gxd # gxd^3\n tv3 = Fp.mul(tv3, gx1); // 14. tv3 = tv3 * gx1 # gx1 * gxd^3\n tv2 = Fp.mul(tv2, tv3); // 15. tv2 = tv2 * tv3 # gx1 * gxd^7\n let y11 = Fp.pow(tv2, ELL2_C4); // 16. y11 = tv2^c4 # (gx1 * gxd^7)^((p - 5) / 8)\n y11 = Fp.mul(y11, tv3); // 17. y11 = y11 * tv3 # gx1*gxd^3*(gx1*gxd^7)^((p-5)/8)\n let y12 = Fp.mul(y11, ELL2_C3); // 18. y12 = y11 * c3\n tv2 = Fp.sqr(y11); // 19. tv2 = y11^2\n tv2 = Fp.mul(tv2, gxd); // 20. tv2 = tv2 * gxd\n let e1 = Fp.eql(tv2, gx1); // 21. e1 = tv2 == gx1\n let y1 = Fp.cmov(y12, y11, e1); // 22. y1 = CMOV(y12, y11, e1) # If g(x1) is square, this is its sqrt\n let x2n = Fp.mul(x1n, tv1); // 23. x2n = x1n * tv1 # x2 = x2n / xd = 2 * u^2 * x1n / xd\n let y21 = Fp.mul(y11, u); // 24. y21 = y11 * u\n y21 = Fp.mul(y21, ELL2_C2); // 25. y21 = y21 * c2\n let y22 = Fp.mul(y21, ELL2_C3); // 26. y22 = y21 * c3\n let gx2 = Fp.mul(gx1, tv1); // 27. gx2 = gx1 * tv1 # g(x2) = gx2 / gxd = 2 * u^2 * g(x1)\n tv2 = Fp.sqr(y21); // 28. tv2 = y21^2\n tv2 = Fp.mul(tv2, gxd); // 29. tv2 = tv2 * gxd\n let e2 = Fp.eql(tv2, gx2); // 30. e2 = tv2 == gx2\n let y2 = Fp.cmov(y22, y21, e2); // 31. y2 = CMOV(y22, y21, e2) # If g(x2) is square, this is its sqrt\n tv2 = Fp.sqr(y1); // 32. tv2 = y1^2\n tv2 = Fp.mul(tv2, gxd); // 33. tv2 = tv2 * gxd\n let e3 = Fp.eql(tv2, gx1); // 34. e3 = tv2 == gx1\n let xn = Fp.cmov(x2n, x1n, e3); // 35. xn = CMOV(x2n, x1n, e3) # If e3, x = x1, else x = x2\n let y = Fp.cmov(y2, y1, e3); // 36. y = CMOV(y2, y1, e3) # If e3, y = y1, else y = y2\n let e4 = Fp.isOdd(y); // 37. e4 = sgn0(y) == 1 # Fix sign of y\n y = Fp.cmov(y, Fp.neg(y), e3 !== e4); // 38. y = CMOV(y, -y, e3 XOR e4)\n return { xMn: xn, xMd: xd, yMn: y, yMd: _1n }; // 39. return (xn, xd, y, 1)\n}\n\nconst ELL2_C1_EDWARDS = /* @__PURE__ */ (() => FpSqrtEven(Fp, Fp.neg(BigInt(486664))))(); // sgn0(c1) MUST equal 0\nfunction map_to_curve_elligator2_edwards25519(u: bigint) {\n const { xMn, xMd, yMn, yMd } = map_to_curve_elligator2_curve25519(u); // 1. (xMn, xMd, yMn, yMd) =\n // map_to_curve_elligator2_curve25519(u)\n let xn = Fp.mul(xMn, yMd); // 2. xn = xMn * yMd\n xn = Fp.mul(xn, ELL2_C1_EDWARDS); // 3. xn = xn * c1\n let xd = Fp.mul(xMd, yMn); // 4. xd = xMd * yMn # xn / xd = c1 * xM / yM\n let yn = Fp.sub(xMn, xMd); // 5. yn = xMn - xMd\n let yd = Fp.add(xMn, xMd); // 6. yd = xMn + xMd # (n / d - 1) / (n / d + 1) = (n - d) / (n + d)\n let tv1 = Fp.mul(xd, yd); // 7. tv1 = xd * yd\n let e = Fp.eql(tv1, Fp.ZERO); // 8. e = tv1 == 0\n xn = Fp.cmov(xn, Fp.ZERO, e); // 9. xn = CMOV(xn, 0, e)\n xd = Fp.cmov(xd, Fp.ONE, e); // 10. xd = CMOV(xd, 1, e)\n yn = Fp.cmov(yn, Fp.ONE, e); // 11. yn = CMOV(yn, 1, e)\n yd = Fp.cmov(yd, Fp.ONE, e); // 12. yd = CMOV(yd, 1, e)\n\n const inv = Fp.invertBatch([xd, yd]); // batch division\n return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd)\n}\n\nconst htf = /* @__PURE__ */ (() =>\n createHasher(\n ed25519.ExtendedPoint,\n (scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),\n {\n DST: 'edwards25519_XMD:SHA-512_ELL2_RO_',\n encodeDST: 'edwards25519_XMD:SHA-512_ELL2_NU_',\n p: Fp.ORDER,\n m: 1,\n k: 128,\n expand: 'xmd',\n hash: sha512,\n }\n ))();\nexport const hashToCurve: HTFMethod = /* @__PURE__ */ (() => htf.hashToCurve)();\nexport const encodeToCurve: HTFMethod = /* @__PURE__ */ (() => htf.encodeToCurve)();\n\nfunction assertRstPoint(other: unknown) {\n if (!(other instanceof RistPoint)) throw new Error('RistrettoPoint expected');\n}\n\n// √(-1) aka √(a) aka 2^((p-1)/4)\nconst SQRT_M1 = ED25519_SQRT_M1;\n// √(ad - 1)\nconst SQRT_AD_MINUS_ONE = /* @__PURE__ */ BigInt(\n '25063068953384623474111414158702152701244531502492656460079210482610430750235'\n);\n// 1 / √(a-d)\nconst INVSQRT_A_MINUS_D = /* @__PURE__ */ BigInt(\n '54469307008909316920995813868745141605393597292927456921205312896311721017578'\n);\n// 1-d²\nconst ONE_MINUS_D_SQ = /* @__PURE__ */ BigInt(\n '1159843021668779879193775521855586647937357759715417654439879720876111806838'\n);\n// (d-1)²\nconst D_MINUS_ONE_SQ = /* @__PURE__ */ BigInt(\n '40440834346308536858101042469323190826248399146238708352240133220865137265952'\n);\n// Calculates 1/√(number)\nconst invertSqrt = (number: bigint) => uvRatio(_1n, number);\n\nconst MAX_255B = /* @__PURE__ */ BigInt(\n '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'\n);\nconst bytes255ToNumberLE = (bytes: Uint8Array) =>\n ed25519.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B);\n\ntype ExtendedPoint = ExtPointType;\n\n// Computes Elligator map for Ristretto\n// https://ristretto.group/formulas/elligator.html\nfunction calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {\n const { d } = ed25519.CURVE;\n const P = ed25519.CURVE.Fp.ORDER;\n const mod = ed25519.CURVE.Fp.create;\n const r = mod(SQRT_M1 * r0 * r0); // 1\n const Ns = mod((r + _1n) * ONE_MINUS_D_SQ); // 2\n let c = BigInt(-1); // 3\n const D = mod((c - d * r) * mod(r + d)); // 4\n let { isValid: Ns_D_is_sq, value: s } = uvRatio(Ns, D); // 5\n let s_ = mod(s * r0); // 6\n if (!isNegativeLE(s_, P)) s_ = mod(-s_);\n if (!Ns_D_is_sq) s = s_; // 7\n if (!Ns_D_is_sq) c = r; // 8\n const Nt = mod(c * (r - _1n) * D_MINUS_ONE_SQ - D); // 9\n const s2 = s * s;\n const W0 = mod((s + s) * D); // 10\n const W1 = mod(Nt * SQRT_AD_MINUS_ONE); // 11\n const W2 = mod(_1n - s2); // 12\n const W3 = mod(_1n + s2); // 13\n return new ed25519.ExtendedPoint(mod(W0 * W3), mod(W2 * W1), mod(W1 * W3), mod(W0 * W2));\n}\n\n/**\n * Each ed25519/ExtendedPoint has 8 different equivalent points. This can be\n * a source of bugs for protocols like ring signatures. Ristretto was created to solve this.\n * Ristretto point operates in X:Y:Z:T extended coordinates like ExtendedPoint,\n * but it should work in its own namespace: do not combine those two.\n * https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448\n */\nclass RistPoint implements Group {\n static BASE: RistPoint;\n static ZERO: RistPoint;\n // Private property to discourage combining ExtendedPoint + RistrettoPoint\n // Always use Ristretto encoding/decoding instead.\n constructor(private readonly ep: ExtendedPoint) {}\n\n static fromAffine(ap: AffinePoint): RistPoint {\n return new RistPoint(ed25519.ExtendedPoint.fromAffine(ap));\n }\n\n /**\n * Takes uniform output of 64-byte hash function like sha512 and converts it to `RistrettoPoint`.\n * The hash-to-group operation applies Elligator twice and adds the results.\n * **Note:** this is one-way map, there is no conversion from point to hash.\n * https://ristretto.group/formulas/elligator.html\n * @param hex 64-byte output of a hash function\n */\n static hashToCurve(hex: Hex): RistPoint {\n hex = ensureBytes('ristrettoHash', hex, 64);\n const r1 = bytes255ToNumberLE(hex.slice(0, 32));\n const R1 = calcElligatorRistrettoMap(r1);\n const r2 = bytes255ToNumberLE(hex.slice(32, 64));\n const R2 = calcElligatorRistrettoMap(r2);\n return new RistPoint(R1.add(R2));\n }\n\n /**\n * Converts ristretto-encoded string to ristretto point.\n * https://ristretto.group/formulas/decoding.html\n * @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding\n */\n static fromHex(hex: Hex): RistPoint {\n hex = ensureBytes('ristrettoHex', hex, 32);\n const { a, d } = ed25519.CURVE;\n const P = ed25519.CURVE.Fp.ORDER;\n const mod = ed25519.CURVE.Fp.create;\n const emsg = 'RistrettoPoint.fromHex: the hex is not valid encoding of RistrettoPoint';\n const s = bytes255ToNumberLE(hex);\n // 1. Check that s_bytes is the canonical encoding of a field element, or else abort.\n // 3. Check that s is non-negative, or else abort\n if (!equalBytes(numberToBytesLE(s, 32), hex) || isNegativeLE(s, P)) throw new Error(emsg);\n const s2 = mod(s * s);\n const u1 = mod(_1n + a * s2); // 4 (a is -1)\n const u2 = mod(_1n - a * s2); // 5\n const u1_2 = mod(u1 * u1);\n const u2_2 = mod(u2 * u2);\n const v = mod(a * d * u1_2 - u2_2); // 6\n const { isValid, value: I } = invertSqrt(mod(v * u2_2)); // 7\n const Dx = mod(I * u2); // 8\n const Dy = mod(I * Dx * v); // 9\n let x = mod((s + s) * Dx); // 10\n if (isNegativeLE(x, P)) x = mod(-x); // 10\n const y = mod(u1 * Dy); // 11\n const t = mod(x * y); // 12\n if (!isValid || isNegativeLE(t, P) || y === _0n) throw new Error(emsg);\n return new RistPoint(new ed25519.ExtendedPoint(x, y, _1n, t));\n }\n\n static msm(points: RistPoint[], scalars: bigint[]): RistPoint {\n const Fn = Field(ed25519.CURVE.n, ed25519.CURVE.nBitLength);\n return pippenger(RistPoint, Fn, points, scalars);\n }\n\n /**\n * Encodes ristretto point to Uint8Array.\n * https://ristretto.group/formulas/encoding.html\n */\n toRawBytes(): Uint8Array {\n let { ex: x, ey: y, ez: z, et: t } = this.ep;\n const P = ed25519.CURVE.Fp.ORDER;\n const mod = ed25519.CURVE.Fp.create;\n const u1 = mod(mod(z + y) * mod(z - y)); // 1\n const u2 = mod(x * y); // 2\n // Square root always exists\n const u2sq = mod(u2 * u2);\n const { value: invsqrt } = invertSqrt(mod(u1 * u2sq)); // 3\n const D1 = mod(invsqrt * u1); // 4\n const D2 = mod(invsqrt * u2); // 5\n const zInv = mod(D1 * D2 * t); // 6\n let D: bigint; // 7\n if (isNegativeLE(t * zInv, P)) {\n let _x = mod(y * SQRT_M1);\n let _y = mod(x * SQRT_M1);\n x = _x;\n y = _y;\n D = mod(D1 * INVSQRT_A_MINUS_D);\n } else {\n D = D2; // 8\n }\n if (isNegativeLE(x * zInv, P)) y = mod(-y); // 9\n let s = mod((z - y) * D); // 10 (check footer's note, no sqrt(-a))\n if (isNegativeLE(s, P)) s = mod(-s);\n return numberToBytesLE(s, 32); // 11\n }\n\n toHex(): string {\n return bytesToHex(this.toRawBytes());\n }\n\n toString(): string {\n return this.toHex();\n }\n\n // Compare one point to another.\n equals(other: RistPoint): boolean {\n assertRstPoint(other);\n const { ex: X1, ey: Y1 } = this.ep;\n const { ex: X2, ey: Y2 } = other.ep;\n const mod = ed25519.CURVE.Fp.create;\n // (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)\n const one = mod(X1 * Y2) === mod(Y1 * X2);\n const two = mod(Y1 * Y2) === mod(X1 * X2);\n return one || two;\n }\n\n add(other: RistPoint): RistPoint {\n assertRstPoint(other);\n return new RistPoint(this.ep.add(other.ep));\n }\n\n subtract(other: RistPoint): RistPoint {\n assertRstPoint(other);\n return new RistPoint(this.ep.subtract(other.ep));\n }\n\n multiply(scalar: bigint): RistPoint {\n return new RistPoint(this.ep.multiply(scalar));\n }\n\n multiplyUnsafe(scalar: bigint): RistPoint {\n return new RistPoint(this.ep.multiplyUnsafe(scalar));\n }\n\n double(): RistPoint {\n return new RistPoint(this.ep.double());\n }\n\n negate(): RistPoint {\n return new RistPoint(this.ep.negate());\n }\n}\nexport const RistrettoPoint: typeof RistPoint = /* @__PURE__ */ (() => {\n if (!RistPoint.BASE) RistPoint.BASE = new RistPoint(ed25519.ExtendedPoint.BASE);\n if (!RistPoint.ZERO) RistPoint.ZERO = new RistPoint(ed25519.ExtendedPoint.ZERO);\n return RistPoint;\n})();\n\n// Hashing to ristretto255. https://www.rfc-editor.org/rfc/rfc9380#appendix-B\nexport const hashToRistretto255 = (msg: Uint8Array, options: htfBasicOpts): RistPoint => {\n const d = options.DST;\n const DST = typeof d === 'string' ? utf8ToBytes(d) : d;\n const uniform_bytes = expand_message_xmd(msg, DST, 64, sha512);\n const P = RistPoint.hashToCurve(uniform_bytes);\n return P;\n};\nexport const hash_to_ristretto255: (msg: Uint8Array, options: htfBasicOpts) => RistPoint =\n hashToRistretto255; // legacy\n","/**\n * SHA2-256 a.k.a. sha256. In JS, it is the fastest hash, even faster than Blake3.\n *\n * To break sha256 using birthday attack, attackers need to try 2^128 hashes.\n * BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.\n *\n * Check out [FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).\n * @module\n */\nimport { Chi, HashMD, Maj } from './_md.js';\nimport { type CHash, rotr, wrapConstructor } from './utils.js';\n\n/** Round constants: first 32 bits of fractional parts of the cube roots of the first 64 primes 2..311). */\n// prettier-ignore\nconst SHA256_K = /* @__PURE__ */ new Uint32Array([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2\n]);\n\n/** Initial state: first 32 bits of fractional parts of the square roots of the first 8 primes 2..19. */\n// prettier-ignore\nconst SHA256_IV = /* @__PURE__ */ new Uint32Array([\n 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19\n]);\n\n/**\n * Temporary buffer, not used to store anything between runs.\n * Named this way because it matches specification.\n */\nconst SHA256_W = /* @__PURE__ */ new Uint32Array(64);\nexport class SHA256 extends HashMD {\n // We cannot use array here since array allows indexing by variable\n // which means optimizer/compiler cannot use registers.\n protected A: number = SHA256_IV[0] | 0;\n protected B: number = SHA256_IV[1] | 0;\n protected C: number = SHA256_IV[2] | 0;\n protected D: number = SHA256_IV[3] | 0;\n protected E: number = SHA256_IV[4] | 0;\n protected F: number = SHA256_IV[5] | 0;\n protected G: number = SHA256_IV[6] | 0;\n protected H: number = SHA256_IV[7] | 0;\n\n constructor() {\n super(64, 32, 8, false);\n }\n protected get(): [number, number, number, number, number, number, number, number] {\n const { A, B, C, D, E, F, G, H } = this;\n return [A, B, C, D, E, F, G, H];\n }\n // prettier-ignore\n protected set(\n A: number, B: number, C: number, D: number, E: number, F: number, G: number, H: number\n ): void {\n this.A = A | 0;\n this.B = B | 0;\n this.C = C | 0;\n this.D = D | 0;\n this.E = E | 0;\n this.F = F | 0;\n this.G = G | 0;\n this.H = H | 0;\n }\n protected process(view: DataView, offset: number): void {\n // Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array\n for (let i = 0; i < 16; i++, offset += 4) SHA256_W[i] = view.getUint32(offset, false);\n for (let i = 16; i < 64; i++) {\n const W15 = SHA256_W[i - 15];\n const W2 = SHA256_W[i - 2];\n const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ (W15 >>> 3);\n const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ (W2 >>> 10);\n SHA256_W[i] = (s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16]) | 0;\n }\n // Compression function main loop, 64 rounds\n let { A, B, C, D, E, F, G, H } = this;\n for (let i = 0; i < 64; i++) {\n const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);\n const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;\n const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);\n const T2 = (sigma0 + Maj(A, B, C)) | 0;\n H = G;\n G = F;\n F = E;\n E = (D + T1) | 0;\n D = C;\n C = B;\n B = A;\n A = (T1 + T2) | 0;\n }\n // Add the compressed chunk to the current hash value\n A = (A + this.A) | 0;\n B = (B + this.B) | 0;\n C = (C + this.C) | 0;\n D = (D + this.D) | 0;\n E = (E + this.E) | 0;\n F = (F + this.F) | 0;\n G = (G + this.G) | 0;\n H = (H + this.H) | 0;\n this.set(A, B, C, D, E, F, G, H);\n }\n protected roundClean(): void {\n SHA256_W.fill(0);\n }\n destroy(): void {\n this.set(0, 0, 0, 0, 0, 0, 0, 0);\n this.buffer.fill(0);\n }\n}\n\n/**\n * Constants taken from https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf.\n */\nclass SHA224 extends SHA256 {\n protected A = 0xc1059ed8 | 0;\n protected B = 0x367cd507 | 0;\n protected C = 0x3070dd17 | 0;\n protected D = 0xf70e5939 | 0;\n protected E = 0xffc00b31 | 0;\n protected F = 0x68581511 | 0;\n protected G = 0x64f98fa7 | 0;\n protected H = 0xbefa4fa4 | 0;\n constructor() {\n super();\n this.outputLen = 28;\n }\n}\n\n/** SHA2-256 hash function */\nexport const sha256: CHash = /* @__PURE__ */ wrapConstructor(() => new SHA256());\n/** SHA2-224 hash function */\nexport const sha224: CHash = /* @__PURE__ */ wrapConstructor(() => new SHA224());\n","// @ts-check\n\n/**\n * Base58 characters include numbers 123456789, uppercase ABCDEFGHJKLMNPQRSTUVWXYZ and lowercase abcdefghijkmnopqrstuvwxyz.\n * @typedef {String} base58_chars Base58 characters include numbers 123456789, uppercase ABCDEFGHJKLMNPQRSTUVWXYZ and lowercase abcdefghijkmnopqrstuvwxyz.\n */\nconst base58_chars =\n \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nexport default base58_chars;\n","import base58_chars from \"./base58_chars.js\";\n\n/**\n * Converts a base58 string to the corresponding binary representation.\n * @param { import(\"./base58_chars.js\").base58_chars } base58String base58 encoded string.\n * @returns {Uint8Array} Binary representation for the base58 string.\n * @example\n * ```js\n * const bin = base58_to_binary(\"6MRy\")\n * console.log(bin)\n * ```\n * Logged output will be Uint8Array(3) [15, 239, 64].\n */\nfunction base58_to_binary(base58String) {\n if (!base58String || typeof base58String !== \"string\")\n throw new Error(`Expected base58 string but got “${base58String}”`);\n if (base58String.match(/[IOl0]/gmu))\n throw new Error(\n `Invalid base58 character “${base58String.match(/[IOl0]/gmu)}”`\n );\n const lz = base58String.match(/^1+/gmu);\n const psz = lz ? lz[0].length : 0;\n const size =\n ((base58String.length - psz) * (Math.log(58) / Math.log(256)) + 1) >>> 0;\n\n return new Uint8Array([\n ...new Uint8Array(psz),\n ...base58String\n .match(/.{1}/gmu)\n .map((i) => base58_chars.indexOf(i))\n .reduce((acc, i) => {\n acc = acc.map((j) => {\n const x = j * 58 + i;\n i = x >> 8;\n return x;\n });\n return acc;\n }, new Uint8Array(size))\n .reverse()\n .filter(\n (\n (lastValue) => (value) =>\n // @ts-ignore\n (lastValue = lastValue || value)\n )(false)\n ),\n ]);\n}\n\nexport default base58_to_binary;\n","// @ts-check\n\nimport base58_chars from \"./base58_chars.js\";\n\n/**\n * Generates a mapping between base58 and ascii.\n * @returns {Array} mapping between ascii and base58.\n */\nconst create_base58_map = () => {\n const base58M = Array(256).fill(-1);\n for (let i = 0; i < base58_chars.length; ++i)\n base58M[base58_chars.charCodeAt(i)] = i;\n\n return base58M;\n};\n\nexport default create_base58_map;\n","// @ts-check\n\nimport base58_chars from \"./base58_chars.js\";\nimport create_base58_map from \"./create_base58_map.js\";\n\nconst base58Map = create_base58_map();\n\n/** @typedef {import(\"./base58_chars.js\").base58_chars} base58_chars */\n\n/**\n * Converts a Uint8Array into a base58 string.\n * @param {Uint8Array} uint8array Unsigned integer array.\n * @returns { import(\"./base58_chars.js\").base58_chars } base58 string representation of the binary array.\n * @example Usage.\n * ```js\n * const str = binary_to_base58([15, 239, 64])\n * console.log(str)\n * ```\n * Logged output will be 6MRy.\n */\nfunction binary_to_base58(uint8array) {\n const result = [];\n\n for (const byte of uint8array) {\n let carry = byte;\n for (let j = 0; j < result.length; ++j) {\n // @ts-ignore\n const x = (base58Map[result[j]] << 8) + carry;\n result[j] = base58_chars.charCodeAt(x % 58);\n carry = (x / 58) | 0;\n }\n while (carry) {\n result.push(base58_chars.charCodeAt(carry % 58));\n carry = (carry / 58) | 0;\n }\n }\n\n for (const byte of uint8array)\n if (byte) break;\n else result.push(\"1\".charCodeAt(0));\n\n result.reverse();\n\n return String.fromCharCode(...result);\n}\n\nexport default binary_to_base58;\n","/**\n * base64.ts\n *\n * Licensed under the BSD 3-Clause License.\n * http://opensource.org/licenses/BSD-3-Clause\n *\n * References:\n * http://en.wikipedia.org/wiki/Base64\n *\n * @author Dan Kogai (https://github.com/dankogai)\n */\nconst version = '3.7.7';\n/**\n * @deprecated use lowercase `version`.\n */\nconst VERSION = version;\nconst _hasBuffer = typeof Buffer === 'function';\nconst _TD = typeof TextDecoder === 'function' ? new TextDecoder() : undefined;\nconst _TE = typeof TextEncoder === 'function' ? new TextEncoder() : undefined;\nconst b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';\nconst b64chs = Array.prototype.slice.call(b64ch);\nconst b64tab = ((a) => {\n let tab = {};\n a.forEach((c, i) => tab[c] = i);\n return tab;\n})(b64chs);\nconst b64re = /^(?:[A-Za-z\\d+\\/]{4})*?(?:[A-Za-z\\d+\\/]{2}(?:==)?|[A-Za-z\\d+\\/]{3}=?)?$/;\nconst _fromCC = String.fromCharCode.bind(String);\nconst _U8Afrom = typeof Uint8Array.from === 'function'\n ? Uint8Array.from.bind(Uint8Array)\n : (it) => new Uint8Array(Array.prototype.slice.call(it, 0));\nconst _mkUriSafe = (src) => src\n .replace(/=/g, '').replace(/[+\\/]/g, (m0) => m0 == '+' ? '-' : '_');\nconst _tidyB64 = (s) => s.replace(/[^A-Za-z0-9\\+\\/]/g, '');\n/**\n * polyfill version of `btoa`\n */\nconst btoaPolyfill = (bin) => {\n // console.log('polyfilled');\n let u32, c0, c1, c2, asc = '';\n const pad = bin.length % 3;\n for (let i = 0; i < bin.length;) {\n if ((c0 = bin.charCodeAt(i++)) > 255 ||\n (c1 = bin.charCodeAt(i++)) > 255 ||\n (c2 = bin.charCodeAt(i++)) > 255)\n throw new TypeError('invalid character found');\n u32 = (c0 << 16) | (c1 << 8) | c2;\n asc += b64chs[u32 >> 18 & 63]\n + b64chs[u32 >> 12 & 63]\n + b64chs[u32 >> 6 & 63]\n + b64chs[u32 & 63];\n }\n return pad ? asc.slice(0, pad - 3) + \"===\".substring(pad) : asc;\n};\n/**\n * does what `window.btoa` of web browsers do.\n * @param {String} bin binary string\n * @returns {string} Base64-encoded string\n */\nconst _btoa = typeof btoa === 'function' ? (bin) => btoa(bin)\n : _hasBuffer ? (bin) => Buffer.from(bin, 'binary').toString('base64')\n : btoaPolyfill;\nconst _fromUint8Array = _hasBuffer\n ? (u8a) => Buffer.from(u8a).toString('base64')\n : (u8a) => {\n // cf. https://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string/12713326#12713326\n const maxargs = 0x1000;\n let strs = [];\n for (let i = 0, l = u8a.length; i < l; i += maxargs) {\n strs.push(_fromCC.apply(null, u8a.subarray(i, i + maxargs)));\n }\n return _btoa(strs.join(''));\n };\n/**\n * converts a Uint8Array to a Base64 string.\n * @param {boolean} [urlsafe] URL-and-filename-safe a la RFC4648 §5\n * @returns {string} Base64 string\n */\nconst fromUint8Array = (u8a, urlsafe = false) => urlsafe ? _mkUriSafe(_fromUint8Array(u8a)) : _fromUint8Array(u8a);\n// This trick is found broken https://github.com/dankogai/js-base64/issues/130\n// const utob = (src: string) => unescape(encodeURIComponent(src));\n// reverting good old fationed regexp\nconst cb_utob = (c) => {\n if (c.length < 2) {\n var cc = c.charCodeAt(0);\n return cc < 0x80 ? c\n : cc < 0x800 ? (_fromCC(0xc0 | (cc >>> 6))\n + _fromCC(0x80 | (cc & 0x3f)))\n : (_fromCC(0xe0 | ((cc >>> 12) & 0x0f))\n + _fromCC(0x80 | ((cc >>> 6) & 0x3f))\n + _fromCC(0x80 | (cc & 0x3f)));\n }\n else {\n var cc = 0x10000\n + (c.charCodeAt(0) - 0xD800) * 0x400\n + (c.charCodeAt(1) - 0xDC00);\n return (_fromCC(0xf0 | ((cc >>> 18) & 0x07))\n + _fromCC(0x80 | ((cc >>> 12) & 0x3f))\n + _fromCC(0x80 | ((cc >>> 6) & 0x3f))\n + _fromCC(0x80 | (cc & 0x3f)));\n }\n};\nconst re_utob = /[\\uD800-\\uDBFF][\\uDC00-\\uDFFFF]|[^\\x00-\\x7F]/g;\n/**\n * @deprecated should have been internal use only.\n * @param {string} src UTF-8 string\n * @returns {string} UTF-16 string\n */\nconst utob = (u) => u.replace(re_utob, cb_utob);\n//\nconst _encode = _hasBuffer\n ? (s) => Buffer.from(s, 'utf8').toString('base64')\n : _TE\n ? (s) => _fromUint8Array(_TE.encode(s))\n : (s) => _btoa(utob(s));\n/**\n * converts a UTF-8-encoded string to a Base64 string.\n * @param {boolean} [urlsafe] if `true` make the result URL-safe\n * @returns {string} Base64 string\n */\nconst encode = (src, urlsafe = false) => urlsafe\n ? _mkUriSafe(_encode(src))\n : _encode(src);\n/**\n * converts a UTF-8-encoded string to URL-safe Base64 RFC4648 §5.\n * @returns {string} Base64 string\n */\nconst encodeURI = (src) => encode(src, true);\n// This trick is found broken https://github.com/dankogai/js-base64/issues/130\n// const btou = (src: string) => decodeURIComponent(escape(src));\n// reverting good old fationed regexp\nconst re_btou = /[\\xC0-\\xDF][\\x80-\\xBF]|[\\xE0-\\xEF][\\x80-\\xBF]{2}|[\\xF0-\\xF7][\\x80-\\xBF]{3}/g;\nconst cb_btou = (cccc) => {\n switch (cccc.length) {\n case 4:\n var cp = ((0x07 & cccc.charCodeAt(0)) << 18)\n | ((0x3f & cccc.charCodeAt(1)) << 12)\n | ((0x3f & cccc.charCodeAt(2)) << 6)\n | (0x3f & cccc.charCodeAt(3)), offset = cp - 0x10000;\n return (_fromCC((offset >>> 10) + 0xD800)\n + _fromCC((offset & 0x3FF) + 0xDC00));\n case 3:\n return _fromCC(((0x0f & cccc.charCodeAt(0)) << 12)\n | ((0x3f & cccc.charCodeAt(1)) << 6)\n | (0x3f & cccc.charCodeAt(2)));\n default:\n return _fromCC(((0x1f & cccc.charCodeAt(0)) << 6)\n | (0x3f & cccc.charCodeAt(1)));\n }\n};\n/**\n * @deprecated should have been internal use only.\n * @param {string} src UTF-16 string\n * @returns {string} UTF-8 string\n */\nconst btou = (b) => b.replace(re_btou, cb_btou);\n/**\n * polyfill version of `atob`\n */\nconst atobPolyfill = (asc) => {\n // console.log('polyfilled');\n asc = asc.replace(/\\s+/g, '');\n if (!b64re.test(asc))\n throw new TypeError('malformed base64.');\n asc += '=='.slice(2 - (asc.length & 3));\n let u24, bin = '', r1, r2;\n for (let i = 0; i < asc.length;) {\n u24 = b64tab[asc.charAt(i++)] << 18\n | b64tab[asc.charAt(i++)] << 12\n | (r1 = b64tab[asc.charAt(i++)]) << 6\n | (r2 = b64tab[asc.charAt(i++)]);\n bin += r1 === 64 ? _fromCC(u24 >> 16 & 255)\n : r2 === 64 ? _fromCC(u24 >> 16 & 255, u24 >> 8 & 255)\n : _fromCC(u24 >> 16 & 255, u24 >> 8 & 255, u24 & 255);\n }\n return bin;\n};\n/**\n * does what `window.atob` of web browsers do.\n * @param {String} asc Base64-encoded string\n * @returns {string} binary string\n */\nconst _atob = typeof atob === 'function' ? (asc) => atob(_tidyB64(asc))\n : _hasBuffer ? (asc) => Buffer.from(asc, 'base64').toString('binary')\n : atobPolyfill;\n//\nconst _toUint8Array = _hasBuffer\n ? (a) => _U8Afrom(Buffer.from(a, 'base64'))\n : (a) => _U8Afrom(_atob(a).split('').map(c => c.charCodeAt(0)));\n/**\n * converts a Base64 string to a Uint8Array.\n */\nconst toUint8Array = (a) => _toUint8Array(_unURI(a));\n//\nconst _decode = _hasBuffer\n ? (a) => Buffer.from(a, 'base64').toString('utf8')\n : _TD\n ? (a) => _TD.decode(_toUint8Array(a))\n : (a) => btou(_atob(a));\nconst _unURI = (a) => _tidyB64(a.replace(/[-_]/g, (m0) => m0 == '-' ? '+' : '/'));\n/**\n * converts a Base64 string to a UTF-8 string.\n * @param {String} src Base64 string. Both normal and URL-safe are supported\n * @returns {string} UTF-8 string\n */\nconst decode = (src) => _decode(_unURI(src));\n/**\n * check if a value is a valid Base64 string\n * @param {String} src a value to check\n */\nconst isValid = (src) => {\n if (typeof src !== 'string')\n return false;\n const s = src.replace(/\\s+/g, '').replace(/={0,2}$/, '');\n return !/[^\\s0-9a-zA-Z\\+/]/.test(s) || !/[^\\s0-9a-zA-Z\\-_]/.test(s);\n};\n//\nconst _noEnum = (v) => {\n return {\n value: v, enumerable: false, writable: true, configurable: true\n };\n};\n/**\n * extend String.prototype with relevant methods\n */\nconst extendString = function () {\n const _add = (name, body) => Object.defineProperty(String.prototype, name, _noEnum(body));\n _add('fromBase64', function () { return decode(this); });\n _add('toBase64', function (urlsafe) { return encode(this, urlsafe); });\n _add('toBase64URI', function () { return encode(this, true); });\n _add('toBase64URL', function () { return encode(this, true); });\n _add('toUint8Array', function () { return toUint8Array(this); });\n};\n/**\n * extend Uint8Array.prototype with relevant methods\n */\nconst extendUint8Array = function () {\n const _add = (name, body) => Object.defineProperty(Uint8Array.prototype, name, _noEnum(body));\n _add('toBase64', function (urlsafe) { return fromUint8Array(this, urlsafe); });\n _add('toBase64URI', function () { return fromUint8Array(this, true); });\n _add('toBase64URL', function () { return fromUint8Array(this, true); });\n};\n/**\n * extend Builtin prototypes with relevant methods\n */\nconst extendBuiltins = () => {\n extendString();\n extendUint8Array();\n};\nconst gBase64 = {\n version: version,\n VERSION: VERSION,\n atob: _atob,\n atobPolyfill: atobPolyfill,\n btoa: _btoa,\n btoaPolyfill: btoaPolyfill,\n fromBase64: decode,\n toBase64: encode,\n encode: encode,\n encodeURI: encodeURI,\n encodeURL: encodeURI,\n utob: utob,\n btou: btou,\n decode: decode,\n isValid: isValid,\n fromUint8Array: fromUint8Array,\n toUint8Array: toUint8Array,\n extendString: extendString,\n extendUint8Array: extendUint8Array,\n extendBuiltins: extendBuiltins\n};\n// makecjs:CUT //\nexport { version };\nexport { VERSION };\nexport { _atob as atob };\nexport { atobPolyfill };\nexport { _btoa as btoa };\nexport { btoaPolyfill };\nexport { decode as fromBase64 };\nexport { encode as toBase64 };\nexport { utob };\nexport { encode };\nexport { encodeURI };\nexport { encodeURI as encodeURL };\nexport { btou };\nexport { decode };\nexport { isValid };\nexport { fromUint8Array };\nexport { toUint8Array };\nexport { extendString };\nexport { extendUint8Array };\nexport { extendBuiltins };\n// and finally,\nexport { gBase64 as Base64 };\n","export const LsPrefix = \"__fastnear_\";\n\nexport interface StorageBackend {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n clear(): void;\n}\n\n// Default: Use `localStorage` if available, otherwise an in-memory fallback\nexport const createDefaultStorage = (): StorageBackend =>\n typeof localStorage !== \"undefined\"\n ? localStorage\n : {\n getItem: (key) => memoryStore.get(key) || null,\n setItem: (key, value) => memoryStore.set(key, value),\n removeItem: (key) => memoryStore.delete(key),\n clear: () => memoryStore.clear(),\n };\n\nexport const memoryStore = new Map(); // Internal memory storage\n\nlet storageBackend: StorageBackend = createDefaultStorage();\n\n// Functional storage module\nexport const storage = {\n setBackend: (customBackend: StorageBackend) => {\n storageBackend = customBackend;\n },\n\n set: (key: string, value: any) => {\n if (value === null || value === undefined) {\n storageBackend.removeItem(LsPrefix + key);\n } else {\n storageBackend.setItem(LsPrefix + key, JSON.stringify(value));\n }\n },\n\n get: (key: string): any => {\n const value = storageBackend.getItem(LsPrefix + key);\n if (value === null) return null;\n try {\n return JSON.parse(value);\n } catch {\n return value; // Return raw string if not JSON\n }\n },\n\n remove: (key: string) => storageBackend.removeItem(key),\n clear: () => storageBackend.clear(),\n};\n","import {\n binary_to_base58 as toBase58,\n base58_to_binary as fromBase58,\n} from \"base58-js\";\nimport Big from \"big.js\";\nimport {\n encode as JsBase64Encode,\n decode as JsBase64Decode,\n fromUint8Array as JsBase64FromUint8Array,\n toUint8Array as JsBase64ToUint8Array\n} from 'js-base64';\nimport { storage } from \"./storage.js\";\n\nexport { toBase58, fromBase58 };\n\nexport function toHex(data: Uint8Array): string {\n return Array.from(data)\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nexport function fromHex(hex: string): Uint8Array {\n if (hex.length % 2) throw new Error('Hex string must be even length');\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n bytes[i/2] = parseInt(hex.slice(i, i + 2), 16);\n }\n return bytes;\n}\n\nexport function base64ToBytes(b64Val: string): Uint8Array {\n return JsBase64ToUint8Array(b64Val);\n}\n\nexport function bytesToBase64(bytesArr: Uint8Array): string {\n return JsBase64FromUint8Array(bytesArr);\n}\n\nexport function toBase64(strVal: string) {\n try {\n return JsBase64Encode(strVal);\n } catch (e) {\n console.error('Issue base64 encoding', e);\n return null;\n }\n}\n\nexport function fromBase64(strVal: string) {\n try {\n return JsBase64Decode(strVal);\n } catch (e) {\n console.error('Issue base64 decoding', e);\n return null;\n }\n}\n\nexport function convertUnit(s: string | TemplateStringsArray, ...args: any[]): string {\n // Reconstruct raw string from template literal\n if (Array.isArray(s)) {\n s = s.reduce((acc, part, i) => {\n return acc + (args[i - 1] ?? \"\") + part;\n });\n }\n // Convert from `100 NEAR` into yoctoNear\n if (typeof s == \"string\") {\n const match = s.match(/([0-9.,_]+)\\s*([a-zA-Z]+)?/);\n if (match) {\n const amount = match[1].replace(/[_,]/g, \"\");\n const unitPart = match[2];\n if (unitPart) {\n switch (unitPart.toLowerCase()) {\n case \"near\":\n return Big(amount).mul(Big(10).pow(24)).toFixed(0);\n case \"tgas\":\n return Big(amount).mul(Big(10).pow(12)).toFixed(0);\n case \"ggas\":\n return Big(amount).mul(Big(10).pow(9)).toFixed(0);\n case \"gas\":\n case \"yoctonear\":\n return Big(amount).toFixed(0);\n default:\n throw new Error(`Unknown unit: ${unitPart}`);\n }\n } else {\n return Big(amount).toFixed(0);\n }\n }\n }\n return Big(`${s}`).toFixed(0);\n}\n\nexport function lsSet(key: string, value: any) {\n storage.set(key, value);\n}\n\nexport function lsGet(key: string): any {\n return storage.get(key);\n}\n\nexport function deepCopy(obj) {\n return JSON.parse(JSON.stringify(obj));\n}\n\nexport function tryParseJson(...args) {\n try {\n return JSON.parse(args[0]);\n } catch {\n if (args.length > 1) {\n return args[1];\n }\n return args[0];\n }\n}\n\nexport function parseJsonFromBytes(bytes: Uint8Array) {\n try {\n const decoder = new TextDecoder();\n return JSON.parse(\n decoder.decode(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes))\n );\n } catch (e) {\n try {\n return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n } catch (e) {\n return bytes;\n }\n }\n}\n\nexport function canSignWithLAK(actions) {\n return (\n actions.length === 1 &&\n actions[0].type === \"FunctionCall\" &&\n Big(actions[0]?.deposit ?? \"0\").eq(0)\n );\n}\n","import { ed25519 } from \"@noble/curves/ed25519\";\nimport { sha256 } from \"@noble/hashes/sha2\";\nimport { fromBase58, toBase58 } from \"./misc.js\";\nimport { Hex } from \"@noble/curves/abstract/utils\";\n\nexport { sha256 };\n\nexport const keyFromString = (key) =>\n fromBase58(\n key.includes(\":\")\n ? (() => {\n const [curve, keyPart] = key.split(\":\");\n if (curve !== \"ed25519\") {\n throw new Error(`Unsupported curve: ${curve}`);\n }\n return keyPart;\n })()\n : key,\n );\n\nexport const keyToString = (key: Uint8Array) => `ed25519:${toBase58(key)}`;\n\nexport function publicKeyFromPrivate(privateKey: string) {\n const secret = keyFromString(privateKey).slice(0, 32);\n const publicKey = ed25519.getPublicKey(secret);\n return keyToString(publicKey);\n}\n\nexport function privateKeyFromRandom() {\n const privateKey = crypto.getRandomValues(new Uint8Array(64));\n return keyToString(privateKey);\n}\n\nexport function signHash(hashBytes: Uint8Array, privateKey: string, opts?: any): Hex {\n const secret = keyFromString(privateKey).slice(0, 32);\n const signature = ed25519.sign(hashBytes, secret);\n\n if (opts?.returnBase58) {\n return toBase58(signature);\n }\n\n return signature;\n}\n\nexport function signBytes(bytes: Uint8Array, privateKey: string) {\n const hash = sha256(bytes);\n return signHash(hash, privateKey);\n}\n","export var integers = ['u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', 'f32', 'f64'];\n","var EncodeBuffer = /** @class */ (function () {\n function EncodeBuffer() {\n this.offset = 0;\n this.buffer_size = 256;\n this.buffer = new ArrayBuffer(this.buffer_size);\n this.view = new DataView(this.buffer);\n }\n EncodeBuffer.prototype.resize_if_necessary = function (needed_space) {\n if (this.buffer_size - this.offset < needed_space) {\n this.buffer_size = Math.max(this.buffer_size * 2, this.buffer_size + needed_space);\n var new_buffer = new ArrayBuffer(this.buffer_size);\n new Uint8Array(new_buffer).set(new Uint8Array(this.buffer));\n this.buffer = new_buffer;\n this.view = new DataView(new_buffer);\n }\n };\n EncodeBuffer.prototype.get_used_buffer = function () {\n return new Uint8Array(this.buffer).slice(0, this.offset);\n };\n EncodeBuffer.prototype.store_value = function (value, type) {\n var bSize = type.substring(1);\n var size = parseInt(bSize) / 8;\n this.resize_if_necessary(size);\n var toCall = type[0] === 'f' ? \"setFloat\".concat(bSize) : type[0] === 'i' ? \"setInt\".concat(bSize) : \"setUint\".concat(bSize);\n this.view[toCall](this.offset, value, true);\n this.offset += size;\n };\n EncodeBuffer.prototype.store_bytes = function (from) {\n this.resize_if_necessary(from.length);\n new Uint8Array(this.buffer).set(new Uint8Array(from), this.offset);\n this.offset += from.length;\n };\n return EncodeBuffer;\n}());\nexport { EncodeBuffer };\nvar DecodeBuffer = /** @class */ (function () {\n function DecodeBuffer(buf) {\n this.offset = 0;\n this.buffer_size = buf.length;\n this.buffer = new ArrayBuffer(buf.length);\n new Uint8Array(this.buffer).set(buf);\n this.view = new DataView(this.buffer);\n }\n DecodeBuffer.prototype.assert_enough_buffer = function (size) {\n if (this.offset + size > this.buffer.byteLength) {\n throw new Error('Error in schema, the buffer is smaller than expected');\n }\n };\n DecodeBuffer.prototype.consume_value = function (type) {\n var bSize = type.substring(1);\n var size = parseInt(bSize) / 8;\n this.assert_enough_buffer(size);\n var toCall = type[0] === 'f' ? \"getFloat\".concat(bSize) : type[0] === 'i' ? \"getInt\".concat(bSize) : \"getUint\".concat(bSize);\n var ret = this.view[toCall](this.offset, true);\n this.offset += size;\n return ret;\n };\n DecodeBuffer.prototype.consume_bytes = function (size) {\n this.assert_enough_buffer(size);\n var ret = this.buffer.slice(this.offset, this.offset + size);\n this.offset += size;\n return ret;\n };\n return DecodeBuffer;\n}());\nexport { DecodeBuffer };\n","var __extends = (this && this.__extends) || (function () {\n var extendStatics = function (d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n };\n return function (d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n };\n})();\nimport { integers } from './types.js';\nexport function isArrayLike(value) {\n // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like\n return (Array.isArray(value) ||\n (!!value &&\n typeof value === 'object' &&\n 'length' in value &&\n typeof (value.length) === 'number' &&\n (value.length === 0 ||\n (value.length > 0 &&\n (value.length - 1) in value))));\n}\nexport function expect_type(value, type, fieldPath) {\n if (typeof (value) !== type) {\n throw new Error(\"Expected \".concat(type, \" not \").concat(typeof (value), \"(\").concat(value, \") at \").concat(fieldPath.join('.')));\n }\n}\nexport function expect_bigint(value, fieldPath) {\n var basicType = ['number', 'string', 'bigint', 'boolean'].includes(typeof (value));\n var strObject = typeof (value) === 'object' && value !== null && 'toString' in value;\n if (!basicType && !strObject) {\n throw new Error(\"Expected bigint, number, boolean or string not \".concat(typeof (value), \"(\").concat(value, \") at \").concat(fieldPath.join('.')));\n }\n}\nexport function expect_same_size(length, expected, fieldPath) {\n if (length !== expected) {\n throw new Error(\"Array length \".concat(length, \" does not match schema length \").concat(expected, \" at \").concat(fieldPath.join('.')));\n }\n}\nexport function expect_enum(value, fieldPath) {\n if (typeof (value) !== 'object' || value === null) {\n throw new Error(\"Expected object not \".concat(typeof (value), \"(\").concat(value, \") at \").concat(fieldPath.join('.')));\n }\n}\n// Validate Schema\nvar VALID_STRING_TYPES = integers.concat(['bool', 'string']);\nvar VALID_OBJECT_KEYS = ['option', 'enum', 'array', 'set', 'map', 'struct'];\nvar ErrorSchema = /** @class */ (function (_super) {\n __extends(ErrorSchema, _super);\n function ErrorSchema(schema, expected) {\n var message = \"Invalid schema: \".concat(JSON.stringify(schema), \" expected \").concat(expected);\n return _super.call(this, message) || this;\n }\n return ErrorSchema;\n}(Error));\nexport { ErrorSchema };\nexport function validate_schema(schema) {\n if (typeof (schema) === 'string' && VALID_STRING_TYPES.includes(schema)) {\n return;\n }\n if (schema && typeof (schema) === 'object') {\n var keys = Object.keys(schema);\n if (keys.length === 1 && VALID_OBJECT_KEYS.includes(keys[0])) {\n var key = keys[0];\n if (key === 'option')\n return validate_schema(schema[key]);\n if (key === 'enum')\n return validate_enum_schema(schema[key]);\n if (key === 'array')\n return validate_array_schema(schema[key]);\n if (key === 'set')\n return validate_schema(schema[key]);\n if (key === 'map')\n return validate_map_schema(schema[key]);\n if (key === 'struct')\n return validate_struct_schema(schema[key]);\n }\n }\n throw new ErrorSchema(schema, VALID_OBJECT_KEYS.join(', ') + ' or ' + VALID_STRING_TYPES.join(', '));\n}\nfunction validate_enum_schema(schema) {\n if (!Array.isArray(schema))\n throw new ErrorSchema(schema, 'Array');\n for (var _i = 0, schema_1 = schema; _i < schema_1.length; _i++) {\n var sch = schema_1[_i];\n if (typeof sch !== 'object' || !('struct' in sch)) {\n throw new Error('Missing \"struct\" key in enum schema');\n }\n if (typeof sch.struct !== 'object' || Object.keys(sch.struct).length !== 1) {\n throw new Error('The \"struct\" in each enum must have a single key');\n }\n validate_schema({ struct: sch.struct });\n }\n}\nfunction validate_array_schema(schema) {\n if (typeof schema !== 'object')\n throw new ErrorSchema(schema, '{ type, len? }');\n if (schema.len && typeof schema.len !== 'number') {\n throw new Error(\"Invalid schema: \".concat(schema));\n }\n if ('type' in schema)\n return validate_schema(schema.type);\n throw new ErrorSchema(schema, '{ type, len? }');\n}\nfunction validate_map_schema(schema) {\n if (typeof schema === 'object' && 'key' in schema && 'value' in schema) {\n validate_schema(schema.key);\n validate_schema(schema.value);\n }\n else {\n throw new ErrorSchema(schema, '{ key, value }');\n }\n}\nfunction validate_struct_schema(schema) {\n if (typeof schema !== 'object')\n throw new ErrorSchema(schema, 'object');\n for (var key in schema) {\n validate_schema(schema[key]);\n }\n}\n","import { integers } from './types.js';\nimport { EncodeBuffer } from './buffer.js';\nimport * as utils from './utils.js';\nvar BorshSerializer = /** @class */ (function () {\n function BorshSerializer(checkTypes) {\n this.encoded = new EncodeBuffer();\n this.fieldPath = ['value'];\n this.checkTypes = checkTypes;\n }\n BorshSerializer.prototype.encode = function (value, schema) {\n this.encode_value(value, schema);\n return this.encoded.get_used_buffer();\n };\n BorshSerializer.prototype.encode_value = function (value, schema) {\n if (typeof schema === 'string') {\n if (integers.includes(schema))\n return this.encode_integer(value, schema);\n if (schema === 'string')\n return this.encode_string(value);\n if (schema === 'bool')\n return this.encode_boolean(value);\n }\n if (typeof schema === 'object') {\n if ('option' in schema)\n return this.encode_option(value, schema);\n if ('enum' in schema)\n return this.encode_enum(value, schema);\n if ('array' in schema)\n return this.encode_array(value, schema);\n if ('set' in schema)\n return this.encode_set(value, schema);\n if ('map' in schema)\n return this.encode_map(value, schema);\n if ('struct' in schema)\n return this.encode_struct(value, schema);\n }\n };\n BorshSerializer.prototype.encode_integer = function (value, schema) {\n var size = parseInt(schema.substring(1));\n if (size <= 32 || schema == 'f64') {\n this.checkTypes && utils.expect_type(value, 'number', this.fieldPath);\n this.encoded.store_value(value, schema);\n }\n else {\n this.checkTypes && utils.expect_bigint(value, this.fieldPath);\n this.encode_bigint(BigInt(value), size);\n }\n };\n BorshSerializer.prototype.encode_bigint = function (value, size) {\n var buffer_len = size / 8;\n var buffer = new Uint8Array(buffer_len);\n for (var i = 0; i < buffer_len; i++) {\n buffer[i] = Number(value & BigInt(0xff));\n value = value >> BigInt(8);\n }\n this.encoded.store_bytes(new Uint8Array(buffer));\n };\n BorshSerializer.prototype.encode_string = function (value) {\n this.checkTypes && utils.expect_type(value, 'string', this.fieldPath);\n var _value = value;\n // encode to utf8 bytes without using TextEncoder\n var utf8Bytes = [];\n for (var i = 0; i < _value.length; i++) {\n var charCode = _value.charCodeAt(i);\n if (charCode < 0x80) {\n utf8Bytes.push(charCode);\n }\n else if (charCode < 0x800) {\n utf8Bytes.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));\n }\n else if (charCode < 0xd800 || charCode >= 0xe000) {\n utf8Bytes.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));\n }\n else {\n i++;\n charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (_value.charCodeAt(i) & 0x3ff));\n utf8Bytes.push(0xf0 | (charCode >> 18), 0x80 | ((charCode >> 12) & 0x3f), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));\n }\n }\n // 4 bytes for length + string bytes\n this.encoded.store_value(utf8Bytes.length, 'u32');\n this.encoded.store_bytes(new Uint8Array(utf8Bytes));\n };\n BorshSerializer.prototype.encode_boolean = function (value) {\n this.checkTypes && utils.expect_type(value, 'boolean', this.fieldPath);\n this.encoded.store_value(value ? 1 : 0, 'u8');\n };\n BorshSerializer.prototype.encode_option = function (value, schema) {\n if (value === null || value === undefined) {\n this.encoded.store_value(0, 'u8');\n }\n else {\n this.encoded.store_value(1, 'u8');\n this.encode_value(value, schema.option);\n }\n };\n BorshSerializer.prototype.encode_enum = function (value, schema) {\n this.checkTypes && utils.expect_enum(value, this.fieldPath);\n var valueKey = Object.keys(value)[0];\n for (var i = 0; i < schema[\"enum\"].length; i++) {\n var valueSchema = schema[\"enum\"][i];\n if (valueKey === Object.keys(valueSchema.struct)[0]) {\n this.encoded.store_value(i, 'u8');\n return this.encode_struct(value, valueSchema);\n }\n }\n throw new Error(\"Enum key (\".concat(valueKey, \") not found in enum schema: \").concat(JSON.stringify(schema), \" at \").concat(this.fieldPath.join('.')));\n };\n BorshSerializer.prototype.encode_array = function (value, schema) {\n if (utils.isArrayLike(value))\n return this.encode_arraylike(value, schema);\n if (value instanceof ArrayBuffer)\n return this.encode_buffer(value, schema);\n throw new Error(\"Expected Array-like not \".concat(typeof (value), \"(\").concat(value, \") at \").concat(this.fieldPath.join('.')));\n };\n BorshSerializer.prototype.encode_arraylike = function (value, schema) {\n if (schema.array.len) {\n utils.expect_same_size(value.length, schema.array.len, this.fieldPath);\n }\n else {\n // 4 bytes for length\n this.encoded.store_value(value.length, 'u32');\n }\n // array values\n for (var i = 0; i < value.length; i++) {\n this.encode_value(value[i], schema.array.type);\n }\n };\n BorshSerializer.prototype.encode_buffer = function (value, schema) {\n if (schema.array.len) {\n utils.expect_same_size(value.byteLength, schema.array.len, this.fieldPath);\n }\n else {\n // 4 bytes for length\n this.encoded.store_value(value.byteLength, 'u32');\n }\n // array values\n this.encoded.store_bytes(new Uint8Array(value));\n };\n BorshSerializer.prototype.encode_set = function (value, schema) {\n this.checkTypes && utils.expect_type(value, 'object', this.fieldPath);\n var isSet = value instanceof Set;\n var values = isSet ? Array.from(value.values()) : Object.values(value);\n // 4 bytes for length\n this.encoded.store_value(values.length, 'u32');\n // set values\n for (var _i = 0, values_1 = values; _i < values_1.length; _i++) {\n var value_1 = values_1[_i];\n this.encode_value(value_1, schema.set);\n }\n };\n BorshSerializer.prototype.encode_map = function (value, schema) {\n this.checkTypes && utils.expect_type(value, 'object', this.fieldPath);\n var isMap = value instanceof Map;\n var keys = isMap ? Array.from(value.keys()) : Object.keys(value);\n // 4 bytes for length\n this.encoded.store_value(keys.length, 'u32');\n // store key/values\n for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) {\n var key = keys_1[_i];\n this.encode_value(key, schema.map.key);\n this.encode_value(isMap ? value.get(key) : value[key], schema.map.value);\n }\n };\n BorshSerializer.prototype.encode_struct = function (value, schema) {\n this.checkTypes && utils.expect_type(value, 'object', this.fieldPath);\n for (var _i = 0, _a = Object.keys(schema.struct); _i < _a.length; _i++) {\n var key = _a[_i];\n this.fieldPath.push(key);\n this.encode_value(value[key], schema.struct[key]);\n this.fieldPath.pop();\n }\n };\n return BorshSerializer;\n}());\nexport { BorshSerializer };\n","import { integers } from './types.js';\nimport { DecodeBuffer } from './buffer.js';\nvar BorshDeserializer = /** @class */ (function () {\n function BorshDeserializer(bufferArray) {\n this.buffer = new DecodeBuffer(bufferArray);\n }\n BorshDeserializer.prototype.decode = function (schema) {\n return this.decode_value(schema);\n };\n BorshDeserializer.prototype.decode_value = function (schema) {\n if (typeof schema === 'string') {\n if (integers.includes(schema))\n return this.decode_integer(schema);\n if (schema === 'string')\n return this.decode_string();\n if (schema === 'bool')\n return this.decode_boolean();\n }\n if (typeof schema === 'object') {\n if ('option' in schema)\n return this.decode_option(schema);\n if ('enum' in schema)\n return this.decode_enum(schema);\n if ('array' in schema)\n return this.decode_array(schema);\n if ('set' in schema)\n return this.decode_set(schema);\n if ('map' in schema)\n return this.decode_map(schema);\n if ('struct' in schema)\n return this.decode_struct(schema);\n }\n throw new Error(\"Unsupported type: \".concat(schema));\n };\n BorshDeserializer.prototype.decode_integer = function (schema) {\n var size = parseInt(schema.substring(1));\n if (size <= 32 || schema == 'f64') {\n return this.buffer.consume_value(schema);\n }\n return this.decode_bigint(size, schema.startsWith('i'));\n };\n BorshDeserializer.prototype.decode_bigint = function (size, signed) {\n if (signed === void 0) { signed = false; }\n var buffer_len = size / 8;\n var buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len));\n var bits = buffer.reduceRight(function (r, x) { return r + x.toString(16).padStart(2, '0'); }, '');\n if (signed && buffer[buffer_len - 1]) {\n return BigInt.asIntN(size, BigInt(\"0x\".concat(bits)));\n }\n return BigInt(\"0x\".concat(bits));\n };\n BorshDeserializer.prototype.decode_string = function () {\n var len = this.decode_integer('u32');\n var buffer = new Uint8Array(this.buffer.consume_bytes(len));\n // decode utf-8 string without using TextDecoder\n // first get all bytes to single byte code points\n var codePoints = [];\n for (var i = 0; i < len; ++i) {\n var byte = buffer[i];\n if (byte < 0x80) {\n codePoints.push(byte);\n }\n else if (byte < 0xE0) {\n codePoints.push(((byte & 0x1F) << 6) | (buffer[++i] & 0x3F));\n }\n else if (byte < 0xF0) {\n codePoints.push(((byte & 0x0F) << 12) | ((buffer[++i] & 0x3F) << 6) | (buffer[++i] & 0x3F));\n }\n else {\n var codePoint = ((byte & 0x07) << 18) | ((buffer[++i] & 0x3F) << 12) | ((buffer[++i] & 0x3F) << 6) | (buffer[++i] & 0x3F);\n codePoints.push(codePoint);\n }\n }\n // then decode code points to utf-8\n return String.fromCodePoint.apply(String, codePoints);\n };\n BorshDeserializer.prototype.decode_boolean = function () {\n return this.buffer.consume_value('u8') > 0;\n };\n BorshDeserializer.prototype.decode_option = function (schema) {\n var option = this.buffer.consume_value('u8');\n if (option === 1) {\n return this.decode_value(schema.option);\n }\n if (option !== 0) {\n throw new Error(\"Invalid option \".concat(option));\n }\n return null;\n };\n BorshDeserializer.prototype.decode_enum = function (schema) {\n var _a;\n var valueIndex = this.buffer.consume_value('u8');\n if (valueIndex > schema[\"enum\"].length) {\n throw new Error(\"Enum option \".concat(valueIndex, \" is not available\"));\n }\n var struct = schema[\"enum\"][valueIndex].struct;\n var key = Object.keys(struct)[0];\n return _a = {}, _a[key] = this.decode_value(struct[key]), _a;\n };\n BorshDeserializer.prototype.decode_array = function (schema) {\n var result = [];\n var len = schema.array.len ? schema.array.len : this.decode_integer('u32');\n for (var i = 0; i < len; ++i) {\n result.push(this.decode_value(schema.array.type));\n }\n return result;\n };\n BorshDeserializer.prototype.decode_set = function (schema) {\n var len = this.decode_integer('u32');\n var result = new Set();\n for (var i = 0; i < len; ++i) {\n result.add(this.decode_value(schema.set));\n }\n return result;\n };\n BorshDeserializer.prototype.decode_map = function (schema) {\n var len = this.decode_integer('u32');\n var result = new Map();\n for (var i = 0; i < len; ++i) {\n var key = this.decode_value(schema.map.key);\n var value = this.decode_value(schema.map.value);\n result.set(key, value);\n }\n return result;\n };\n BorshDeserializer.prototype.decode_struct = function (schema) {\n var result = {};\n for (var key in schema.struct) {\n result[key] = this.decode_value(schema.struct[key]);\n }\n return result;\n };\n return BorshDeserializer;\n}());\nexport { BorshDeserializer };\n","import { BorshSerializer } from './serialize.js';\nimport { BorshDeserializer } from './deserialize.js';\nimport * as utils from './utils.js';\nexport function serialize(schema, value, validate) {\n if (validate === void 0) { validate = true; }\n if (validate)\n utils.validate_schema(schema);\n var serializer = new BorshSerializer(validate);\n return serializer.encode(value, schema);\n}\nexport function deserialize(schema, buffer, validate) {\n if (validate === void 0) { validate = true; }\n if (validate)\n utils.validate_schema(schema);\n var deserializer = new BorshDeserializer(buffer);\n return deserializer.decode(schema);\n}\n","import { Schema } from \"borsh\"\n\nexport const nearChainSchema = new (class BorshSchema {\n Ed25519Signature: Schema = {\n struct: {\n data: { array: { type: \"u8\", len: 64 } },\n },\n };\n Secp256k1Signature: Schema = {\n struct: {\n data: { array: { type: \"u8\", len: 65 } },\n },\n };\n Signature: Schema = {\n enum: [\n { struct: { ed25519Signature: this.Ed25519Signature } },\n { struct: { secp256k1Signature: this.Secp256k1Signature } },\n ],\n };\n Ed25519Data: Schema = {\n struct: {\n data: { array: { type: \"u8\", len: 32 } },\n },\n };\n Secp256k1Data: Schema = {\n struct: {\n data: { array: { type: \"u8\", len: 64 } },\n },\n };\n PublicKey: Schema = {\n enum: [\n { struct: { ed25519Key: this.Ed25519Data } },\n { struct: { secp256k1Key: this.Secp256k1Data } },\n ],\n };\n FunctionCallPermission: Schema = {\n struct: {\n allowance: { option: \"u128\" },\n receiverId: \"string\",\n methodNames: { array: { type: \"string\" } },\n },\n };\n FullAccessPermission: Schema = {\n struct: {},\n };\n AccessKeyPermission: Schema = {\n enum: [\n { struct: { functionCall: this.FunctionCallPermission } },\n { struct: { fullAccess: this.FullAccessPermission } },\n ],\n };\n AccessKey: Schema = {\n struct: {\n nonce: \"u64\",\n permission: this.AccessKeyPermission,\n },\n };\n CreateAccount: Schema = {\n struct: {},\n };\n DeployContract: Schema = {\n struct: {\n code: { array: { type: \"u8\" } },\n },\n };\n FunctionCall: Schema = {\n struct: {\n methodName: \"string\",\n args: { array: { type: \"u8\" } },\n gas: \"u64\",\n deposit: \"u128\",\n },\n };\n Transfer: Schema = {\n struct: {\n deposit: \"u128\",\n },\n };\n Stake: Schema = {\n struct: {\n stake: \"u128\",\n publicKey: this.PublicKey,\n },\n };\n AddKey: Schema = {\n struct: {\n publicKey: this.PublicKey,\n accessKey: this.AccessKey,\n },\n };\n DeleteKey: Schema = {\n struct: {\n publicKey: this.PublicKey,\n },\n };\n DeleteAccount: Schema = {\n struct: {\n beneficiaryId: \"string\",\n },\n };\n ClassicAction: Schema = {\n enum: [\n { struct: { createAccount: this.CreateAccount } },\n { struct: { deployContract: this.DeployContract } },\n { struct: { functionCall: this.FunctionCall } },\n { struct: { transfer: this.Transfer } },\n { struct: { stake: this.Stake } },\n { struct: { addKey: this.AddKey } },\n { struct: { deleteKey: this.DeleteKey } },\n { struct: { deleteAccount: this.DeleteAccount } },\n ],\n };\n DelegateAction: Schema = {\n struct: {\n senderId: \"string\",\n receiverId: \"string\",\n actions: { array: { type: this.ClassicAction } },\n nonce: \"u64\",\n maxBlockHeight: \"u64\",\n publicKey: this.PublicKey,\n },\n };\n SignedDelegate: Schema = {\n struct: {\n delegateAction: this.DelegateAction,\n signature: this.Signature,\n },\n };\n Action: Schema = {\n enum: [\n { struct: { createAccount: this.CreateAccount } },\n { struct: { deployContract: this.DeployContract } },\n { struct: { functionCall: this.FunctionCall } },\n { struct: { transfer: this.Transfer } },\n { struct: { stake: this.Stake } },\n { struct: { addKey: this.AddKey } },\n { struct: { deleteKey: this.DeleteKey } },\n { struct: { deleteAccount: this.DeleteAccount } },\n { struct: { signedDelegate: this.SignedDelegate } },\n ],\n };\n Transaction: Schema = {\n struct: {\n signerId: \"string\",\n publicKey: this.PublicKey,\n nonce: \"u64\",\n receiverId: \"string\",\n blockHash: { array: { type: \"u8\", len: 32 } },\n actions: { array: { type: this.Action } },\n },\n };\n SignedTransaction: Schema = {\n struct: {\n transaction: this.Transaction,\n signature: this.Signature,\n },\n };\n})();\n\nexport const getBorshSchema = () => nearChainSchema;\n","import { serialize as borshSerialize, deserialize as borshDeserialize, Schema } from \"borsh\";\nimport { keyFromString } from \"./crypto.js\";\nimport {base64ToBytes, fromBase58, fromBase64, toBase64} from \"./misc.js\";\nimport { getBorshSchema } from \"@fastnear/borsh-schema\";\n\nexport interface PlainTransaction {\n signerId: string;\n publicKey: string;\n nonce: string | bigint | number;\n receiverId: string;\n blockHash: string;\n actions: Array;\n}\n\nexport interface PlainSignedTransaction {\n transaction: object;\n signature: object;\n}\n\n// Function to return a JSON-ready version of the transaction\nexport const txToJson = (tx: PlainTransaction): Record => {\n return JSON.parse(JSON.stringify(tx, (key, value) =>\n typeof value === 'bigint' ? value.toString() : value\n ));\n};\n\n// dude let's make this better. head just couldn't find a good name\nexport const txToJsonStringified = (tx: PlainTransaction): string => {\n return JSON.stringify(txToJson(tx));\n}\n\nexport function mapTransaction(jsonTransaction: PlainTransaction) {\n return {\n signerId: jsonTransaction.signerId,\n publicKey: {\n ed25519Key: {\n data: keyFromString(jsonTransaction.publicKey)\n }\n },\n nonce: BigInt(jsonTransaction.nonce),\n receiverId: jsonTransaction.receiverId,\n blockHash: fromBase58(jsonTransaction.blockHash),\n actions: jsonTransaction.actions.map(mapAction)\n };\n}\n\nexport function serializeTransaction(jsonTransaction: PlainTransaction) {\n console.log(\"fastnear: serializing transaction\");\n\n const transaction = mapTransaction(jsonTransaction);\n console.log(\"fastnear: mapped transaction for borsh:\", transaction);\n\n return borshSerialize(SCHEMA.Transaction, transaction);\n}\n\nexport function serializeSignedTransaction(jsonTransaction: PlainTransaction, signature) {\n console.log(\"fastnear: Serializing Signed Transaction\", jsonTransaction);\n console.log('fastnear: signature', signature)\n console.log('fastnear: signature length', fromBase58(signature).length)\n\n const mappedSignedTx = mapTransaction(jsonTransaction)\n console.log('fastnear: mapped (for borsh schema) signed transaction', mappedSignedTx)\n\n const plainSignedTransaction: PlainSignedTransaction = {\n transaction: mappedSignedTx,\n signature: {\n ed25519Signature: {\n data: fromBase58(signature),\n },\n },\n };\n\n const borshSignedTx = borshSerialize(SCHEMA.SignedTransaction, plainSignedTransaction, true);\n console.log('fastnear: borsh-serialized signed transaction:', borshSignedTx);\n\n return borshSignedTx;\n}\n\nexport function mapAction(action: any): object {\n switch (action.type) {\n case \"CreateAccount\": {\n return {\n createAccount: {},\n };\n }\n case \"DeployContract\": {\n return {\n deployContract: {\n code: base64ToBytes(action.codeBase64),\n },\n };\n }\n case \"FunctionCall\": {\n return {\n functionCall: {\n methodName: action.methodName,\n args: (action.argsBase64 !== null && action.argsBase64 !== undefined) ?\n base64ToBytes(action.argsBase64) :\n (new TextEncoder().encode(JSON.stringify(action.args))),\n gas: BigInt(action.gas ?? \"300000000000000\"),\n deposit: BigInt(action.deposit ?? \"0\"),\n },\n };\n }\n case \"Transfer\": {\n return {\n transfer: {\n deposit: BigInt(action.deposit),\n },\n };\n }\n case \"Stake\": {\n return {\n stake: {\n stake: BigInt(action.stake),\n publicKey: {\n ed25519Key: {\n data: keyFromString(action.publicKey),\n },\n },\n },\n };\n }\n case \"AddKey\": {\n return {\n addKey: {\n publicKey: {\n ed25519Key: {\n data: keyFromString(action.publicKey),\n },\n },\n accessKey: {\n nonce: BigInt(action.accessKey.nonce),\n permission:\n action.accessKey.permission === \"FullAccess\"\n ? { fullAccess: {} }\n : {\n functionCall: {\n allowance: action.accessKey.allowance\n ? BigInt(action.accessKey.allowance)\n : null,\n receiverId: action.accessKey.receiverId,\n methodNames: action.accessKey.methodNames,\n },\n },\n },\n },\n };\n }\n case \"DeleteKey\": {\n return {\n deleteKey: {\n publicKey: {\n ed25519Key: {\n data: keyFromString(action.publicKey),\n },\n },\n },\n };\n }\n case \"DeleteAccount\": {\n return {\n deleteAccount: {\n beneficiaryId: action.beneficiaryId,\n },\n };\n }\n case \"SignedDelegate\": {\n return {\n signedDelegate: {\n delegateAction: mapAction(action.delegateAction),\n signature: {\n ed25519Signature: fromBase58(action.signature),\n },\n },\n };\n }\n default: {\n throw new Error(\"Not implemented action: \" + action.type);\n }\n }\n}\n\nexport const SCHEMA = getBorshSchema();\n","import {\n lsSet,\n lsGet,\n publicKeyFromPrivate,\n} from \"@fastnear/utils\";\nimport {WalletAdapter} from \"@fastnear/wallet-adapter\";\n\nexport const WIDGET_URL = \"https://js.cdn.fastnear.com\";\n\nexport const DEFAULT_NETWORK_ID = \"mainnet\";\nexport const NETWORKS = {\n testnet: {\n networkId: \"testnet\",\n nodeUrl: \"https://rpc.testnet.fastnear.com/\",\n },\n mainnet: {\n networkId: \"mainnet\",\n nodeUrl: \"https://rpc.mainnet.fastnear.com/\",\n },\n};\n\nexport interface NetworkConfig {\n networkId: string;\n nodeUrl?: string;\n walletUrl?: string;\n helperUrl?: string;\n explorerUrl?: string;\n\n [key: string]: any;\n}\n\nexport interface AppState {\n accountId?: string | null;\n privateKey?: string | null;\n lastWalletId?: string | null;\n publicKey?: string | null;\n accessKeyContractId?: string | null;\n\n [key: string]: any;\n}\n\nexport interface TxStatus {\n txId: string;\n updateTimestamp?: number;\n\n [key: string]: any;\n}\n\nexport type TxHistory = Record;\n\nexport interface EventListeners {\n account: Set<(accountId: string) => void>;\n tx: Set<(tx: TxStatus) => void>;\n}\n\nexport interface UnbroadcastedEvents {\n account: string[];\n tx: TxStatus[];\n}\n\nexport interface WalletAdapterState {\n publicKey?: string | null;\n privateKey?: string | null;\n accountId?: string | null;\n lastWalletId?: string | null;\n networkId: string;\n}\n\n\n// Load config from localStorage or default to the network's config\nexport let _config: NetworkConfig = lsGet(\"config\") || {\n ...NETWORKS[DEFAULT_NETWORK_ID]\n};\n\n// Load application state from localStorage\nexport let _state: AppState = lsGet(\"state\") || {};\n\n// Triggered by the wallet adapter\nexport const onAdapterStateUpdate = (state: WalletAdapterState) => {\n console.log(\"Adapter state update:\", state);\n const { accountId, lastWalletId, privateKey } = state;\n update({\n accountId: accountId || undefined,\n lastWalletId: lastWalletId || undefined,\n ...(privateKey ? { privateKey } : {}),\n });\n}\n\nexport const getWalletAdapterState = (): WalletAdapterState => {\n return {\n publicKey: _state.publicKey,\n accountId: _state.accountId,\n lastWalletId: _state.lastWalletId,\n networkId: _config.networkId,\n };\n}\n\n// We can create an adapter instance here\nexport let _adapter = new WalletAdapter({\n onStateUpdate: onAdapterStateUpdate,\n lastState: getWalletAdapterState(),\n widgetUrl: WIDGET_URL,\n});\n\n// Attempt to set publicKey if we have a privateKey\ntry {\n _state.publicKey = _state.privateKey\n ? publicKeyFromPrivate(_state.privateKey)\n : null;\n} catch (e) {\n console.error(\"Error parsing private key:\", e);\n _state.privateKey = null;\n lsSet(\"nonce\", null);\n}\n\n// Transaction history\nexport let _txHistory: TxHistory = lsGet(\"txHistory\") || {};\n\n\nexport const _unbroadcastedEvents: UnbroadcastedEvents = {\n account: [],\n tx: [],\n};\n\n// events / listeners\nexport const events = {\n _eventListeners: {\n account: new Set(),\n tx: new Set(),\n },\n\n notifyAccountListeners: (accountId: string) => {\n if (events._eventListeners.account.size === 0) {\n _unbroadcastedEvents.account.push(accountId);\n return;\n }\n events._eventListeners.account.forEach((callback: any) => {\n try {\n callback(accountId);\n } catch (e) {\n console.error(e);\n }\n });\n },\n\n notifyTxListeners: (tx: TxStatus) => {\n if (events._eventListeners.tx.size === 0) {\n _unbroadcastedEvents.tx.push(tx);\n return;\n }\n events._eventListeners.tx.forEach((callback: any) => {\n try {\n callback(tx);\n } catch (e) {\n console.error(e);\n }\n });\n },\n\n onAccount: (callback: (accountId: string) => void) => {\n events._eventListeners.account.add(callback);\n if (_unbroadcastedEvents.account.length > 0) {\n const accountEvent = _unbroadcastedEvents.account;\n _unbroadcastedEvents.account = [];\n accountEvent.forEach(events.notifyAccountListeners);\n }\n },\n\n onTx: (callback: (tx: TxStatus) => void): void => {\n events._eventListeners.tx.add(callback);\n if (_unbroadcastedEvents.tx.length > 0) {\n const txEvent = _unbroadcastedEvents.tx;\n _unbroadcastedEvents.tx = [];\n txEvent.forEach(events.notifyTxListeners);\n }\n }\n}\n\n// Mutators\n// @todo: in favor of limiting when out of alpha\n// but haven't given it enough thought ~ mike\nexport const update = (newState: Partial) => {\n const oldState = _state;\n _state = {..._state, ...newState};\n\n lsSet(\"state\", {\n accountId: _state.accountId,\n privateKey: _state.privateKey,\n lastWalletId: _state.lastWalletId,\n accessKeyContractId: _state.accessKeyContractId,\n });\n\n if (\n newState.hasOwnProperty(\"privateKey\") &&\n newState.privateKey !== oldState.privateKey\n ) {\n _state.publicKey = newState.privateKey\n ? publicKeyFromPrivate(newState.privateKey as string)\n : null;\n lsSet(\"nonce\", null);\n }\n\n if (newState.accountId !== oldState.accountId) {\n events.notifyAccountListeners(newState.accountId as string);\n }\n\n if (\n (newState.hasOwnProperty(\"lastWalletId\") &&\n newState.lastWalletId !== oldState.lastWalletId) ||\n (newState.hasOwnProperty(\"accountId\") &&\n newState.accountId !== oldState.accountId) ||\n (newState.hasOwnProperty(\"privateKey\") &&\n newState.privateKey !== oldState.privateKey)\n ) {\n _adapter.setState(getWalletAdapterState());\n }\n}\n\nexport const updateTxHistory = (txStatus: TxStatus) => {\n const txId = txStatus.txId;\n _txHistory[txId] = {\n ...(_txHistory[txId] || {}),\n ...txStatus,\n updateTimestamp: Date.now(),\n };\n lsSet(\"txHistory\", _txHistory);\n events.notifyTxListeners(_txHistory[txId]);\n}\n\nexport const getConfig = (): NetworkConfig => {\n return _config;\n}\n\nexport const getTxHistory = (): TxHistory => {\n return _txHistory;\n}\n\n// Exposed \"write\" functions\nexport const setConfig = (newConf: NetworkConfig): void => {\n _config = { ...NETWORKS[newConf.networkId], ...newConf };\n lsSet(\"config\", _config);\n}\n\nexport const resetTxHistory = (): void => {\n _txHistory = {};\n lsSet(\"txHistory\", _txHistory);\n}\n","/**\n * @typedef {Object} WalletState\n * @property {string} [accountId] - NEAR account ID if signed in\n * @property {string} [publicKey] - Public key if available\n * @property {string} [privateKey] - Private key if available\n * @property {string} [lastWalletId] - ID of last used wallet\n * @property {string} [networkId] - ID of last used network\n */\n\n/**\n * @typedef {Object} SignInConfig\n * @property {string} networkId - NEAR network ID ('mainnet' or 'testnet')\n * @property {string} contractId - Contract ID to request access for\n * @property {string} [walletId] - Preferred wallet to use. E.g. 'near', 'here', 'meteor'\n * @property {string} [callbackUrl] - URL to redirect back to after wallet interaction\n */\n\n/**\n * @typedef {Object} SignInResult\n * @property {string} [url] - URL to redirect to if needed\n * @property {string} [accountId] - Account ID if immediately available\n * @property {string} [error] - Error message if sign in failed\n */\nexport interface SignInResult {\n url?: string;\n accountId?: string;\n error?: string;\n}\n\n/**\n * @typedef {Object} Transaction\n * @property {string} [signerId] - Transaction signer account ID\n * @property {string} receiverId - Transaction receiver account ID\n * @property {Object[]} actions - Transaction actions to perform\n */\n\n/**\n * @typedef {Object} TransactionConfig\n * @property {Transaction} transactions - Transaction actions to perform\n * @property {string} [callbackUrl] - URL to redirect back to after wallet interaction\n */\n\n/**\n * @typedef {Object} TransactionResult\n * @property {string} [url] - URL to redirect to if needed\n * @property {string} [hash] - Transaction hash if immediately available\n * @property {string} [error] - Error message if transaction failed\n * Represents the result of attempting to send or execute a transaction.\n */\nexport interface TransactionResult {\n /** URL to redirect to if needed. */\n url?: string;\n\n /** Transaction hash if immediately available. */\n hash?: string;\n\n /** Error message if the transaction failed. */\n error?: string;\n}\n\nexport interface WalletAdapterConstructor {\n widgetUrl?: string;\n targetOrigin?: string;\n onStateUpdate?: (state: any) => void;\n lastState?: any;\n callbackUrl?: string;\n}\n\n/**\n * @typedef {Object} WalletAdapterConfig\n * @property {string} [widgetUrl] - URL of the wallet widget (defaults to official hosted version)\n * @property {string} [targetOrigin] - Target origin for postMessage (defaults to '*')\n * @property {string} [lastState] - The last state that was given by WalletAdapter before the redirect or reload.\n * @property {(state: WalletState) => void} [onStateUpdate] - Called when wallet state changes\n * @property {string} [callbackUrl] - Default callback URL for wallet interactions (defaults to current page URL)\n */\n\n/**\n * Interface for interacting with NEAR wallets\n */\nexport class WalletAdapter {\n /** @type {HTMLIFrameElement} */\n #iframe: HTMLIFrameElement | null = null;\n\n /** @type {string} */\n #targetOrigin;\n\n /** @type {string} */\n #widgetUrl;\n\n /** @type {Map} */\n #pending = new Map();\n\n /** @type {WalletState} */\n #state;\n\n /** @type {Function} */\n #onStateUpdate;\n\n /** @type {string} */\n #callbackUrl;\n\n /** @type {string} */\n static defaultWidgetUrl = \"https://wallet-adapter.fastnear.com\";\n\n /**\n * @param {WalletAdapterConfig} [config]\n */\n constructor({\n widgetUrl = WalletAdapter.defaultWidgetUrl,\n targetOrigin = \"*\",\n onStateUpdate,\n lastState,\n callbackUrl = window.location.href,\n }: WalletAdapterConstructor = {}) {\n this.#targetOrigin = targetOrigin;\n this.#widgetUrl = widgetUrl;\n this.#onStateUpdate = onStateUpdate;\n this.#callbackUrl = callbackUrl;\n this.#state = lastState || {};\n window.addEventListener(\"message\", this.#handleMessage.bind(this));\n }\n\n /**\n * Creates an iframe for wallet interaction\n * @param {string} path - Path to load in iframe\n * @returns {HTMLIFrameElement}\n */\n #createIframe(path) {\n // Remove existing iframe if any\n if (this.#iframe) {\n this.#iframe.remove();\n }\n\n // Create URL\n const url = new URL(path, this.#widgetUrl);\n\n // Create and configure iframe\n const iframe = document.createElement(\"iframe\");\n iframe.src = url.toString();\n iframe.allow = \"usb\";\n iframe.style.border = \"none\";\n iframe.style.zIndex = \"10000\";\n iframe.style.position = \"fixed\";\n iframe.style.display = \"block\";\n iframe.style.top = \"0\";\n iframe.style.left = \"0\";\n iframe.style.width = \"100%\";\n iframe.style.height = \"100%\";\n document.body.appendChild(iframe);\n\n this.#iframe = iframe;\n return iframe;\n }\n\n /**\n * Handles messages from the wallet widget\n * @param {MessageEvent} event\n */\n #handleMessage(event) {\n // Check origin if specified\n if (this.#targetOrigin !== \"*\" && event.origin !== this.#targetOrigin) {\n return;\n }\n\n const { id, type, action, payload } = event.data;\n if (type !== \"wallet-adapter\") return;\n\n // Handle close action\n if (action === \"close\") {\n this.#iframe?.remove();\n this.#iframe = null;\n return;\n }\n\n // Update state if provided\n if (payload?.state) {\n this.#state = { ...this.#state, ...payload.state };\n this.#onStateUpdate?.(this.#state);\n }\n\n // Resolve pending promise if any\n const resolve = this.#pending.get(id) as ((value: SignInResult) => void);\n if (resolve) {\n this.#pending.delete(id);\n this.#iframe?.remove();\n this.#iframe = null;\n resolve(payload as SignInResult);\n }\n }\n\n /**\n * Sends a message to the wallet widget\n * @param {string} path - Path to load in iframe\n * @param {string} method - Method to call\n * @param {Object} params - Parameters to pass\n * @returns {Promise}\n */\n async #sendMessage(path, method, params): Promise {\n return new Promise((resolve) => {\n const id = Math.random().toString(36).slice(2);\n this.#pending.set(id, resolve);\n\n const iframe = this.#createIframe(path);\n\n iframe.onload = () => {\n iframe.contentWindow?.postMessage(\n {\n type: \"wallet-adapter\",\n method,\n params: {\n id,\n ...params,\n state: this.#state,\n callbackUrl: params.callbackUrl || this.#callbackUrl,\n },\n },\n this.#targetOrigin\n );\n };\n });\n }\n\n /**\n * Get current wallet state\n * @returns {WalletState}\n */\n getState() {\n return { ...this.#state };\n }\n\n /**\n * Set current wallet state\n * @param state\n */\n setState(state) {\n this.#state = state;\n }\n\n /**\n * Sign in with a NEAR wallet\n * @param {SignInConfig} config\n * @returns {Promise}\n *\n * Should be returning SignInResult\n */\n async signIn(config): Promise {\n return this.#sendMessage(\"/login.html\", \"signIn\", config);\n }\n\n /**\n * Send a transaction using connected wallet\n * @param {TransactionConfig} config\n * @returns {Promise}\n */\n async sendTransactions(config): Promise {\n return this.#sendMessage(\"/send.html\", \"sendTransactions\", config);\n }\n\n /**\n * Clean up adapter resources\n */\n destroy() {\n window.removeEventListener(\"message\", this.#handleMessage);\n this.#iframe?.remove();\n this.#iframe = null;\n }\n}\n","import Big from \"big.js\";\nimport {\n lsSet,\n lsGet,\n tryParseJson,\n fromBase64,\n toBase64,\n canSignWithLAK,\n toBase58,\n parseJsonFromBytes,\n signHash,\n publicKeyFromPrivate,\n privateKeyFromRandom,\n serializeTransaction,\n serializeSignedTransaction, bytesToBase64, PlainTransaction,\n} from \"@fastnear/utils\";\n\nimport {\n _adapter,\n _state,\n DEFAULT_NETWORK_ID,\n NETWORKS,\n getTxHistory,\n update,\n updateTxHistory,\n} from \"./state.js\";\n\nimport {\n getConfig,\n setConfig,\n resetTxHistory,\n} from \"./state.js\";\n\nimport { sha256 } from \"@noble/hashes/sha2\";\nimport * as reExportAllUtils from \"@fastnear/utils\";\nimport * as stateExports from \"./state.js\";\n\nBig.DP = 27;\nexport const MaxBlockDelayMs = 1000 * 60 * 60 * 6; // 6 hours\n\nexport interface AccessKeyWithError {\n result: {\n nonce: number;\n permission?: any;\n error?: string;\n }\n}\n\nexport interface WalletTxResult {\n url?: string;\n outcomes?: Array<{ transaction: { hash: string } }>;\n rejected?: boolean;\n error?: string;\n}\n\nexport interface BlockView {\n result: {\n header: {\n hash: string;\n timestamp_nanosec: string;\n }\n }\n}\n\n// The structure it's saved to in storage\nexport interface LastKnownBlock {\n header: {\n hash: string;\n timestamp_nanosec: string;\n }\n}\n\nexport function withBlockId(params: Record, blockId?: string) {\n if (blockId === \"final\" || blockId === \"optimistic\") {\n return { ...params, finality: blockId };\n }\n return blockId ? { ...params, block_id: blockId } : { ...params, finality: \"optimistic\" };\n}\n\nexport async function sendRpc(method: string, params: Record | any[]) {\n const config = getConfig();\n if (!config?.nodeUrl) {\n throw new Error(\"fastnear: getConfig() returned invalid config: missing nodeUrl.\");\n }\n const response = await fetch(config.nodeUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: `fastnear-${Date.now()}`,\n method,\n params,\n }),\n });\n const result = await response.json();\n if (result.error) {\n throw new Error(JSON.stringify(result.error));\n }\n return result;\n}\n\nexport function afterTxSent(txId: string) {\n const txHistory = getTxHistory();\n sendRpc(\"tx\", {\n tx_hash: txHistory[txId]?.txHash,\n sender_account_id: txHistory[txId]?.tx?.signerId,\n wait_until: \"EXECUTED_OPTIMISTIC\",\n })\n .then( result => {\n const successValue = result?.result?.status?.SuccessValue;\n updateTxHistory({\n txId,\n status: \"Executed\",\n result,\n successValue: successValue ? tryParseJson(fromBase64(successValue)) : undefined,\n finalState: true,\n });\n })\n .catch((error) => {\n updateTxHistory({\n txId,\n status: \"ErrorAfterIncluded\",\n error: tryParseJson(error.message) ?? error.message,\n finalState: true,\n });\n });\n}\n\nexport async function sendTxToRpc(signedTxBase64: string, waitUntil: string | undefined, txId: string) {\n // default to \"INCLUDED\"\n // see options: https://docs.near.org/api/rpc/transactions#tx-status-result\n waitUntil = waitUntil || \"INCLUDED\";\n\n try {\n const sendTxRes = await sendRpc(\"send_tx\", {\n signed_tx_base64: signedTxBase64,\n wait_until: waitUntil,\n });\n\n updateTxHistory({ txId, status: \"Included\", finalState: false });\n afterTxSent(txId);\n\n return sendTxRes;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n updateTxHistory({\n txId,\n status: \"Error\",\n error: tryParseJson(errorMessage) ?? errorMessage,\n finalState: false,\n });\n throw new Error(errorMessage);\n }\n}\n\nexport interface AccessKeyView {\n nonce: number;\n permission: any;\n}\n\n/**\n * Generates a mock transaction ID.\n *\n * This function creates a pseudo-unique transaction ID for testing or\n * non-production use. It combines the current timestamp with a\n * random component for uniqueness.\n *\n * **Note:** This is not cryptographically secure and should not be used\n * for actual transaction processing.\n *\n * @returns {string} A mock transaction ID in the format `tx-{timestamp}-{random}`\n */\nexport function generateTxId(): string {\n const randomPart = crypto.getRandomValues(new Uint32Array(2)).join(\"\");\n return `tx-${Date.now()}-${parseInt(randomPart, 10).toString(36)}`;\n}\n\nexport const accountId = () => _state.accountId;\nexport const publicKey = () => _state.publicKey;\n\nexport const config = (newConfig?: Record) => {\n const current = getConfig();\n if (newConfig) {\n if (newConfig.networkId && current.networkId !== newConfig.networkId) {\n setConfig(newConfig.networkId);\n update({ accountId: null, privateKey: null, lastWalletId: null });\n lsSet(\"block\", null);\n resetTxHistory();\n }\n setConfig({ ...getConfig(), ...newConfig });\n }\n return getConfig();\n};\n\nexport const authStatus = (): string | Record => {\n if (!_state.accountId) {\n return \"SignedOut\";\n }\n return \"SignedIn\";\n};\n\n// this is an intentional stub\n// and it's probably partially done, to help ease future features\n// for now we'll assume each web end user has one keypair in storage\n// for every contract they wish to interact with\n// later, it may be prudent to hold multiple, but until then this function\n// just returns the access key as if it were among others in the array.\n// we're pretending like we really thought about which access key we're returning\n// based on the opts argument. this allows us to fill this logic in later.\nexport const getPublicKeyForContract = (opts?: any) => {\n return publicKey();\n}\n\n// returns details on the selected:\n// network, wallet, and explorer details as well as\n// sending account, contract, and selected public key\nexport const selected = () => {\n const network = getConfig().networkId;\n const nodeUrl = getConfig().nodeUrl;\n const walletUrl = getConfig().walletUrl;\n const helperUrl = getConfig().helperUrl;\n const explorerUrl = getConfig().explorerUrl;\n\n const account = accountId();\n const contract = _state.accessKeyContractId;\n const publicKey = getPublicKeyForContract();\n\n return {\n network,\n nodeUrl,\n walletUrl,\n helperUrl,\n explorerUrl,\n account,\n contract,\n publicKey\n }\n}\n\nexport const requestSignIn = async ({ contractId }: { contractId: string }) => {\n const privateKey = privateKeyFromRandom();\n update({ accessKeyContractId: contractId, accountId: null, privateKey });\n const pubKey = publicKeyFromPrivate(privateKey);\n\n const result = await _adapter.signIn({\n networkId: getConfig().networkId,\n contractId,\n publicKey: pubKey,\n });\n\n if (result.error) {\n throw new Error(`Wallet error: ${result.error}`);\n }\n if (result.url) {\n if (typeof window !== \"undefined\") {\n setTimeout(() => {\n window.location.href = result.url;\n }, 100);\n }\n } else if (result.accountId) {\n update({ accountId: result.accountId });\n }\n};\n\nexport const view = async ({\n contractId,\n methodName,\n args,\n argsBase64,\n blockId,\n }: {\n contractId: string;\n methodName: string;\n args?: any;\n argsBase64?: string;\n blockId?: string;\n}) => {\n const encodedArgs = argsBase64 || (args ? toBase64(JSON.stringify(args)) : \"\");\n const queryResult = await sendRpc(\n \"query\",\n withBlockId(\n {\n request_type: \"call_function\",\n account_id: contractId,\n method_name: methodName,\n args_base64: encodedArgs,\n },\n blockId\n )\n );\n\n return parseJsonFromBytes(queryResult.result.result);\n};\n\nexport const queryAccount = async ({\n accountId,\n blockId,\n }: {\n accountId: string;\n blockId?: string;\n}) => {\n return sendRpc(\n \"query\",\n withBlockId({ request_type: \"view_account\", account_id: accountId }, blockId)\n );\n};\n\nexport const queryBlock = async ({ blockId }: { blockId?: string }): Promise => {\n return sendRpc(\"block\", withBlockId({}, blockId));\n};\n\nexport const queryAccessKey = async ({\n accountId,\n publicKey,\n blockId,\n }: {\n accountId: string;\n publicKey: string;\n blockId?: string;\n}): Promise => {\n return sendRpc(\n \"query\",\n withBlockId(\n { request_type: \"view_access_key\", account_id: accountId, public_key: publicKey },\n blockId\n )\n );\n};\n\nexport const queryTx = async ({ txHash, accountId }: { txHash: string; accountId: string }) => {\n return sendRpc(\"tx\", [txHash, accountId]);\n};\n\nexport const localTxHistory = () => {\n return getTxHistory();\n};\n\nexport const signOut = () => {\n update({ accountId: null, privateKey: null, contractId: null });\n setConfig(NETWORKS[DEFAULT_NETWORK_ID]);\n};\n\nexport const sendTx = async ({\n receiverId,\n actions,\n waitUntil,\n }: {\n receiverId: string;\n actions: any[];\n waitUntil?: string;\n}) => {\n const signerId = _state.accountId;\n if (!signerId) throw new Error(\"Must sign in\");\n\n const publicKey = _state.publicKey ?? \"\";\n const privKey = _state.privateKey;\n // this generates a mock transaction ID so we can keep track of each tx\n const txId = generateTxId();\n\n if (!privKey || receiverId !== _state.accessKeyContractId || !canSignWithLAK(actions)) {\n const jsonTx = { signerId, receiverId, actions };\n updateTxHistory({ status: \"Pending\", txId, tx: jsonTx, finalState: false });\n\n const url = new URL(typeof window !== \"undefined\" ? window.location.href : \"\");\n url.searchParams.set(\"txIds\", txId);\n\n // preserve existing url params\n const existingParams = new URLSearchParams(window.location.search);\n existingParams.forEach((value, key) => {\n if (!url.searchParams.has(key)) {\n url.searchParams.set(key, value);\n }\n });\n\n // we're wanting to preserve URL params that we send in\n // but make sure we're not feeding back error params\n // from a previous failure\n\n url.searchParams.delete(\"errorCode\");\n url.searchParams.delete(\"errorMessage\");\n\n try {\n const result: WalletTxResult = await _adapter.sendTransactions({\n transactions: [jsonTx],\n callbackUrl: url.toString(),\n });\n\n if (result.url) {\n if (typeof window !== \"undefined\") {\n setTimeout(() => {\n window.location.href = result.url!;\n }, 100);\n }\n } else if (result.outcomes?.length) {\n result.outcomes.forEach((r) =>\n updateTxHistory({\n txId,\n status: \"Executed\",\n result: r,\n txHash: r.transaction.hash,\n finalState: true,\n })\n );\n } else if (result.rejected) {\n updateTxHistory({ txId, status: \"RejectedByUser\", finalState: true });\n } else if (result.error) {\n updateTxHistory({\n txId,\n status: \"Error\",\n error: tryParseJson(result.error),\n finalState: true,\n });\n }\n\n return result;\n } catch (err) {\n console.error('fastnear: error sending tx using adapter:', err)\n updateTxHistory({\n txId,\n status: \"Error\",\n error: tryParseJson((err as Error).message),\n finalState: true,\n });\n\n return Promise.reject(err);\n }\n }\n\n let nonce = lsGet(\"nonce\") as number | null;\n if (nonce == null) {\n const accessKey = await queryAccessKey({ accountId: signerId, publicKey: publicKey });\n if (accessKey.result.error) {\n throw new Error(`Access key error: ${accessKey.result.error} when attempting to get nonce for ${signerId} for public key ${publicKey}`);\n }\n nonce = accessKey.result.nonce;\n lsSet(\"nonce\", nonce);\n }\n\n let lastKnownBlock = lsGet(\"block\") as LastKnownBlock | null;\n if (\n !lastKnownBlock ||\n parseFloat(lastKnownBlock.header.timestamp_nanosec) / 1e6 + MaxBlockDelayMs < Date.now()\n ) {\n const latestBlock = await queryBlock({ blockId: \"final\" });\n lastKnownBlock = {\n header: {\n hash: latestBlock.result.header.hash,\n timestamp_nanosec: latestBlock.result.header.timestamp_nanosec,\n },\n };\n lsSet(\"block\", lastKnownBlock);\n }\n\n nonce += 1;\n lsSet(\"nonce\", nonce);\n\n const blockHash = lastKnownBlock.header.hash;\n\n const plainTransactionObj: PlainTransaction = {\n signerId,\n publicKey,\n nonce,\n receiverId,\n blockHash,\n actions,\n };\n\n const txBytes = serializeTransaction(plainTransactionObj);\n const txHashBytes = sha256(txBytes);\n const txHash58 = toBase58(txHashBytes);\n\n const signatureBase58 = signHash(txHashBytes, privKey, { returnBase58: true });\n const signedTransactionBytes = serializeSignedTransaction(plainTransactionObj, signatureBase58);\n const signedTxBase64 = bytesToBase64(signedTransactionBytes);\n\n updateTxHistory({\n status: \"Pending\",\n txId,\n tx: plainTransactionObj,\n signature: signatureBase58,\n signedTxBase64,\n txHash: txHash58,\n finalState: false,\n });\n\n try {\n return await sendTxToRpc(signedTxBase64, waitUntil, txId);\n } catch (error) {\n console.error(\"Error Sending Transaction:\", error, plainTransactionObj, signedTxBase64);\n }\n};\n\n// exports\nexport const exp = {\n utils: {}, // we will map this in a moment, giving keys, for IDE hints\n borsh: reExportAllUtils.exp.borsh,\n borshSchema: reExportAllUtils.exp.borshSchema.getBorshSchema(),\n};\n\nfor (const key in reExportAllUtils) {\n exp.utils[key] = reExportAllUtils[key];\n}\n\n// devx\nexport const utils = exp.utils;\n\nexport const state = {}\n\nfor (const key in stateExports) {\n state[key] = stateExports[key];\n}\n\n// devx\n\nexport const event = state['events'];\ndelete state['events'];\n\n// Wallet redirect handling\ntry {\n if (typeof window !== \"undefined\") {\n const url = new URL(window.location.href);\n const accId = url.searchParams.get(\"account_id\");\n const pubKey = url.searchParams.get(\"public_key\");\n const errCode = url.searchParams.get(\"errorCode\");\n const errMsg = url.searchParams.get(\"errorMessage\");\n const decodedErrMsg = errMsg ? decodeURIComponent(errMsg) : null;\n\n const txHashes = url.searchParams.get(\"transactionHashes\");\n const txIds = url.searchParams.get(\"txIds\");\n\n if (errCode || errMsg) {\n console.warn(new Error(`Wallet raises:\\ncode: ${errCode}\\nmessage: ${decodedErrMsg}`));\n }\n\n if (accId && pubKey) {\n if (pubKey === _state.publicKey) {\n update({ accountId: accId });\n } else {\n // it's possible the end user has a URL param that's old. we'll remove the public_key param\n // if logged out, no need to throw warning\n if (authStatus() === \"SignedIn\") {\n console.warn(\"Public key mismatch from wallet redirect\", pubKey, _state.publicKey);\n }\n url.searchParams.delete(\"public_key\");\n }\n }\n\n if (txHashes || txIds) {\n const hashArr = txHashes ? txHashes.split(\",\") : [];\n const idArr = txIds ? txIds.split(\",\") : [];\n if (idArr.length > hashArr.length) {\n idArr.forEach((id) => {\n updateTxHistory({ txId: id, status: \"RejectedByUser\", finalState: true });\n });\n } else if (idArr.length === hashArr.length) {\n idArr.forEach((id, i) => {\n updateTxHistory({\n txId: id,\n status: \"PendingGotTxHash\",\n txHash: hashArr[i],\n finalState: false,\n });\n afterTxSent(id);\n });\n } else {\n console.error(new Error(\"Transaction hash mismatch from wallet redirect\"), idArr, hashArr);\n }\n }\n\n // we can consider removing these, but want to be careful because\n // it can be helpful for a dev to have a URL they can debug with\n // we won't want to remove information\n\n // pretty sure txIds can go, especially if you can tell it's been more than 5 minutes or something\n // public_key sometimes confuses it, so this might only be needed when adding a new access key\n // and perhaps once we've confirmed that the transaction hashes are getting saved to storage\n // (not sure about that section of code) then we can get rid of the transactionHashes, too\n\n url.searchParams.delete(\"txIds\");\n if (authStatus() === \"SignedOut\") {\n url.searchParams.delete(\"errorCode\");\n url.searchParams.delete(\"errorMessage\");\n }\n // ^ we've decided these ones make sense to keep\n\n // I'd like to keep this for posterity. for a bit.\n // url.searchParams.delete(\"account_id\");\n // url.searchParams.delete(\"public_key\");\n\n // url.searchParams.delete(\"all_keys\");\n // url.searchParams.delete(\"transactionHashes\");\n // window.history.replaceState({}, \"\", url.toString());\n }\n} catch (e) {\n console.error(\"Error handling wallet redirect:\", e);\n}\n\n// action helpers\nexport const actions = {\n functionCall: ({\n methodName,\n gas,\n deposit,\n args,\n argsBase64,\n }: {\n methodName: string;\n gas?: string;\n deposit?: string;\n args?: Record;\n argsBase64?: string;\n }) => ({\n type: \"FunctionCall\",\n methodName,\n args,\n argsBase64,\n gas,\n deposit,\n }),\n\n transfer: (yoctoAmount: string) => ({\n type: \"Transfer\",\n deposit: yoctoAmount,\n }),\n\n stakeNEAR: ({amount, publicKey}: { amount: string; publicKey: string }) => ({\n type: \"Stake\",\n stake: amount,\n publicKey,\n }),\n\n addFullAccessKey: ({publicKey}: { publicKey: string }) => ({\n type: \"AddKey\",\n publicKey: publicKey,\n accessKey: {permission: \"FullAccess\"},\n }),\n\n addLimitedAccessKey: ({\n publicKey,\n allowance,\n accountId,\n methodNames,\n }: {\n publicKey: string;\n allowance: string;\n accountId: string;\n methodNames: string[];\n }) => ({\n type: \"AddKey\",\n publicKey: publicKey,\n accessKey: {\n permission: \"FunctionCall\",\n allowance,\n receiverId: accountId,\n methodNames,\n },\n }),\n\n deleteKey: ({publicKey}: { publicKey: string }) => ({\n type: \"DeleteKey\",\n publicKey,\n }),\n\n deleteAccount: ({beneficiaryId}: { beneficiaryId: string }) => ({\n type: \"DeleteAccount\",\n beneficiaryId,\n }),\n\n createAccount: () => ({\n type: \"CreateAccount\",\n }),\n\n deployContract: ({codeBase64}: { codeBase64: string }) => ({\n type: \"DeployContract\",\n codeBase64,\n }),\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,MAAAA,eAAA;AAAA,WAAAA,cAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAAAC;AAAA,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACiBA,MAAI,KAAK;AAAT,MAUE,KAAK;AAVP,MAaE,SAAS;AAbX,MAgBE,YAAY;AAhBd,MAuBE,KAAK;AAvBP,MA8BE,KAAK;AA9BP,MAqCE,SAAS;AArCX,MA4CE,OAAO;AA5CT,MA6CE,UAAU,OAAO;AA7CnB,MA8CE,aAAa,UAAU;AA9CzB,MA+CE,aAAa,UAAU;AA/CzB,MAgDE,cAAc,OAAO;AAhDvB,MAmDE,IAAI,CAAC;AAnDP,MAoDE,YAAY;AApDd,MAqDE,UAAU;AAMZ,WAAS,QAAQ;AAQf,aAASC,KAAI,GAAG;AACd,UAAI,IAAI;AAGR,UAAI,EAAE,aAAaA,MAAM,QAAO,MAAM,YAAY,MAAM,IAAI,IAAIA,KAAI,CAAC;AAGrE,UAAI,aAAaA,MAAK;AACpB,UAAE,IAAI,EAAE;AACR,UAAE,IAAI,EAAE;AACR,UAAE,IAAI,EAAE,EAAE,MAAM;AAAA,MAClB,OAAO;AACL,YAAI,OAAO,MAAM,UAAU;AACzB,cAAIA,KAAI,WAAW,QAAQ,OAAO,MAAM,UAAU;AAChD,kBAAM,UAAU,UAAU,OAAO;AAAA,UACnC;AAGA,cAAI,MAAM,KAAK,IAAI,IAAI,IAAI,OAAO,OAAO,CAAC;AAAA,QAC5C;AAEA,cAAM,GAAG,CAAC;AAAA,MACZ;AAIA,QAAE,cAAcA;AAAA,IAClB;AA3BS,WAAAA,MAAA;AA6BT,IAAAA,KAAI,YAAY;AAChB,IAAAA,KAAI,KAAK;AACT,IAAAA,KAAI,KAAK;AACT,IAAAA,KAAI,KAAK;AACT,IAAAA,KAAI,KAAK;AACT,IAAAA,KAAI,SAAS;AACb,IAAAA,KAAI,YAAY;AAChB,IAAAA,KAAI,cAAc;AAClB,IAAAA,KAAI,gBAAgB;AACpB,IAAAA,KAAI,UAAU;AAEd,WAAOA;AAAA,EACT;AAjDS;AA0DT,WAAS,MAAM,GAAG,GAAG;AACnB,QAAI,GAAG,GAAG;AAEV,QAAI,CAAC,QAAQ,KAAK,CAAC,GAAG;AACpB,YAAM,MAAM,UAAU,QAAQ;AAAA,IAChC;AAGA,MAAE,IAAI,EAAE,OAAO,CAAC,KAAK,OAAO,IAAI,EAAE,MAAM,CAAC,GAAG,MAAM;AAGlD,SAAK,IAAI,EAAE,QAAQ,GAAG,KAAK,GAAI,KAAI,EAAE,QAAQ,KAAK,EAAE;AAGpD,SAAK,IAAI,EAAE,OAAO,IAAI,KAAK,GAAG;AAG5B,UAAI,IAAI,EAAG,KAAI;AACf,WAAK,CAAC,EAAE,MAAM,IAAI,CAAC;AACnB,UAAI,EAAE,UAAU,GAAG,CAAC;AAAA,IACtB,WAAW,IAAI,GAAG;AAGhB,UAAI,EAAE;AAAA,IACR;AAEA,SAAK,EAAE;AAGP,SAAK,IAAI,GAAG,IAAI,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,GAAE;AAE7C,QAAI,KAAK,IAAI;AAGX,QAAE,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,IAChB,OAAO;AAGL,aAAO,KAAK,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,MAAK;AACxC,QAAE,IAAI,IAAI,IAAI;AACd,QAAE,IAAI,CAAC;AAGP,WAAK,IAAI,GAAG,KAAK,KAAK,GAAE,EAAE,GAAG,IAAI,CAAC,EAAE,OAAO,GAAG;AAAA,IAChD;AAEA,WAAO;AAAA,EACT;AA/CS;AA0DT,WAAS,MAAM,GAAG,IAAI,IAAI,MAAM;AAC9B,QAAI,KAAK,EAAE;AAEX,QAAI,OAAO,UAAW,MAAK,EAAE,YAAY;AACzC,QAAI,OAAO,KAAK,OAAO,KAAK,OAAO,KAAK,OAAO,GAAG;AAChD,YAAM,MAAM,UAAU;AAAA,IACxB;AAEA,QAAI,KAAK,GAAG;AACV,aACE,OAAO,MAAM,QAAQ,CAAC,CAAC,GAAG,CAAC,MAAM,OAAO,MACxC,OAAO,KAAK,GAAG,CAAC,KAAK,KACrB,OAAO,MAAM,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,MAAM,MAAM,QAAQ,GAAG,CAAC,MAAM;AAG9D,SAAG,SAAS;AAEZ,UAAI,MAAM;AAGR,UAAE,IAAI,EAAE,IAAI,KAAK;AACjB,WAAG,CAAC,IAAI;AAAA,MACV,OAAO;AAGL,WAAG,CAAC,IAAI,EAAE,IAAI;AAAA,MAChB;AAAA,IACF,WAAW,KAAK,GAAG,QAAQ;AAGzB,aACE,OAAO,KAAK,GAAG,EAAE,KAAK,KACtB,OAAO,MAAM,GAAG,EAAE,IAAI,KAAK,GAAG,EAAE,MAAM,MACnC,QAAQ,GAAG,KAAK,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,OACpD,OAAO,MAAM,QAAQ,CAAC,CAAC,GAAG,CAAC;AAG7B,SAAG,SAAS;AAGZ,UAAI,MAAM;AAGR,eAAO,EAAE,GAAG,EAAE,EAAE,IAAI,KAAI;AACtB,aAAG,EAAE,IAAI;AACT,cAAI,OAAO,GAAG;AACZ,cAAE,EAAE;AACJ,eAAG,QAAQ,CAAC;AACZ;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,WAAK,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE,IAAI,IAAG,IAAI;AAAA,IAC1C;AAEA,WAAO;AAAA,EACT;AA1DS;AAiET,WAAS,UAAU,GAAG,eAAe,WAAW;AAC9C,QAAI,IAAI,EAAE,GACR,IAAI,EAAE,EAAE,KAAK,EAAE,GACf,IAAI,EAAE;AAGR,QAAI,eAAe;AACjB,UAAI,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,MAAM,EAAE,MAAM,CAAC,IAAI,OAAO,IAAI,IAAI,MAAM,QAAQ;AAAA,IAG7E,WAAW,IAAI,GAAG;AAChB,aAAO,EAAE,IAAI,KAAI,MAAM;AACvB,UAAI,OAAO;AAAA,IACb,WAAW,IAAI,GAAG;AAChB,UAAI,EAAE,IAAI,GAAG;AACX,aAAK,KAAK,GAAG,MAAM,MAAK;AAAA,MAC1B,WAAW,IAAI,GAAG;AAChB,YAAI,EAAE,MAAM,GAAG,CAAC,IAAI,MAAM,EAAE,MAAM,CAAC;AAAA,MACrC;AAAA,IACF,WAAW,IAAI,GAAG;AAChB,UAAI,EAAE,OAAO,CAAC,IAAI,MAAM,EAAE,MAAM,CAAC;AAAA,IACnC;AAEA,WAAO,EAAE,IAAI,KAAK,YAAY,MAAM,IAAI;AAAA,EAC1C;AAxBS;AAiCT,IAAE,MAAM,WAAY;AAClB,QAAI,IAAI,IAAI,KAAK,YAAY,IAAI;AACjC,MAAE,IAAI;AACN,WAAO;AAAA,EACT;AAQA,IAAE,MAAM,SAAU,GAAG;AACnB,QAAI,OACF,IAAI,MACJ,KAAK,EAAE,GACP,MAAM,IAAI,IAAI,EAAE,YAAY,CAAC,GAAG,GAChC,IAAI,EAAE,GACN,IAAI,EAAE,GACN,IAAI,EAAE,GACN,IAAI,EAAE;AAGR,QAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,EAAG,QAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI;AAGxD,QAAI,KAAK,EAAG,QAAO;AAEnB,YAAQ,IAAI;AAGZ,QAAI,KAAK,EAAG,QAAO,IAAI,IAAI,QAAQ,IAAI;AAEvC,SAAK,IAAI,GAAG,WAAW,IAAI,GAAG,UAAU,IAAI;AAG5C,SAAK,IAAI,IAAI,EAAE,IAAI,KAAI;AACrB,UAAI,GAAG,CAAC,KAAK,GAAG,CAAC,EAAG,QAAO,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,QAAQ,IAAI;AAAA,IACzD;AAGA,WAAO,KAAK,IAAI,IAAI,IAAI,IAAI,QAAQ,IAAI;AAAA,EAC1C;AAOA,IAAE,MAAM,SAAU,GAAG;AACnB,QAAI,IAAI,MACNA,OAAM,EAAE,aACR,IAAI,EAAE,GACN,KAAK,IAAI,IAAIA,KAAI,CAAC,GAAG,GACrB,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,IACrB,KAAKA,KAAI;AAEX,QAAI,OAAO,CAAC,CAAC,MAAM,KAAK,KAAK,KAAK,QAAQ;AACxC,YAAM,MAAM,UAAU;AAAA,IACxB;AAGA,QAAI,CAAC,EAAE,CAAC,GAAG;AACT,YAAM,MAAM,WAAW;AAAA,IACzB;AAGA,QAAI,CAAC,EAAE,CAAC,GAAG;AACT,QAAE,IAAI;AACN,QAAE,IAAI,CAAC,EAAE,IAAI,CAAC;AACd,aAAO;AAAA,IACT;AAEA,QAAI,IAAI,IAAI,GAAG,KAAK,IAClB,KAAK,EAAE,MAAM,GACb,KAAK,KAAK,EAAE,QACZ,KAAK,EAAE,QACP,IAAI,EAAE,MAAM,GAAG,EAAE,GACjB,KAAK,EAAE,QACP,IAAI,GACJ,KAAK,EAAE,IAAI,CAAC,GACZ,KAAK,GACL,IAAI,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK;AAE/B,MAAE,IAAI;AACN,QAAI,IAAI,IAAI,IAAI;AAGhB,OAAG,QAAQ,CAAC;AAGZ,WAAO,OAAO,KAAK,GAAE,KAAK,CAAC;AAE3B,OAAG;AAGD,WAAK,IAAI,GAAG,IAAI,IAAI,KAAK;AAGvB,YAAI,OAAO,KAAK,EAAE,SAAS;AACzB,gBAAM,KAAK,KAAK,IAAI;AAAA,QACtB,OAAO;AACL,eAAK,KAAK,IAAI,MAAM,GAAG,EAAE,KAAK,MAAK;AACjC,gBAAI,EAAE,EAAE,KAAK,EAAE,EAAE,GAAG;AAClB,oBAAM,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,IAAI;AAC1B;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAGA,YAAI,MAAM,GAAG;AAIX,eAAK,KAAK,MAAM,KAAK,IAAI,IAAI,MAAK;AAChC,gBAAI,EAAE,EAAE,EAAE,IAAI,GAAG,EAAE,GAAG;AACpB,mBAAK;AACL,qBAAO,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,GAAE,EAAE,IAAI;AAChC,gBAAE,EAAE,EAAE;AACN,gBAAE,EAAE,KAAK;AAAA,YACX;AACA,cAAE,EAAE,KAAK,GAAG,EAAE;AAAA,UAChB;AAEA,iBAAO,CAAC,EAAE,CAAC,IAAI,GAAE,MAAM;AAAA,QACzB,OAAO;AACL;AAAA,QACF;AAAA,MACF;AAGA,SAAG,IAAI,IAAI,MAAM,IAAI,EAAE;AAGvB,UAAI,EAAE,CAAC,KAAK,IAAK,GAAE,EAAE,IAAI,EAAE,EAAE,KAAK;AAAA,UAC7B,KAAI,CAAC,EAAE,EAAE,CAAC;AAAA,IAEjB,UAAU,OAAO,MAAM,EAAE,CAAC,MAAM,cAAc;AAG9C,QAAI,CAAC,GAAG,CAAC,KAAK,MAAM,GAAG;AAGrB,SAAG,MAAM;AACT,QAAE;AACF;AAAA,IACF;AAGA,QAAI,KAAK,EAAG,OAAM,GAAG,GAAGA,KAAI,IAAI,EAAE,CAAC,MAAM,SAAS;AAElD,WAAO;AAAA,EACT;AAMA,IAAE,KAAK,SAAU,GAAG;AAClB,WAAO,KAAK,IAAI,CAAC,MAAM;AAAA,EACzB;AAOA,IAAE,KAAK,SAAU,GAAG;AAClB,WAAO,KAAK,IAAI,CAAC,IAAI;AAAA,EACvB;AAOA,IAAE,MAAM,SAAU,GAAG;AACnB,WAAO,KAAK,IAAI,CAAC,IAAI;AAAA,EACvB;AAMA,IAAE,KAAK,SAAU,GAAG;AAClB,WAAO,KAAK,IAAI,CAAC,IAAI;AAAA,EACvB;AAOA,IAAE,MAAM,SAAU,GAAG;AACnB,WAAO,KAAK,IAAI,CAAC,IAAI;AAAA,EACvB;AAMA,IAAE,QAAQ,EAAE,MAAM,SAAU,GAAG;AAC7B,QAAI,GAAG,GAAG,GAAG,MACX,IAAI,MACJA,OAAM,EAAE,aACR,IAAI,EAAE,GACN,KAAK,IAAI,IAAIA,KAAI,CAAC,GAAG;AAGvB,QAAI,KAAK,GAAG;AACV,QAAE,IAAI,CAAC;AACP,aAAO,EAAE,KAAK,CAAC;AAAA,IACjB;AAEA,QAAI,KAAK,EAAE,EAAE,MAAM,GACjB,KAAK,EAAE,GACP,KAAK,EAAE,GACP,KAAK,EAAE;AAGT,QAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG;AACpB,UAAI,GAAG,CAAC,GAAG;AACT,UAAE,IAAI,CAAC;AAAA,MACT,WAAW,GAAG,CAAC,GAAG;AAChB,YAAI,IAAIA,KAAI,CAAC;AAAA,MACf,OAAO;AACL,UAAE,IAAI;AAAA,MACR;AACA,aAAO;AAAA,IACT;AAGA,QAAI,IAAI,KAAK,IAAI;AAEf,UAAI,OAAO,IAAI,GAAG;AAChB,YAAI,CAAC;AACL,YAAI;AAAA,MACN,OAAO;AACL,aAAK;AACL,YAAI;AAAA,MACN;AAEA,QAAE,QAAQ;AACV,WAAK,IAAI,GAAG,MAAM,GAAE,KAAK,CAAC;AAC1B,QAAE,QAAQ;AAAA,IACZ,OAAO;AAGL,YAAM,OAAO,GAAG,SAAS,GAAG,UAAU,KAAK,IAAI;AAE/C,WAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAI,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG;AAClB,iBAAO,GAAG,CAAC,IAAI,GAAG,CAAC;AACnB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,MAAM;AACR,UAAI;AACJ,WAAK;AACL,WAAK;AACL,QAAE,IAAI,CAAC,EAAE;AAAA,IACX;AAMA,SAAK,KAAK,IAAI,GAAG,WAAW,IAAI,GAAG,WAAW,EAAG,QAAO,MAAM,IAAG,GAAG,IAAI;AAGxE,SAAK,IAAI,GAAG,IAAI,KAAI;AAClB,UAAI,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,GAAG;AACnB,aAAK,IAAI,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,IAAG,CAAC,IAAI;AACpC,UAAE,GAAG,CAAC;AACN,WAAG,CAAC,KAAK;AAAA,MACX;AAEA,SAAG,CAAC,KAAK,GAAG,CAAC;AAAA,IACf;AAGA,WAAO,GAAG,EAAE,CAAC,MAAM,IAAI,IAAG,IAAI;AAG9B,WAAO,GAAG,CAAC,MAAM,KAAI;AACnB,SAAG,MAAM;AACT,QAAE;AAAA,IACJ;AAEA,QAAI,CAAC,GAAG,CAAC,GAAG;AAGV,QAAE,IAAI;AAGN,WAAK,CAAC,KAAK,CAAC;AAAA,IACd;AAEA,MAAE,IAAI;AACN,MAAE,IAAI;AAEN,WAAO;AAAA,EACT;AAMA,IAAE,MAAM,SAAU,GAAG;AACnB,QAAI,MACF,IAAI,MACJA,OAAM,EAAE,aACR,IAAI,EAAE,GACN,KAAK,IAAI,IAAIA,KAAI,CAAC,GAAG;AAEvB,QAAI,CAAC,EAAE,EAAE,CAAC,GAAG;AACX,YAAM,MAAM,WAAW;AAAA,IACzB;AAEA,MAAE,IAAI,EAAE,IAAI;AACZ,WAAO,EAAE,IAAI,CAAC,KAAK;AACnB,MAAE,IAAI;AACN,MAAE,IAAI;AAEN,QAAI,KAAM,QAAO,IAAIA,KAAI,CAAC;AAE1B,QAAIA,KAAI;AACR,QAAIA,KAAI;AACR,IAAAA,KAAI,KAAKA,KAAI,KAAK;AAClB,QAAI,EAAE,IAAI,CAAC;AACX,IAAAA,KAAI,KAAK;AACT,IAAAA,KAAI,KAAK;AAET,WAAO,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC;AAAA,EAC9B;AAMA,IAAE,MAAM,WAAY;AAClB,QAAI,IAAI,IAAI,KAAK,YAAY,IAAI;AACjC,MAAE,IAAI,CAAC,EAAE;AACT,WAAO;AAAA,EACT;AAMA,IAAE,OAAO,EAAE,MAAM,SAAU,GAAG;AAC5B,QAAI,GAAG,GAAG,GACR,IAAI,MACJA,OAAM,EAAE;AAEV,QAAI,IAAIA,KAAI,CAAC;AAGb,QAAI,EAAE,KAAK,EAAE,GAAG;AACd,QAAE,IAAI,CAAC,EAAE;AACT,aAAO,EAAE,MAAM,CAAC;AAAA,IAClB;AAEA,QAAI,KAAK,EAAE,GACT,KAAK,EAAE,GACP,KAAK,EAAE,GACP,KAAK,EAAE;AAGT,QAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG;AACpB,UAAI,CAAC,GAAG,CAAC,GAAG;AACV,YAAI,GAAG,CAAC,GAAG;AACT,cAAI,IAAIA,KAAI,CAAC;AAAA,QACf,OAAO;AACL,YAAE,IAAI,EAAE;AAAA,QACV;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,SAAK,GAAG,MAAM;AAId,QAAI,IAAI,KAAK,IAAI;AACf,UAAI,IAAI,GAAG;AACT,aAAK;AACL,YAAI;AAAA,MACN,OAAO;AACL,YAAI,CAAC;AACL,YAAI;AAAA,MACN;AAEA,QAAE,QAAQ;AACV,aAAO,MAAM,GAAE,KAAK,CAAC;AACrB,QAAE,QAAQ;AAAA,IACZ;AAGA,QAAI,GAAG,SAAS,GAAG,SAAS,GAAG;AAC7B,UAAI;AACJ,WAAK;AACL,WAAK;AAAA,IACP;AAEA,QAAI,GAAG;AAGP,SAAK,IAAI,GAAG,GAAG,GAAG,CAAC,KAAK,GAAI,MAAK,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK;AAIrE,QAAI,GAAG;AACL,SAAG,QAAQ,CAAC;AACZ,QAAE;AAAA,IACJ;AAGA,SAAK,IAAI,GAAG,QAAQ,GAAG,EAAE,CAAC,MAAM,IAAI,IAAG,IAAI;AAE3C,MAAE,IAAI;AACN,MAAE,IAAI;AAEN,WAAO;AAAA,EACT;AAUA,IAAE,MAAM,SAAU,GAAG;AACnB,QAAI,IAAI,MACN,MAAM,IAAI,EAAE,YAAY,GAAG,GAC3B,IAAI,KACJ,QAAQ,IAAI;AAEd,QAAI,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC,aAAa,IAAI,WAAW;AAChD,YAAM,MAAM,UAAU,UAAU;AAAA,IAClC;AAEA,QAAI,MAAO,KAAI,CAAC;AAEhB,eAAS;AACP,UAAI,IAAI,EAAG,KAAI,EAAE,MAAM,CAAC;AACxB,YAAM;AACN,UAAI,CAAC,EAAG;AACR,UAAI,EAAE,MAAM,CAAC;AAAA,IACf;AAEA,WAAO,QAAQ,IAAI,IAAI,CAAC,IAAI;AAAA,EAC9B;AAUA,IAAE,OAAO,SAAU,IAAI,IAAI;AACzB,QAAI,OAAO,CAAC,CAAC,MAAM,KAAK,KAAK,KAAK,QAAQ;AACxC,YAAM,MAAM,UAAU,WAAW;AAAA,IACnC;AACA,WAAO,MAAM,IAAI,KAAK,YAAY,IAAI,GAAG,IAAI,EAAE;AAAA,EACjD;AAYA,IAAE,QAAQ,SAAU,IAAI,IAAI;AAC1B,QAAI,OAAO,UAAW,MAAK;AAAA,aAClB,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,UAAU,KAAK,QAAQ;AACnD,YAAM,MAAM,UAAU;AAAA,IACxB;AACA,WAAO,MAAM,IAAI,KAAK,YAAY,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,EAAE;AAAA,EAC9D;AAOA,IAAE,OAAO,WAAY;AACnB,QAAI,GAAG,GAAG,GACR,IAAI,MACJA,OAAM,EAAE,aACR,IAAI,EAAE,GACN,IAAI,EAAE,GACN,OAAO,IAAIA,KAAI,KAAK;AAGtB,QAAI,CAAC,EAAE,EAAE,CAAC,EAAG,QAAO,IAAIA,KAAI,CAAC;AAG7B,QAAI,IAAI,GAAG;AACT,YAAM,MAAM,OAAO,gBAAgB;AAAA,IACrC;AAGA,QAAI,KAAK,KAAK,CAAC,UAAU,GAAG,MAAM,IAAI,CAAC;AAIvC,QAAI,MAAM,KAAK,MAAM,IAAI,GAAG;AAC1B,UAAI,EAAE,EAAE,KAAK,EAAE;AACf,UAAI,EAAE,EAAE,SAAS,IAAI,GAAI,MAAK;AAC9B,UAAI,KAAK,KAAK,CAAC;AACf,YAAM,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI;AACtC,UAAI,IAAIA,MAAK,KAAK,IAAI,IAAI,QAAQ,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC;AAAA,IAC5F,OAAO;AACL,UAAI,IAAIA,KAAI,IAAI,EAAE;AAAA,IACpB;AAEA,QAAI,EAAE,KAAKA,KAAI,MAAM;AAGrB,OAAG;AACD,UAAI;AACJ,UAAI,KAAK,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;AAAA,IACjC,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE;AAE7D,WAAO,MAAM,IAAIA,KAAI,MAAM,KAAK,EAAE,IAAI,GAAGA,KAAI,EAAE;AAAA,EACjD;AAMA,IAAE,QAAQ,EAAE,MAAM,SAAU,GAAG;AAC7B,QAAI,GACF,IAAI,MACJA,OAAM,EAAE,aACR,KAAK,EAAE,GACP,MAAM,IAAI,IAAIA,KAAI,CAAC,GAAG,GACtB,IAAI,GAAG,QACP,IAAI,GAAG,QACP,IAAI,EAAE,GACN,IAAI,EAAE;AAGR,MAAE,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI;AAGvB,QAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG;AACpB,QAAE,IAAI,CAAC,EAAE,IAAI,CAAC;AACd,aAAO;AAAA,IACT;AAGA,MAAE,IAAI,IAAI;AAGV,QAAI,IAAI,GAAG;AACT,UAAI;AACJ,WAAK;AACL,WAAK;AACL,UAAI;AACJ,UAAI;AACJ,UAAI;AAAA,IACN;AAGA,SAAK,IAAI,IAAI,MAAM,IAAI,IAAI,CAAC,GAAG,MAAM,GAAE,CAAC,IAAI;AAK5C,SAAK,IAAI,GAAG,OAAM;AAChB,UAAI;AAGJ,WAAK,IAAI,IAAI,GAAG,IAAI,KAAI;AAGtB,YAAI,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI;AACnC,UAAE,GAAG,IAAI,IAAI;AAGb,YAAI,IAAI,KAAK;AAAA,MACf;AAEA,QAAE,CAAC,IAAI;AAAA,IACT;AAGA,QAAI,EAAG,GAAE,EAAE;AAAA,QACN,GAAE,MAAM;AAGb,SAAK,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,IAAI,GAAE,IAAI;AACnC,MAAE,IAAI;AAEN,WAAO;AAAA,EACT;AAUA,IAAE,gBAAgB,SAAU,IAAI,IAAI;AAClC,QAAI,IAAI,MACN,IAAI,EAAE,EAAE,CAAC;AAEX,QAAI,OAAO,WAAW;AACpB,UAAI,OAAO,CAAC,CAAC,MAAM,KAAK,KAAK,KAAK,QAAQ;AACxC,cAAM,MAAM,UAAU;AAAA,MACxB;AACA,UAAI,MAAM,IAAI,EAAE,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE;AACxC,aAAO,EAAE,EAAE,SAAS,KAAK,GAAE,EAAE,KAAK,CAAC;AAAA,IACrC;AAEA,WAAO,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC;AAAA,EAC/B;AAaA,IAAE,UAAU,SAAU,IAAI,IAAI;AAC5B,QAAI,IAAI,MACN,IAAI,EAAE,EAAE,CAAC;AAEX,QAAI,OAAO,WAAW;AACpB,UAAI,OAAO,CAAC,CAAC,MAAM,KAAK,KAAK,KAAK,QAAQ;AACxC,cAAM,MAAM,UAAU;AAAA,MACxB;AACA,UAAI,MAAM,IAAI,EAAE,YAAY,CAAC,GAAG,KAAK,EAAE,IAAI,GAAG,EAAE;AAGhD,WAAK,KAAK,KAAK,EAAE,IAAI,GAAG,EAAE,EAAE,SAAS,KAAK,GAAE,EAAE,KAAK,CAAC;AAAA,IACtD;AAEA,WAAO,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC;AAAA,EAChC;AASA,IAAE,OAAO,IAAI,4BAA4B,CAAC,IAAI,EAAE,SAAS,EAAE,WAAW,WAAY;AAChF,QAAI,IAAI,MACNA,OAAM,EAAE;AACV,WAAO,UAAU,GAAG,EAAE,KAAKA,KAAI,MAAM,EAAE,KAAKA,KAAI,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAAA,EAC9D;AAMA,IAAE,WAAW,WAAY;AACvB,QAAI,IAAI,CAAC,UAAU,MAAM,MAAM,IAAI;AACnC,QAAI,KAAK,YAAY,WAAW,QAAQ,CAAC,KAAK,GAAG,EAAE,SAAS,CAAC,GAAG;AAC9D,YAAM,MAAM,OAAO,sBAAsB;AAAA,IAC3C;AACA,WAAO;AAAA,EACT;AAYA,IAAE,cAAc,SAAU,IAAI,IAAI;AAChC,QAAI,IAAI,MACNA,OAAM,EAAE,aACR,IAAI,EAAE,EAAE,CAAC;AAEX,QAAI,OAAO,WAAW;AACpB,UAAI,OAAO,CAAC,CAAC,MAAM,KAAK,KAAK,KAAK,QAAQ;AACxC,cAAM,MAAM,UAAU,WAAW;AAAA,MACnC;AACA,UAAI,MAAM,IAAIA,KAAI,CAAC,GAAG,IAAI,EAAE;AAC5B,aAAO,EAAE,EAAE,SAAS,KAAK,GAAE,EAAE,KAAK,CAAC;AAAA,IACrC;AAEA,WAAO,UAAU,GAAG,MAAM,EAAE,KAAK,EAAE,KAAKA,KAAI,MAAM,EAAE,KAAKA,KAAI,IAAI,CAAC,CAAC,CAAC;AAAA,EACtE;AASA,IAAE,UAAU,WAAY;AACtB,QAAI,IAAI,MACNA,OAAM,EAAE;AACV,QAAIA,KAAI,WAAW,MAAM;AACvB,YAAM,MAAM,OAAO,oBAAoB;AAAA,IACzC;AACA,WAAO,UAAU,GAAG,EAAE,KAAKA,KAAI,MAAM,EAAE,KAAKA,KAAI,IAAI,IAAI;AAAA,EAC1D;AAMO,MAAI,MAAM,MAAM;AAGvB,MAAO,cAAQ;;;AClgCf,MAAAC,eAAA;AAAA,WAAAA,cAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACWA,WAAS,QAAQ,GAAU;AACzB,WAAO,aAAa,cAAe,YAAY,OAAO,CAAC,KAAK,EAAE,YAAY,SAAS;EACrF;AAFS;AAKT,WAAS,OAAO,MAA8B,SAAiB;AAC7D,QAAI,CAAC,QAAQ,CAAC;AAAG,YAAM,IAAI,MAAM,qBAAqB;AACtD,QAAI,QAAQ,SAAS,KAAK,CAAC,QAAQ,SAAS,EAAE,MAAM;AAClD,YAAM,IAAI,MAAM,mCAAmC,UAAU,kBAAkB,EAAE,MAAM;EAC3F;AAJS;AAuBT,WAAS,QAAQ,UAAe,gBAAgB,MAAI;AAClD,QAAI,SAAS;AAAW,YAAM,IAAI,MAAM,kCAAkC;AAC1E,QAAI,iBAAiB,SAAS;AAAU,YAAM,IAAI,MAAM,uCAAuC;EACjG;AAHS;AAMT,WAAS,QAAQ,KAAU,UAAa;AACtC,WAAO,GAAG;AACV,UAAM,MAAM,SAAS;AACrB,QAAI,IAAI,SAAS,KAAK;AACpB,YAAM,IAAI,MAAM,2DAA2D,GAAG;IAChF;EACF;AANS;;;ACtCF,MAAMC,UACX,OAAO,eAAe,YAAY,YAAY,aAAa,WAAW,SAAS;;;ACyB3E,WAAU,WAAW,KAAe;AACxC,WAAO,IAAI,SAAS,IAAI,QAAQ,IAAI,YAAY,IAAI,UAAU;EAChE;AAFgB;AAKV,WAAU,KAAK,MAAc,OAAa;AAC9C,WAAQ,QAAS,KAAK,QAAW,SAAS;EAC5C;AAFgB;AAiHV,WAAU,YAAY,KAAW;AACrC,QAAI,OAAO,QAAQ;AAAU,YAAM,IAAI,MAAM,sCAAsC,OAAO,GAAG;AAC7F,WAAO,IAAI,WAAW,IAAI,YAAW,EAAG,OAAO,GAAG,CAAC;EACrD;AAHgB;AAYV,WAAU,QAAQ,MAAW;AACjC,QAAI,OAAO,SAAS;AAAU,aAAO,YAAY,IAAI;AACrD,WAAO,IAAI;AACX,WAAO;EACT;AAJgB;AA0BV,MAAgB,OAAhB,MAAoB;IA7L1B,OA6L0B;;;;IAsBxB,QAAK;AACH,aAAO,KAAK,WAAU;IACxB;;AAiCI,WAAU,gBACd,UAAuB;AAOvB,UAAM,QAAQ,wBAAC,QAA2B,SAAQ,EAAG,OAAO,QAAQ,GAAG,CAAC,EAAE,OAAM,GAAlE;AACd,UAAM,MAAM,SAAQ;AACpB,UAAM,YAAY,IAAI;AACtB,UAAM,WAAW,IAAI;AACrB,UAAM,SAAS,MAAM,SAAQ;AAC7B,WAAO;EACT;AAdgB;AAiDV,WAAU,YAAY,cAAc,IAAE;AAC1C,QAAIC,WAAU,OAAOA,QAAO,oBAAoB,YAAY;AAC1D,aAAOA,QAAO,gBAAgB,IAAI,WAAW,WAAW,CAAC;IAC3D;AAEA,QAAIA,WAAU,OAAOA,QAAO,gBAAgB,YAAY;AACtD,aAAOA,QAAO,YAAY,WAAW;IACvC;AACA,UAAM,IAAI,MAAM,wCAAwC;EAC1D;AATgB;;;AC/RV,WAAU,aACdC,OACA,YACA,OACA,MAAa;AAEb,QAAI,OAAOA,MAAK,iBAAiB;AAAY,aAAOA,MAAK,aAAa,YAAY,OAAO,IAAI;AAC7F,UAAMC,QAAO,OAAO,EAAE;AACtB,UAAM,WAAW,OAAO,UAAU;AAClC,UAAM,KAAK,OAAQ,SAASA,QAAQ,QAAQ;AAC5C,UAAM,KAAK,OAAO,QAAQ,QAAQ;AAClC,UAAM,IAAI,OAAO,IAAI;AACrB,UAAM,IAAI,OAAO,IAAI;AACrB,IAAAD,MAAK,UAAU,aAAa,GAAG,IAAI,IAAI;AACvC,IAAAA,MAAK,UAAU,aAAa,GAAG,IAAI,IAAI;EACzC;AAfgB;AAkBV,WAAU,IAAI,GAAW,GAAW,GAAS;AACjD,WAAQ,IAAI,IAAM,CAAC,IAAI;EACzB;AAFgB;AAKV,WAAU,IAAI,GAAW,GAAW,GAAS;AACjD,WAAQ,IAAI,IAAM,IAAI,IAAM,IAAI;EAClC;AAFgB;AAQV,MAAgB,SAAhB,cAAoD,KAAO;IAvCjE,OAuCiE;;;IAc/D,YACW,UACF,WACE,WACA,MAAa;AAEtB,YAAK;AALI,WAAA,WAAA;AACF,WAAA,YAAA;AACE,WAAA,YAAA;AACA,WAAA,OAAA;AATD,WAAA,WAAW;AACX,WAAA,SAAS;AACT,WAAA,MAAM;AACN,WAAA,YAAY;AASpB,WAAK,SAAS,IAAI,WAAW,QAAQ;AACrC,WAAK,OAAO,WAAW,KAAK,MAAM;IACpC;IACA,OAAO,MAAW;AAChB,cAAQ,IAAI;AACZ,YAAM,EAAE,MAAAA,OAAM,QAAQ,SAAQ,IAAK;AACnC,aAAO,QAAQ,IAAI;AACnB,YAAM,MAAM,KAAK;AACjB,eAAS,MAAM,GAAG,MAAM,OAAO;AAC7B,cAAM,OAAO,KAAK,IAAI,WAAW,KAAK,KAAK,MAAM,GAAG;AAEpD,YAAI,SAAS,UAAU;AACrB,gBAAM,WAAW,WAAW,IAAI;AAChC,iBAAO,YAAY,MAAM,KAAK,OAAO;AAAU,iBAAK,QAAQ,UAAU,GAAG;AACzE;QACF;AACA,eAAO,IAAI,KAAK,SAAS,KAAK,MAAM,IAAI,GAAG,KAAK,GAAG;AACnD,aAAK,OAAO;AACZ,eAAO;AACP,YAAI,KAAK,QAAQ,UAAU;AACzB,eAAK,QAAQA,OAAM,CAAC;AACpB,eAAK,MAAM;QACb;MACF;AACA,WAAK,UAAU,KAAK;AACpB,WAAK,WAAU;AACf,aAAO;IACT;IACA,WAAW,KAAe;AACxB,cAAQ,IAAI;AACZ,cAAQ,KAAK,IAAI;AACjB,WAAK,WAAW;AAIhB,YAAM,EAAE,QAAQ,MAAAA,OAAM,UAAU,KAAI,IAAK;AACzC,UAAI,EAAE,IAAG,IAAK;AAEd,aAAO,KAAK,IAAI;AAChB,WAAK,OAAO,SAAS,GAAG,EAAE,KAAK,CAAC;AAGhC,UAAI,KAAK,YAAY,WAAW,KAAK;AACnC,aAAK,QAAQA,OAAM,CAAC;AACpB,cAAM;MACR;AAEA,eAAS,IAAI,KAAK,IAAI,UAAU;AAAK,eAAO,CAAC,IAAI;AAIjD,mBAAaA,OAAM,WAAW,GAAG,OAAO,KAAK,SAAS,CAAC,GAAG,IAAI;AAC9D,WAAK,QAAQA,OAAM,CAAC;AACpB,YAAM,QAAQ,WAAW,GAAG;AAC5B,YAAM,MAAM,KAAK;AAEjB,UAAI,MAAM;AAAG,cAAM,IAAI,MAAM,6CAA6C;AAC1E,YAAM,SAAS,MAAM;AACrB,YAAME,SAAQ,KAAK,IAAG;AACtB,UAAI,SAASA,OAAM;AAAQ,cAAM,IAAI,MAAM,oCAAoC;AAC/E,eAAS,IAAI,GAAG,IAAI,QAAQ;AAAK,cAAM,UAAU,IAAI,GAAGA,OAAM,CAAC,GAAG,IAAI;IACxE;IACA,SAAM;AACJ,YAAM,EAAE,QAAQ,UAAS,IAAK;AAC9B,WAAK,WAAW,MAAM;AACtB,YAAM,MAAM,OAAO,MAAM,GAAG,SAAS;AACrC,WAAK,QAAO;AACZ,aAAO;IACT;IACA,WAAW,IAAM;AACf,aAAA,KAAO,IAAK,KAAK,YAAmB;AACpC,SAAG,IAAI,GAAG,KAAK,IAAG,CAAE;AACpB,YAAM,EAAE,UAAU,QAAQ,QAAQ,UAAU,WAAW,IAAG,IAAK;AAC/D,SAAG,SAAS;AACZ,SAAG,MAAM;AACT,SAAG,WAAW;AACd,SAAG,YAAY;AACf,UAAI,SAAS;AAAU,WAAG,OAAO,IAAI,MAAM;AAC3C,aAAO;IACT;;;;ACtIF,MAAM,aAA6B,uBAAO,KAAK,KAAK,CAAC;AACrD,MAAM,OAAuB,uBAAO,EAAE;AAEtC,WAAS,QACP,GACA,KAAK,OAAK;AAKV,QAAI;AAAI,aAAO,EAAE,GAAG,OAAO,IAAI,UAAU,GAAG,GAAG,OAAQ,KAAK,OAAQ,UAAU,EAAC;AAC/E,WAAO,EAAE,GAAG,OAAQ,KAAK,OAAQ,UAAU,IAAI,GAAG,GAAG,OAAO,IAAI,UAAU,IAAI,EAAC;EACjF;AATS;AAWT,WAAS,MAAM,KAAe,KAAK,OAAK;AACtC,QAAI,KAAK,IAAI,YAAY,IAAI,MAAM;AACnC,QAAI,KAAK,IAAI,YAAY,IAAI,MAAM;AACnC,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,YAAM,EAAE,GAAG,EAAC,IAAK,QAAQ,IAAI,CAAC,GAAG,EAAE;AACnC,OAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;IACxB;AACA,WAAO,CAAC,IAAI,EAAE;EAChB;AARS;AAUT,MAAM,QAAQ,wBAAC,GAAW,MAAuB,OAAO,MAAM,CAAC,KAAK,OAAQ,OAAO,MAAM,CAAC,GAA5E;AAEd,MAAM,QAAQ,wBAAC,GAAW,IAAY,MAAsB,MAAM,GAApD;AACd,MAAM,QAAQ,wBAAC,GAAW,GAAW,MAAuB,KAAM,KAAK,IAAO,MAAM,GAAtE;AAEd,MAAM,SAAS,wBAAC,GAAW,GAAW,MAAuB,MAAM,IAAM,KAAM,KAAK,GAArE;AACf,MAAM,SAAS,wBAAC,GAAW,GAAW,MAAuB,KAAM,KAAK,IAAO,MAAM,GAAtE;AAEf,MAAM,SAAS,wBAAC,GAAW,GAAW,MAAuB,KAAM,KAAK,IAAO,MAAO,IAAI,IAA3E;AACf,MAAM,SAAS,wBAAC,GAAW,GAAW,MAAuB,MAAO,IAAI,KAAQ,KAAM,KAAK,GAA5E;AAEf,MAAM,UAAU,wBAAC,IAAY,MAAsB,GAAnC;AAChB,MAAM,UAAU,wBAAC,GAAW,OAAuB,GAAnC;AAEhB,MAAM,SAAS,wBAAC,GAAW,GAAW,MAAuB,KAAK,IAAM,MAAO,KAAK,GAArE;AACf,MAAM,SAAS,wBAAC,GAAW,GAAW,MAAuB,KAAK,IAAM,MAAO,KAAK,GAArE;AAEf,MAAM,SAAS,wBAAC,GAAW,GAAW,MAAuB,KAAM,IAAI,KAAQ,MAAO,KAAK,GAA5E;AACf,MAAM,SAAS,wBAAC,GAAW,GAAW,MAAuB,KAAM,IAAI,KAAQ,MAAO,KAAK,GAA5E;AAIf,WAAS,IACP,IACA,IACA,IACA,IAAU;AAKV,UAAM,KAAK,OAAO,MAAM,OAAO;AAC/B,WAAO,EAAE,GAAI,KAAK,MAAO,IAAI,KAAK,KAAM,KAAM,GAAG,GAAG,IAAI,EAAC;EAC3D;AAXS;AAaT,MAAM,QAAQ,wBAAC,IAAY,IAAY,QAAwB,OAAO,MAAM,OAAO,MAAM,OAAO,IAAlF;AACd,MAAM,QAAQ,wBAAC,KAAa,IAAY,IAAY,OACjD,KAAK,KAAK,MAAO,MAAM,KAAK,KAAM,KAAM,GAD7B;AAEd,MAAM,QAAQ,wBAAC,IAAY,IAAY,IAAY,QAChD,OAAO,MAAM,OAAO,MAAM,OAAO,MAAM,OAAO,IADnC;AAEd,MAAM,QAAQ,wBAAC,KAAa,IAAY,IAAY,IAAY,OAC7D,KAAK,KAAK,KAAK,MAAO,MAAM,KAAK,KAAM,KAAM,GADlC;AAEd,MAAM,QAAQ,wBAAC,IAAY,IAAY,IAAY,IAAY,QAC5D,OAAO,MAAM,OAAO,MAAM,OAAO,MAAM,OAAO,MAAM,OAAO,IADhD;AAEd,MAAM,QAAQ,wBAAC,KAAa,IAAY,IAAY,IAAY,IAAY,OACzE,KAAK,KAAK,KAAK,KAAK,MAAO,MAAM,KAAK,KAAM,KAAM,GADvC;AAad,MAAM,MAAqpC;IACzpC;IAAS;IAAO;IAChB;IAAO;IACP;IAAQ;IAAQ;IAAQ;IACxB;IAAS;IACT;IAAQ;IAAQ;IAAQ;IACxB;IAAK;IAAO;IAAO;IAAO;IAAO;IAAO;;AAE1C,MAAA,cAAe;;;ACjFf,MAAM,CAAC,WAAW,SAAS,IAAqB,uBAAM,YAAI,MAAM;IAC9D;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE;IAAsB;IAAsB;IAAsB;IAClE,IAAI,OAAK,OAAO,CAAC,CAAC,CAAC,GAAE;AAGvB,MAAM,aAA6B,oBAAI,YAAY,EAAE;AACrD,MAAM,aAA6B,oBAAI,YAAY,EAAE;AAC/C,MAAO,SAAP,cAAsB,OAAc;IAvC1C,OAuC0C;;;IAsBxC,cAAA;AACE,YAAM,KAAK,IAAI,IAAI,KAAK;AAlBhB,WAAA,KAAa,aAAa;AAC1B,WAAA,KAAa,aAAa;AAC1B,WAAA,KAAa,aAAa;AAC1B,WAAA,KAAa,aAAa;AAC1B,WAAA,KAAa,aAAa;AAC1B,WAAA,KAAa,aAAa;AAC1B,WAAA,KAAa,aAAa;AAC1B,WAAA,KAAa,aAAa;AAC1B,WAAA,KAAa,aAAa;AAC1B,WAAA,KAAa,aAAa;AAC1B,WAAA,KAAa,aAAa;AAC1B,WAAA,KAAa,YAAa;AAC1B,WAAA,KAAa,YAAa;AAC1B,WAAA,KAAa,aAAa;AAC1B,WAAA,KAAa,aAAa;AAC1B,WAAA,KAAa,YAAa;IAIpC;;IAEU,MAAG;AAIX,YAAM,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,GAAE,IAAK;AAC3E,aAAO,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE;IACxE;;IAEU,IACR,IAAY,IAAY,IAAY,IAAY,IAAY,IAAY,IAAY,IACpF,IAAY,IAAY,IAAY,IAAY,IAAY,IAAY,IAAY,IAAU;AAE9F,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;AACf,WAAK,KAAK,KAAK;IACjB;IACU,QAAQC,OAAgB,QAAc;AAE9C,eAAS,IAAI,GAAG,IAAI,IAAI,KAAK,UAAU,GAAG;AACxC,mBAAW,CAAC,IAAIA,MAAK,UAAU,MAAM;AACrC,mBAAW,CAAC,IAAIA,MAAK,UAAW,UAAU,CAAE;MAC9C;AACA,eAAS,IAAI,IAAI,IAAI,IAAI,KAAK;AAE5B,cAAM,OAAO,WAAW,IAAI,EAAE,IAAI;AAClC,cAAM,OAAO,WAAW,IAAI,EAAE,IAAI;AAClC,cAAM,MAAM,YAAI,OAAO,MAAM,MAAM,CAAC,IAAI,YAAI,OAAO,MAAM,MAAM,CAAC,IAAI,YAAI,MAAM,MAAM,MAAM,CAAC;AAC3F,cAAM,MAAM,YAAI,OAAO,MAAM,MAAM,CAAC,IAAI,YAAI,OAAO,MAAM,MAAM,CAAC,IAAI,YAAI,MAAM,MAAM,MAAM,CAAC;AAE3F,cAAM,MAAM,WAAW,IAAI,CAAC,IAAI;AAChC,cAAM,MAAM,WAAW,IAAI,CAAC,IAAI;AAChC,cAAM,MAAM,YAAI,OAAO,KAAK,KAAK,EAAE,IAAI,YAAI,OAAO,KAAK,KAAK,EAAE,IAAI,YAAI,MAAM,KAAK,KAAK,CAAC;AACvF,cAAM,MAAM,YAAI,OAAO,KAAK,KAAK,EAAE,IAAI,YAAI,OAAO,KAAK,KAAK,EAAE,IAAI,YAAI,MAAM,KAAK,KAAK,CAAC;AAEvF,cAAM,OAAO,YAAI,MAAM,KAAK,KAAK,WAAW,IAAI,CAAC,GAAG,WAAW,IAAI,EAAE,CAAC;AACtE,cAAM,OAAO,YAAI,MAAM,MAAM,KAAK,KAAK,WAAW,IAAI,CAAC,GAAG,WAAW,IAAI,EAAE,CAAC;AAC5E,mBAAW,CAAC,IAAI,OAAO;AACvB,mBAAW,CAAC,IAAI,OAAO;MACzB;AACA,UAAI,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,GAAE,IAAK;AAEzE,eAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAE3B,cAAM,UAAU,YAAI,OAAO,IAAI,IAAI,EAAE,IAAI,YAAI,OAAO,IAAI,IAAI,EAAE,IAAI,YAAI,OAAO,IAAI,IAAI,EAAE;AACvF,cAAM,UAAU,YAAI,OAAO,IAAI,IAAI,EAAE,IAAI,YAAI,OAAO,IAAI,IAAI,EAAE,IAAI,YAAI,OAAO,IAAI,IAAI,EAAE;AAEvF,cAAM,OAAQ,KAAK,KAAO,CAAC,KAAK;AAChC,cAAM,OAAQ,KAAK,KAAO,CAAC,KAAK;AAGhC,cAAM,OAAO,YAAI,MAAM,IAAI,SAAS,MAAM,UAAU,CAAC,GAAG,WAAW,CAAC,CAAC;AACrE,cAAM,MAAM,YAAI,MAAM,MAAM,IAAI,SAAS,MAAM,UAAU,CAAC,GAAG,WAAW,CAAC,CAAC;AAC1E,cAAM,MAAM,OAAO;AAEnB,cAAM,UAAU,YAAI,OAAO,IAAI,IAAI,EAAE,IAAI,YAAI,OAAO,IAAI,IAAI,EAAE,IAAI,YAAI,OAAO,IAAI,IAAI,EAAE;AACvF,cAAM,UAAU,YAAI,OAAO,IAAI,IAAI,EAAE,IAAI,YAAI,OAAO,IAAI,IAAI,EAAE,IAAI,YAAI,OAAO,IAAI,IAAI,EAAE;AACvF,cAAM,OAAQ,KAAK,KAAO,KAAK,KAAO,KAAK;AAC3C,cAAM,OAAQ,KAAK,KAAO,KAAK,KAAO,KAAK;AAC3C,aAAK,KAAK;AACV,aAAK,KAAK;AACV,aAAK,KAAK;AACV,aAAK,KAAK;AACV,aAAK,KAAK;AACV,aAAK,KAAK;AACV,SAAC,EAAE,GAAG,IAAI,GAAG,GAAE,IAAK,YAAI,IAAI,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAC5D,aAAK,KAAK;AACV,aAAK,KAAK;AACV,aAAK,KAAK;AACV,aAAK,KAAK;AACV,aAAK,KAAK;AACV,aAAK,KAAK;AACV,cAAM,MAAM,YAAI,MAAM,KAAK,SAAS,IAAI;AACxC,aAAK,YAAI,MAAM,KAAK,KAAK,SAAS,IAAI;AACtC,aAAK,MAAM;MACb;AAEA,OAAC,EAAE,GAAG,IAAI,GAAG,GAAE,IAAK,YAAI,IAAI,KAAK,KAAK,GAAG,KAAK,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AACpE,OAAC,EAAE,GAAG,IAAI,GAAG,GAAE,IAAK,YAAI,IAAI,KAAK,KAAK,GAAG,KAAK,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AACpE,OAAC,EAAE,GAAG,IAAI,GAAG,GAAE,IAAK,YAAI,IAAI,KAAK,KAAK,GAAG,KAAK,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AACpE,OAAC,EAAE,GAAG,IAAI,GAAG,GAAE,IAAK,YAAI,IAAI,KAAK,KAAK,GAAG,KAAK,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AACpE,OAAC,EAAE,GAAG,IAAI,GAAG,GAAE,IAAK,YAAI,IAAI,KAAK,KAAK,GAAG,KAAK,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AACpE,OAAC,EAAE,GAAG,IAAI,GAAG,GAAE,IAAK,YAAI,IAAI,KAAK,KAAK,GAAG,KAAK,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AACpE,OAAC,EAAE,GAAG,IAAI,GAAG,GAAE,IAAK,YAAI,IAAI,KAAK,KAAK,GAAG,KAAK,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AACpE,OAAC,EAAE,GAAG,IAAI,GAAG,GAAE,IAAK,YAAI,IAAI,KAAK,KAAK,GAAG,KAAK,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AACpE,WAAK,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE;IACzE;IACU,aAAU;AAClB,iBAAW,KAAK,CAAC;AACjB,iBAAW,KAAK,CAAC;IACnB;IACA,UAAO;AACL,WAAK,OAAO,KAAK,CAAC;AAClB,WAAK,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACzD;;AA+EK,MAAM,SAAgC,gCAAgB,MAAM,IAAI,OAAM,CAAE;;;AChP/E,MAAM,MAAsB,uBAAO,CAAC;AACpC,MAAM,MAAsB,uBAAO,CAAC;AACpC,MAAM,MAAsB,uBAAO,CAAC;AAW9B,WAAUC,SAAQ,GAAU;AAChC,WAAO,aAAa,cAAe,YAAY,OAAO,CAAC,KAAK,EAAE,YAAY,SAAS;EACrF;AAFgB,SAAAA,UAAA;AAIV,WAAUC,QAAO,MAAa;AAClC,QAAI,CAACD,SAAQ,IAAI;AAAG,YAAM,IAAI,MAAM,qBAAqB;EAC3D;AAFgB,SAAAC,SAAA;AAIV,WAAU,MAAM,OAAe,OAAc;AACjD,QAAI,OAAO,UAAU;AAAW,YAAM,IAAI,MAAM,QAAQ,4BAA4B,KAAK;EAC3F;AAFgB;AAKhB,MAAM,QAAwB,sBAAM,KAAK,EAAE,QAAQ,IAAG,GAAI,CAAC,GAAG,MAC5D,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC;AAK3B,WAAU,WAAW,OAAiB;AAC1C,IAAAA,QAAO,KAAK;AAEZ,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,aAAO,MAAM,MAAM,CAAC,CAAC;IACvB;AACA,WAAO;EACT;AARgB;AAeV,WAAU,YAAY,KAAW;AACrC,QAAI,OAAO,QAAQ;AAAU,YAAM,IAAI,MAAM,8BAA8B,OAAO,GAAG;AACrF,WAAO,QAAQ,KAAK,MAAM,OAAO,OAAO,GAAG;EAC7C;AAHgB;AAMhB,MAAM,SAAS,EAAE,IAAI,IAAI,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAG;AAC5D,WAAS,cAAc,IAAU;AAC/B,QAAI,MAAM,OAAO,MAAM,MAAM,OAAO;AAAI,aAAO,KAAK,OAAO;AAC3D,QAAI,MAAM,OAAO,KAAK,MAAM,OAAO;AAAG,aAAO,MAAM,OAAO,IAAI;AAC9D,QAAI,MAAM,OAAO,KAAK,MAAM,OAAO;AAAG,aAAO,MAAM,OAAO,IAAI;AAC9D;EACF;AALS;AAUH,WAAU,WAAW,KAAW;AACpC,QAAI,OAAO,QAAQ;AAAU,YAAM,IAAI,MAAM,8BAA8B,OAAO,GAAG;AACrF,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,KAAK;AAChB,QAAI,KAAK;AAAG,YAAM,IAAI,MAAM,qDAAqD,EAAE;AACnF,UAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,aAAS,KAAK,GAAG,KAAK,GAAG,KAAK,IAAI,MAAM,MAAM,GAAG;AAC/C,YAAM,KAAK,cAAc,IAAI,WAAW,EAAE,CAAC;AAC3C,YAAM,KAAK,cAAc,IAAI,WAAW,KAAK,CAAC,CAAC;AAC/C,UAAI,OAAO,UAAa,OAAO,QAAW;AACxC,cAAM,OAAO,IAAI,EAAE,IAAI,IAAI,KAAK,CAAC;AACjC,cAAM,IAAI,MAAM,iDAAiD,OAAO,gBAAgB,EAAE;MAC5F;AACA,YAAM,EAAE,IAAI,KAAK,KAAK;IACxB;AACA,WAAO;EACT;AAhBgB;AAmBV,WAAU,gBAAgB,OAAiB;AAC/C,WAAO,YAAY,WAAW,KAAK,CAAC;EACtC;AAFgB;AAGV,WAAU,gBAAgB,OAAiB;AAC/C,IAAAC,QAAO,KAAK;AACZ,WAAO,YAAY,WAAW,WAAW,KAAK,KAAK,EAAE,QAAO,CAAE,CAAC;EACjE;AAHgB;AAKV,WAAU,gBAAgB,GAAoB,KAAW;AAC7D,WAAO,WAAW,EAAE,SAAS,EAAE,EAAE,SAAS,MAAM,GAAG,GAAG,CAAC;EACzD;AAFgB;AAGV,WAAU,gBAAgB,GAAoB,KAAW;AAC7D,WAAO,gBAAgB,GAAG,GAAG,EAAE,QAAO;EACxC;AAFgB;AAiBV,WAAU,YAAY,OAAe,KAAU,gBAAuB;AAC1E,QAAI;AACJ,QAAI,OAAO,QAAQ,UAAU;AAC3B,UAAI;AACF,cAAM,WAAW,GAAG;MACtB,SAAS,GAAG;AACV,cAAM,IAAI,MAAM,QAAQ,+CAA+C,CAAC;MAC1E;IACF,WAAWC,SAAQ,GAAG,GAAG;AAGvB,YAAM,WAAW,KAAK,GAAG;IAC3B,OAAO;AACL,YAAM,IAAI,MAAM,QAAQ,mCAAmC;IAC7D;AACA,UAAM,MAAM,IAAI;AAChB,QAAI,OAAO,mBAAmB,YAAY,QAAQ;AAChD,YAAM,IAAI,MAAM,QAAQ,gBAAgB,iBAAiB,oBAAoB,GAAG;AAClF,WAAO;EACT;AAnBgB;AAwBV,WAAU,eAAe,QAAoB;AACjD,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,IAAI,OAAO,CAAC;AAClB,MAAAC,QAAO,CAAC;AACR,aAAO,EAAE;IACX;AACA,UAAM,MAAM,IAAI,WAAW,GAAG;AAC9B,aAAS,IAAI,GAAG,MAAM,GAAG,IAAI,OAAO,QAAQ,KAAK;AAC/C,YAAM,IAAI,OAAO,CAAC;AAClB,UAAI,IAAI,GAAG,GAAG;AACd,aAAO,EAAE;IACX;AACA,WAAO;EACT;AAdgB;AAqChB,MAAM,WAAW,wBAAC,MAAc,OAAO,MAAM,YAAY,OAAO,GAA/C;AAEX,WAAU,QAAQ,GAAW,KAAa,KAAW;AACzD,WAAO,SAAS,CAAC,KAAK,SAAS,GAAG,KAAK,SAAS,GAAG,KAAK,OAAO,KAAK,IAAI;EAC1E;AAFgB;AASV,WAAU,SAAS,OAAe,GAAW,KAAa,KAAW;AAMzE,QAAI,CAAC,QAAQ,GAAG,KAAK,GAAG;AACtB,YAAM,IAAI,MAAM,oBAAoB,QAAQ,OAAO,MAAM,aAAa,MAAM,WAAW,CAAC;EAC5F;AARgB;AAgBV,WAAU,OAAO,GAAS;AAC9B,QAAI;AACJ,SAAK,MAAM,GAAG,IAAI,KAAK,MAAM,KAAK,OAAO;AAAE;AAC3C,WAAO;EACT;AAJgB;AA0BT,MAAM,UAAU,wBAAC,OAAuB,OAAO,OAAO,IAAI,CAAC,KAAK,KAAhD;AAkEvB,MAAM,eAAe;IACnB,QAAQ,wBAAC,QAAsB,OAAO,QAAQ,UAAtC;IACR,UAAU,wBAAC,QAAsB,OAAO,QAAQ,YAAtC;IACV,SAAS,wBAAC,QAAsB,OAAO,QAAQ,WAAtC;IACT,QAAQ,wBAAC,QAAsB,OAAO,QAAQ,UAAtC;IACR,oBAAoB,wBAAC,QAAsB,OAAO,QAAQ,YAAYC,SAAQ,GAAG,GAA7D;IACpB,eAAe,wBAAC,QAAsB,OAAO,cAAc,GAAG,GAA/C;IACf,OAAO,wBAAC,QAAsB,MAAM,QAAQ,GAAG,GAAxC;IACP,OAAO,wBAAC,KAAU,WAAsB,OAAe,GAAG,QAAQ,GAAG,GAA9D;IACP,MAAM,wBAAC,QAAsB,OAAO,QAAQ,cAAc,OAAO,cAAc,IAAI,SAAS,GAAtF;;AAMF,WAAU,eACd,QACA,YACA,gBAA2B,CAAA,GAAE;AAE7B,UAAM,aAAa,wBAAC,WAAoB,MAAiB,eAAuB;AAC9E,YAAM,WAAW,aAAa,IAAI;AAClC,UAAI,OAAO,aAAa;AAAY,cAAM,IAAI,MAAM,4BAA4B;AAEhF,YAAM,MAAM,OAAO,SAAgC;AACnD,UAAI,cAAc,QAAQ;AAAW;AACrC,UAAI,CAAC,SAAS,KAAK,MAAM,GAAG;AAC1B,cAAM,IAAI,MACR,WAAW,OAAO,SAAS,IAAI,2BAA2B,OAAO,WAAW,GAAG;MAEnF;IACF,GAXmB;AAYnB,eAAW,CAAC,WAAW,IAAI,KAAK,OAAO,QAAQ,UAAU;AAAG,iBAAW,WAAW,MAAO,KAAK;AAC9F,eAAW,CAAC,WAAW,IAAI,KAAK,OAAO,QAAQ,aAAa;AAAG,iBAAW,WAAW,MAAO,IAAI;AAChG,WAAO;EACT;AApBgB;AAyCV,WAAU,SACd,IAA6B;AAE7B,UAAM,MAAM,oBAAI,QAAO;AACvB,WAAO,CAAC,QAAW,SAAc;AAC/B,YAAM,MAAM,IAAI,IAAI,GAAG;AACvB,UAAI,QAAQ;AAAW,eAAO;AAC9B,YAAM,WAAW,GAAG,KAAK,GAAG,IAAI;AAChC,UAAI,IAAI,KAAK,QAAQ;AACrB,aAAO;IACT;EACF;AAXgB;;;ACnVhB,MAAMC,OAAM,OAAO,CAAC;AAApB,MAAuBC,OAAM,OAAO,CAAC;AAArC,MAAwCC,OAAsB,uBAAO,CAAC;AAAtE,MAAyE,MAAsB,uBAAO,CAAC;AAEvG,MAAM,MAAsB,uBAAO,CAAC;AAApC,MAAuC,MAAsB,uBAAO,CAAC;AAArE,MAAwE,MAAsB,uBAAO,CAAC;AAEtG,MAAM,MAAqB,uBAAO,CAAC;AAAnC,MAAsC,OAAuB,uBAAO,EAAE;AAGhE,WAAU,IAAI,GAAW,GAAS;AACtC,UAAM,SAAS,IAAI;AACnB,WAAO,UAAUF,OAAM,SAAS,IAAI;EACtC;AAHgB;AAWV,WAAU,IAAI,KAAa,OAAe,QAAc;AAC5D,QAAI,QAAQA;AAAK,YAAM,IAAI,MAAM,yCAAyC;AAC1E,QAAI,UAAUA;AAAK,YAAM,IAAI,MAAM,iBAAiB;AACpD,QAAI,WAAWC;AAAK,aAAOD;AAC3B,QAAI,MAAMC;AACV,WAAO,QAAQD,MAAK;AAClB,UAAI,QAAQC;AAAK,cAAO,MAAM,MAAO;AACrC,YAAO,MAAM,MAAO;AACpB,gBAAUA;IACZ;AACA,WAAO;EACT;AAXgB;AAcV,WAAU,KAAK,GAAW,OAAe,QAAc;AAC3D,QAAI,MAAM;AACV,WAAO,UAAUD,MAAK;AACpB,aAAO;AACP,aAAO;IACT;AACA,WAAO;EACT;AAPgB;AAaV,WAAU,OAAO,QAAgB,QAAc;AACnD,QAAI,WAAWA;AAAK,YAAM,IAAI,MAAM,kCAAkC;AACtE,QAAI,UAAUA;AAAK,YAAM,IAAI,MAAM,4CAA4C,MAAM;AAErF,QAAI,IAAI,IAAI,QAAQ,MAAM;AAC1B,QAAI,IAAI;AAER,QAAI,IAAIA,MAAK,IAAIC,MAAK,IAAIA,MAAK,IAAID;AACnC,WAAO,MAAMA,MAAK;AAEhB,YAAM,IAAI,IAAI;AACd,YAAM,IAAI,IAAI;AACd,YAAM,IAAI,IAAI,IAAI;AAClB,YAAM,IAAI,IAAI,IAAI;AAElB,UAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI;IACzC;AACA,UAAM,MAAM;AACZ,QAAI,QAAQC;AAAK,YAAM,IAAI,MAAM,wBAAwB;AACzD,WAAO,IAAI,GAAG,MAAM;EACtB;AApBgB;AA8BV,WAAU,cAAcE,IAAS;AAMrC,UAAM,aAAaA,KAAIF,QAAOC;AAE9B,QAAI,GAAW,GAAW;AAG1B,SAAK,IAAIC,KAAIF,MAAK,IAAI,GAAG,IAAIC,SAAQF,MAAK,KAAKE,MAAK;AAAI;AAGxD,SAAK,IAAIA,MAAK,IAAIC,MAAK,IAAI,GAAG,WAAWA,EAAC,MAAMA,KAAIF,MAAK,KAAK;AAE5D,UAAI,IAAI;AAAM,cAAM,IAAI,MAAM,6CAA6C;IAC7E;AAGA,QAAI,MAAM,GAAG;AACX,YAAM,UAAUE,KAAIF,QAAO;AAC3B,aAAO,gCAAS,YAAeG,KAAe,GAAI;AAChD,cAAM,OAAOA,IAAG,IAAI,GAAG,MAAM;AAC7B,YAAI,CAACA,IAAG,IAAIA,IAAG,IAAI,IAAI,GAAG,CAAC;AAAG,gBAAM,IAAI,MAAM,yBAAyB;AACvE,eAAO;MACT,GAJO;IAKT;AAGA,UAAM,UAAU,IAAIH,QAAOC;AAC3B,WAAO,gCAAS,YAAeE,KAAe,GAAI;AAEhD,UAAIA,IAAG,IAAI,GAAG,SAAS,MAAMA,IAAG,IAAIA,IAAG,GAAG;AAAG,cAAM,IAAI,MAAM,yBAAyB;AACtF,UAAI,IAAI;AAER,UAAI,IAAIA,IAAG,IAAIA,IAAG,IAAIA,IAAG,KAAK,CAAC,GAAG,CAAC;AACnC,UAAI,IAAIA,IAAG,IAAI,GAAG,MAAM;AACxB,UAAI,IAAIA,IAAG,IAAI,GAAG,CAAC;AAEnB,aAAO,CAACA,IAAG,IAAI,GAAGA,IAAG,GAAG,GAAG;AACzB,YAAIA,IAAG,IAAI,GAAGA,IAAG,IAAI;AAAG,iBAAOA,IAAG;AAElC,YAAI,IAAI;AACR,iBAAS,KAAKA,IAAG,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK;AACnC,cAAIA,IAAG,IAAI,IAAIA,IAAG,GAAG;AAAG;AACxB,eAAKA,IAAG,IAAI,EAAE;QAChB;AAEA,cAAM,KAAKA,IAAG,IAAI,GAAGH,QAAO,OAAO,IAAI,IAAI,CAAC,CAAC;AAC7C,YAAIG,IAAG,IAAI,EAAE;AACb,YAAIA,IAAG,IAAI,GAAG,EAAE;AAChB,YAAIA,IAAG,IAAI,GAAG,CAAC;AACf,YAAI;MACN;AACA,aAAO;IACT,GAzBO;EA0BT;AAzDgB;AAsEV,WAAU,OAAOD,IAAS;AAG9B,QAAIA,KAAI,QAAQ,KAAK;AAKnB,YAAM,UAAUA,KAAIF,QAAO;AAC3B,aAAO,gCAAS,UAAaG,KAAe,GAAI;AAC9C,cAAM,OAAOA,IAAG,IAAI,GAAG,MAAM;AAE7B,YAAI,CAACA,IAAG,IAAIA,IAAG,IAAI,IAAI,GAAG,CAAC;AAAG,gBAAM,IAAI,MAAM,yBAAyB;AACvE,eAAO;MACT,GALO;IAMT;AAGA,QAAID,KAAI,QAAQ,KAAK;AACnB,YAAM,MAAMA,KAAI,OAAO;AACvB,aAAO,gCAAS,UAAaC,KAAe,GAAI;AAC9C,cAAM,KAAKA,IAAG,IAAI,GAAGF,IAAG;AACxB,cAAM,IAAIE,IAAG,IAAI,IAAI,EAAE;AACvB,cAAM,KAAKA,IAAG,IAAI,GAAG,CAAC;AACtB,cAAM,IAAIA,IAAG,IAAIA,IAAG,IAAI,IAAIF,IAAG,GAAG,CAAC;AACnC,cAAM,OAAOE,IAAG,IAAI,IAAIA,IAAG,IAAI,GAAGA,IAAG,GAAG,CAAC;AACzC,YAAI,CAACA,IAAG,IAAIA,IAAG,IAAI,IAAI,GAAG,CAAC;AAAG,gBAAM,IAAI,MAAM,yBAAyB;AACvE,eAAO;MACT,GARO;IAST;AAGA,QAAID,KAAI,SAAS,KAAK;IAoBtB;AAEA,WAAO,cAAcA,EAAC;EACxB;AAvDgB;AA0DT,MAAM,eAAe,wBAAC,KAAa,YACvC,IAAI,KAAK,MAAM,IAAIF,UAASA,MADH;AA+C5B,MAAM,eAAe;IACnB;IAAU;IAAW;IAAO;IAAO;IAAO;IAAQ;IAClD;IAAO;IAAO;IAAO;IAAO;IAAO;IACnC;IAAQ;IAAQ;IAAQ;;AAEpB,WAAU,cAAiB,OAAgB;AAC/C,UAAM,UAAU;MACd,OAAO;MACP,MAAM;MACN,OAAO;MACP,MAAM;;AAER,UAAM,OAAO,aAAa,OAAO,CAAC,KAAK,QAAe;AACpD,UAAI,GAAG,IAAI;AACX,aAAO;IACT,GAAG,OAAO;AACV,WAAO,eAAe,OAAO,IAAI;EACnC;AAZgB;AAoBV,WAAU,MAAS,GAAc,KAAQ,OAAa;AAG1D,QAAI,QAAQD;AAAK,YAAM,IAAI,MAAM,yCAAyC;AAC1E,QAAI,UAAUA;AAAK,aAAO,EAAE;AAC5B,QAAI,UAAUC;AAAK,aAAO;AAC1B,QAAI,IAAI,EAAE;AACV,QAAI,IAAI;AACR,WAAO,QAAQD,MAAK;AAClB,UAAI,QAAQC;AAAK,YAAI,EAAE,IAAI,GAAG,CAAC;AAC/B,UAAI,EAAE,IAAI,CAAC;AACX,gBAAUA;IACZ;AACA,WAAO;EACT;AAdgB;AAoBV,WAAU,cAAiB,GAAc,MAAS;AACtD,UAAM,MAAM,IAAI,MAAM,KAAK,MAAM;AAEjC,UAAM,iBAAiB,KAAK,OAAO,CAAC,KAAK,KAAK,MAAK;AACjD,UAAI,EAAE,IAAI,GAAG;AAAG,eAAO;AACvB,UAAI,CAAC,IAAI;AACT,aAAO,EAAE,IAAI,KAAK,GAAG;IACvB,GAAG,EAAE,GAAG;AAER,UAAM,WAAW,EAAE,IAAI,cAAc;AAErC,SAAK,YAAY,CAAC,KAAK,KAAK,MAAK;AAC/B,UAAI,EAAE,IAAI,GAAG;AAAG,eAAO;AACvB,UAAI,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC;AAC1B,aAAO,EAAE,IAAI,KAAK,GAAG;IACvB,GAAG,QAAQ;AACX,WAAO;EACT;AAjBgB;AA4CV,WAAU,QACd,GACA,YAAmB;AAMnB,UAAM,cAAc,eAAe,SAAY,aAAa,EAAE,SAAS,CAAC,EAAE;AAC1E,UAAM,cAAc,KAAK,KAAK,cAAc,CAAC;AAC7C,WAAO,EAAE,YAAY,aAAa,YAAW;EAC/C;AAXgB;AA6BV,WAAU,MACd,OACAI,SACA,OAAO,OACP,QAAiC,CAAA,GAAE;AAEnC,QAAI,SAASC;AAAK,YAAM,IAAI,MAAM,4CAA4C,KAAK;AACnF,UAAM,EAAE,YAAY,MAAM,aAAa,MAAK,IAAK,QAAQ,OAAOD,OAAM;AACtE,QAAI,QAAQ;AAAM,YAAM,IAAI,MAAM,gDAAgD;AAClF,QAAI;AACJ,UAAM,IAAuB,OAAO,OAAO;MACzC;MACA;MACA;MACA;MACA,MAAM,QAAQ,IAAI;MAClB,MAAMC;MACN,KAAKC;MACL,QAAQ,wBAAC,QAAQ,IAAI,KAAK,KAAK,GAAvB;MACR,SAAS,wBAAC,QAAO;AACf,YAAI,OAAO,QAAQ;AACjB,gBAAM,IAAI,MAAM,iDAAiD,OAAO,GAAG;AAC7E,eAAOD,QAAO,OAAO,MAAM;MAC7B,GAJS;MAKT,KAAK,wBAAC,QAAQ,QAAQA,MAAjB;MACL,OAAO,wBAAC,SAAS,MAAMC,UAASA,MAAzB;MACP,KAAK,wBAAC,QAAQ,IAAI,CAAC,KAAK,KAAK,GAAxB;MACL,KAAK,wBAAC,KAAK,QAAQ,QAAQ,KAAtB;MAEL,KAAK,wBAAC,QAAQ,IAAI,MAAM,KAAK,KAAK,GAA7B;MACL,KAAK,wBAAC,KAAK,QAAQ,IAAI,MAAM,KAAK,KAAK,GAAlC;MACL,KAAK,wBAAC,KAAK,QAAQ,IAAI,MAAM,KAAK,KAAK,GAAlC;MACL,KAAK,wBAAC,KAAK,QAAQ,IAAI,MAAM,KAAK,KAAK,GAAlC;MACL,KAAK,wBAAC,KAAK,UAAU,MAAM,GAAG,KAAK,KAAK,GAAnC;MACL,KAAK,wBAAC,KAAK,QAAQ,IAAI,MAAM,OAAO,KAAK,KAAK,GAAG,KAAK,GAAjD;;MAGL,MAAM,wBAAC,QAAQ,MAAM,KAAf;MACN,MAAM,wBAAC,KAAK,QAAQ,MAAM,KAApB;MACN,MAAM,wBAAC,KAAK,QAAQ,MAAM,KAApB;MACN,MAAM,wBAAC,KAAK,QAAQ,MAAM,KAApB;MAEN,KAAK,wBAAC,QAAQ,OAAO,KAAK,KAAK,GAA1B;MACL,MACE,MAAM,SACL,CAAC,MAAK;AACL,YAAI,CAAC;AAAO,kBAAQ,OAAO,KAAK;AAChC,eAAO,MAAM,GAAG,CAAC;MACnB;MACF,aAAa,wBAAC,QAAQ,cAAc,GAAG,GAAG,GAA7B;;;MAGb,MAAM,wBAAC,GAAG,GAAG,MAAO,IAAI,IAAI,GAAtB;MACN,SAAS,wBAAC,QAAS,OAAO,gBAAgB,KAAK,KAAK,IAAI,gBAAgB,KAAK,KAAK,GAAzE;MACT,WAAW,wBAAC,UAAS;AACnB,YAAI,MAAM,WAAW;AACnB,gBAAM,IAAI,MAAM,+BAA+B,QAAQ,iBAAiB,MAAM,MAAM;AACtF,eAAO,OAAO,gBAAgB,KAAK,IAAI,gBAAgB,KAAK;MAC9D,GAJW;KAKD;AACZ,WAAO,OAAO,OAAO,CAAC;EACxB;AA7DgB;;;ACzXhB,MAAMC,OAAM,OAAO,CAAC;AACpB,MAAMC,OAAM,OAAO,CAAC;AAsBpB,WAAS,gBAAoC,WAAoB,MAAO;AACtE,UAAM,MAAM,KAAK,OAAM;AACvB,WAAO,YAAY,MAAM;EAC3B;AAHS;AAKT,WAAS,UAAU,GAAW,MAAY;AACxC,QAAI,CAAC,OAAO,cAAc,CAAC,KAAK,KAAK,KAAK,IAAI;AAC5C,YAAM,IAAI,MAAM,uCAAuC,OAAO,cAAc,CAAC;EACjF;AAHS;AAKT,WAAS,UAAU,GAAW,MAAY;AACxC,cAAU,GAAG,IAAI;AACjB,UAAM,UAAU,KAAK,KAAK,OAAO,CAAC,IAAI;AACtC,UAAM,aAAa,MAAM,IAAI;AAC7B,WAAO,EAAE,SAAS,WAAU;EAC9B;AALS;AAOT,WAAS,kBAAkB,QAAe,GAAM;AAC9C,QAAI,CAAC,MAAM,QAAQ,MAAM;AAAG,YAAM,IAAI,MAAM,gBAAgB;AAC5D,WAAO,QAAQ,CAAC,GAAG,MAAK;AACtB,UAAI,EAAE,aAAa;AAAI,cAAM,IAAI,MAAM,4BAA4B,CAAC;IACtE,CAAC;EACH;AALS;AAMT,WAAS,mBAAmB,SAAgB,OAAU;AACpD,QAAI,CAAC,MAAM,QAAQ,OAAO;AAAG,YAAM,IAAI,MAAM,2BAA2B;AACxE,YAAQ,QAAQ,CAAC,GAAG,MAAK;AACvB,UAAI,CAAC,MAAM,QAAQ,CAAC;AAAG,cAAM,IAAI,MAAM,6BAA6B,CAAC;IACvE,CAAC;EACH;AALS;AAST,MAAM,mBAAmB,oBAAI,QAAO;AACpC,MAAM,mBAAmB,oBAAI,QAAO;AAEpC,WAAS,KAAKC,IAAM;AAClB,WAAO,iBAAiB,IAAIA,EAAC,KAAK;EACpC;AAFS;AA+BH,WAAU,KAAyB,GAAwB,MAAY;AAC3E,WAAO;MACL;MAEA,eAAe,KAAM;AACnB,eAAO,KAAK,GAAG,MAAM;MACvB;;MAGA,aAAa,KAAQ,GAAW,IAAI,EAAE,MAAI;AACxC,YAAI,IAAO;AACX,eAAO,IAAIF,MAAK;AACd,cAAI,IAAIC;AAAK,gBAAI,EAAE,IAAI,CAAC;AACxB,cAAI,EAAE,OAAM;AACZ,gBAAMA;QACR;AACA,eAAO;MACT;;;;;;;;;;;;;MAcA,iBAAiB,KAAQ,GAAS;AAChC,cAAM,EAAE,SAAS,WAAU,IAAK,UAAU,GAAG,IAAI;AACjD,cAAM,SAAc,CAAA;AACpB,YAAI,IAAO;AACX,YAAI,OAAO;AACX,iBAASE,UAAS,GAAGA,UAAS,SAASA,WAAU;AAC/C,iBAAO;AACP,iBAAO,KAAK,IAAI;AAEhB,mBAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,mBAAO,KAAK,IAAI,CAAC;AACjB,mBAAO,KAAK,IAAI;UAClB;AACA,cAAI,KAAK,OAAM;QACjB;AACA,eAAO;MACT;;;;;;;;MASA,KAAK,GAAW,aAAkB,GAAS;AAGzC,cAAM,EAAE,SAAS,WAAU,IAAK,UAAU,GAAG,IAAI;AAEjD,YAAI,IAAI,EAAE;AACV,YAAI,IAAI,EAAE;AAEV,cAAM,OAAO,OAAO,KAAK,IAAI,CAAC;AAC9B,cAAM,YAAY,KAAK;AACvB,cAAM,UAAU,OAAO,CAAC;AAExB,iBAASA,UAAS,GAAGA,UAAS,SAASA,WAAU;AAC/C,gBAAM,SAASA,UAAS;AAExB,cAAI,QAAQ,OAAO,IAAI,IAAI;AAG3B,gBAAM;AAIN,cAAI,QAAQ,YAAY;AACtB,qBAAS;AACT,iBAAKF;UACP;AAUA,gBAAM,UAAU;AAChB,gBAAM,UAAU,SAAS,KAAK,IAAI,KAAK,IAAI;AAC3C,gBAAM,QAAQE,UAAS,MAAM;AAC7B,gBAAM,QAAQ,QAAQ;AACtB,cAAI,UAAU,GAAG;AAEf,gBAAI,EAAE,IAAI,gBAAgB,OAAO,YAAY,OAAO,CAAC,CAAC;UACxD,OAAO;AACL,gBAAI,EAAE,IAAI,gBAAgB,OAAO,YAAY,OAAO,CAAC,CAAC;UACxD;QACF;AAMA,eAAO,EAAE,GAAG,EAAC;MACf;;;;;;;;;MAUA,WAAW,GAAW,aAAkB,GAAW,MAAS,EAAE,MAAI;AAChE,cAAM,EAAE,SAAS,WAAU,IAAK,UAAU,GAAG,IAAI;AACjD,cAAM,OAAO,OAAO,KAAK,IAAI,CAAC;AAC9B,cAAM,YAAY,KAAK;AACvB,cAAM,UAAU,OAAO,CAAC;AACxB,iBAASA,UAAS,GAAGA,UAAS,SAASA,WAAU;AAC/C,gBAAM,SAASA,UAAS;AACxB,cAAI,MAAMH;AAAK;AAEf,cAAI,QAAQ,OAAO,IAAI,IAAI;AAE3B,gBAAM;AAGN,cAAI,QAAQ,YAAY;AACtB,qBAAS;AACT,iBAAKC;UACP;AACA,cAAI,UAAU;AAAG;AACjB,cAAI,OAAO,YAAY,SAAS,KAAK,IAAI,KAAK,IAAI,CAAC;AACnD,cAAI,QAAQ;AAAG,mBAAO,KAAK,OAAM;AAEjC,gBAAM,IAAI,IAAI,IAAI;QACpB;AACA,eAAO;MACT;MAEA,eAAe,GAAWC,IAAM,WAAoB;AAElD,YAAI,OAAO,iBAAiB,IAAIA,EAAC;AACjC,YAAI,CAAC,MAAM;AACT,iBAAO,KAAK,iBAAiBA,IAAG,CAAC;AACjC,cAAI,MAAM;AAAG,6BAAiB,IAAIA,IAAG,UAAU,IAAI,CAAC;QACtD;AACA,eAAO;MACT;MAEA,WAAWA,IAAM,GAAW,WAAoB;AAC9C,cAAM,IAAI,KAAKA,EAAC;AAChB,eAAO,KAAK,KAAK,GAAG,KAAK,eAAe,GAAGA,IAAG,SAAS,GAAG,CAAC;MAC7D;MAEA,iBAAiBA,IAAM,GAAW,WAAsB,MAAQ;AAC9D,cAAM,IAAI,KAAKA,EAAC;AAChB,YAAI,MAAM;AAAG,iBAAO,KAAK,aAAaA,IAAG,GAAG,IAAI;AAChD,eAAO,KAAK,WAAW,GAAG,KAAK,eAAe,GAAGA,IAAG,SAAS,GAAG,GAAG,IAAI;MACzE;;;;MAMA,cAAcA,IAAM,GAAS;AAC3B,kBAAU,GAAG,IAAI;AACjB,yBAAiB,IAAIA,IAAG,CAAC;AACzB,yBAAiB,OAAOA,EAAC;MAC3B;;EAEJ;AAhLgB;AA4LV,WAAU,UACd,GACA,QACA,QACA,SAAiB;AAQjB,sBAAkB,QAAQ,CAAC;AAC3B,uBAAmB,SAAS,MAAM;AAClC,QAAI,OAAO,WAAW,QAAQ;AAC5B,YAAM,IAAI,MAAM,qDAAqD;AACvE,UAAM,OAAO,EAAE;AACf,UAAM,QAAQ,OAAO,OAAO,OAAO,MAAM,CAAC;AAC1C,UAAM,aAAa,QAAQ,KAAK,QAAQ,IAAI,QAAQ,IAAI,QAAQ,IAAI,QAAQ,IAAI;AAChF,UAAM,QAAQ,KAAK,cAAc;AACjC,UAAM,UAAU,IAAI,MAAM,OAAO,CAAC,EAAE,KAAK,IAAI;AAC7C,UAAM,WAAW,KAAK,OAAO,OAAO,OAAO,KAAK,UAAU,IAAI;AAC9D,QAAI,MAAM;AACV,aAAS,IAAI,UAAU,KAAK,GAAG,KAAK,YAAY;AAC9C,cAAQ,KAAK,IAAI;AACjB,eAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,cAAM,SAAS,QAAQ,CAAC;AACxB,cAAME,SAAQ,OAAQ,UAAU,OAAO,CAAC,IAAK,OAAO,IAAI,CAAC;AACzD,gBAAQA,MAAK,IAAI,QAAQA,MAAK,EAAE,IAAI,OAAO,CAAC,CAAC;MAC/C;AACA,UAAI,OAAO;AAEX,eAAS,IAAI,QAAQ,SAAS,GAAG,OAAO,MAAM,IAAI,GAAG,KAAK;AACxD,eAAO,KAAK,IAAI,QAAQ,CAAC,CAAC;AAC1B,eAAO,KAAK,IAAI,IAAI;MACtB;AACA,YAAM,IAAI,IAAI,IAAI;AAClB,UAAI,MAAM;AAAG,iBAAS,IAAI,GAAG,IAAI,YAAY;AAAK,gBAAM,IAAI,OAAM;IACpE;AACA,WAAO;EACT;AAxCgB;AA2IV,WAAU,cACd,OAAyB;AAUzB,kBAAc,MAAM,EAAE;AACtB,mBACE,OACA;MACE,GAAG;MACH,GAAG;MACH,IAAI;MACJ,IAAI;OAEN;MACE,YAAY;MACZ,aAAa;KACd;AAGH,WAAO,OAAO,OAAO;MACnB,GAAG,QAAQ,MAAM,GAAG,MAAM,UAAU;MACpC,GAAG;MACH,GAAG,EAAE,GAAG,MAAM,GAAG,MAAK;KACd;EACZ;AA/BgB;;;ACpZhB,MAAMC,OAAM,OAAO,CAAC;AAApB,MAAuBC,OAAM,OAAO,CAAC;AAArC,MAAwCC,OAAM,OAAO,CAAC;AAAtD,MAAyDC,OAAM,OAAO,CAAC;AAkBvE,MAAM,iBAAiB,EAAE,QAAQ,KAAI;AAErC,WAAS,aAAa,OAAgB;AACpC,UAAM,OAAO,cAAc,KAAK;AAChC,IAAG,eACD,OACA;MACE,MAAM;MACN,GAAG;MACH,GAAG;MACH,aAAa;OAEf;MACE,mBAAmB;MACnB,QAAQ;MACR,SAAS;MACT,YAAY;KACb;AAGH,WAAO,OAAO,OAAO,EAAE,GAAG,KAAI,CAAW;EAC3C;AAnBS;AAoFH,WAAU,eAAe,UAAmB;AAChD,UAAM,QAAQ,aAAa,QAAQ;AACnC,UAAM,EACJ,IAAAC,KACA,GAAG,aACH,SACA,MAAM,OACN,aAAAC,cACA,aACA,GAAG,SAAQ,IACT;AAKJ,UAAM,OAAOH,QAAQ,OAAO,cAAc,CAAC,IAAID;AAC/C,UAAM,OAAOG,IAAG;AAChB,UAAM,KAAK,MAAM,MAAM,GAAG,MAAM,UAAU;AAG1C,UAAME,WACJ,MAAM,YACL,CAAC,GAAW,MAAa;AACxB,UAAI;AACF,eAAO,EAAE,SAAS,MAAM,OAAOF,IAAG,KAAK,IAAIA,IAAG,IAAI,CAAC,CAAC,EAAC;MACvD,SAAS,GAAG;AACV,eAAO,EAAE,SAAS,OAAO,OAAOJ,KAAG;MACrC;IACF;AACF,UAAMO,qBAAoB,MAAM,sBAAsB,CAAC,UAAsB;AAC7E,UAAM,SACJ,MAAM,WACL,CAAC,MAAkB,KAAiB,WAAmB;AACtD,YAAM,UAAU,MAAM;AACtB,UAAI,IAAI,UAAU;AAAQ,cAAM,IAAI,MAAM,qCAAqC;AAC/E,aAAO;IACT;AAGF,aAAS,YAAY,OAAe,GAAS;AAC3C,MAAG,SAAS,gBAAgB,OAAO,GAAGP,MAAK,IAAI;IACjD;AAFS;AAIT,aAAS,YAAY,OAAc;AACjC,UAAI,EAAE,iBAAiB;AAAQ,cAAM,IAAI,MAAM,wBAAwB;IACzE;AAFS;AAKT,UAAM,eAAe,SAAS,CAAC,GAAU,OAAoC;AAC3E,YAAM,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,EAAC,IAAK;AAChC,YAAM,MAAM,EAAE,IAAG;AACjB,UAAI,MAAM;AAAM,aAAK,MAAMG,OAAOC,IAAG,IAAI,CAAC;AAC1C,YAAM,KAAK,KAAK,IAAI,EAAE;AACtB,YAAM,KAAK,KAAK,IAAI,EAAE;AACtB,YAAM,KAAK,KAAK,IAAI,EAAE;AACtB,UAAI;AAAK,eAAO,EAAE,GAAGJ,MAAK,GAAGC,KAAG;AAChC,UAAI,OAAOA;AAAK,cAAM,IAAI,MAAM,kBAAkB;AAClD,aAAO,EAAE,GAAG,IAAI,GAAG,GAAE;IACvB,CAAC;AACD,UAAM,kBAAkB,SAAS,CAAC,MAAY;AAC5C,YAAM,EAAE,GAAG,EAAC,IAAK;AACjB,UAAI,EAAE,IAAG;AAAI,cAAM,IAAI,MAAM,iBAAiB;AAG9C,YAAM,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,EAAC,IAAK;AACvC,YAAM,KAAK,KAAK,IAAI,CAAC;AACrB,YAAM,KAAK,KAAK,IAAI,CAAC;AACrB,YAAM,KAAK,KAAK,IAAI,CAAC;AACrB,YAAM,KAAK,KAAK,KAAK,EAAE;AACvB,YAAM,MAAM,KAAK,KAAK,CAAC;AACvB,YAAM,OAAO,KAAK,KAAK,KAAK,MAAM,EAAE,CAAC;AACrC,YAAM,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC;AAC/C,UAAI,SAAS;AAAO,cAAM,IAAI,MAAM,uCAAuC;AAE3E,YAAM,KAAK,KAAK,IAAI,CAAC;AACrB,YAAM,KAAK,KAAK,IAAI,CAAC;AACrB,UAAI,OAAO;AAAI,cAAM,IAAI,MAAM,uCAAuC;AACtE,aAAO;IACT,CAAC;IAID,MAAM,MAAK;MA/Mb,OA+Ma;;;MAIT,YACW,IACA,IACA,IACA,IAAU;AAHV,aAAA,KAAA;AACA,aAAA,KAAA;AACA,aAAA,KAAA;AACA,aAAA,KAAA;AAET,oBAAY,KAAK,EAAE;AACnB,oBAAY,KAAK,EAAE;AACnB,oBAAY,KAAK,EAAE;AACnB,oBAAY,KAAK,EAAE;AACnB,eAAO,OAAO,IAAI;MACpB;MAEA,IAAI,IAAC;AACH,eAAO,KAAK,SAAQ,EAAG;MACzB;MACA,IAAI,IAAC;AACH,eAAO,KAAK,SAAQ,EAAG;MACzB;MAEA,OAAO,WAAW,GAAsB;AACtC,YAAI,aAAa;AAAO,gBAAM,IAAI,MAAM,4BAA4B;AACpE,cAAM,EAAE,GAAG,EAAC,IAAK,KAAK,CAAA;AACtB,oBAAY,KAAK,CAAC;AAClB,oBAAY,KAAK,CAAC;AAClB,eAAO,IAAI,MAAM,GAAG,GAAGA,MAAK,KAAK,IAAI,CAAC,CAAC;MACzC;MACA,OAAO,WAAW,QAAe;AAC/B,cAAM,QAAQG,IAAG,YAAY,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AACpD,eAAO,OAAO,IAAI,CAAC,GAAG,MAAM,EAAE,SAAS,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,MAAM,UAAU;MACxE;;MAEA,OAAO,IAAI,QAAiB,SAAiB;AAC3C,eAAO,UAAU,OAAO,IAAI,QAAQ,OAAO;MAC7C;;MAGA,eAAe,YAAkB;AAC/B,aAAK,cAAc,MAAM,UAAU;MACrC;;;MAGA,iBAAc;AACZ,wBAAgB,IAAI;MACtB;;MAGA,OAAO,OAAY;AACjB,oBAAY,KAAK;AACjB,cAAM,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,GAAE,IAAK;AACnC,cAAM,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,GAAE,IAAK;AACnC,cAAM,OAAO,KAAK,KAAK,EAAE;AACzB,cAAM,OAAO,KAAK,KAAK,EAAE;AACzB,cAAM,OAAO,KAAK,KAAK,EAAE;AACzB,cAAM,OAAO,KAAK,KAAK,EAAE;AACzB,eAAO,SAAS,QAAQ,SAAS;MACnC;MAEA,MAAG;AACD,eAAO,KAAK,OAAO,MAAM,IAAI;MAC/B;MAEA,SAAM;AAEJ,eAAO,IAAI,MAAM,KAAK,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;MACnE;;;;MAKA,SAAM;AACJ,cAAM,EAAE,EAAC,IAAK;AACd,cAAM,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,GAAE,IAAK;AACnC,cAAM,IAAI,KAAK,KAAK,EAAE;AACtB,cAAM,IAAI,KAAK,KAAK,EAAE;AACtB,cAAM,IAAI,KAAKF,OAAM,KAAK,KAAK,EAAE,CAAC;AAClC,cAAM,IAAI,KAAK,IAAI,CAAC;AACpB,cAAM,OAAO,KAAK;AAClB,cAAM,IAAI,KAAK,KAAK,OAAO,IAAI,IAAI,IAAI,CAAC;AACxC,cAAMM,KAAI,IAAI;AACd,cAAM,IAAIA,KAAI;AACd,cAAM,IAAI,IAAI;AACd,cAAM,KAAK,KAAK,IAAI,CAAC;AACrB,cAAM,KAAK,KAAKA,KAAI,CAAC;AACrB,cAAM,KAAK,KAAK,IAAI,CAAC;AACrB,cAAM,KAAK,KAAK,IAAIA,EAAC;AACrB,eAAO,IAAI,MAAM,IAAI,IAAI,IAAI,EAAE;MACjC;;;;MAKA,IAAI,OAAY;AACd,oBAAY,KAAK;AACjB,cAAM,EAAE,GAAG,EAAC,IAAK;AACjB,cAAM,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,GAAE,IAAK;AAC3C,cAAM,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,GAAE,IAAK;AAK3C,YAAI,MAAM,OAAO,EAAE,GAAG;AACpB,gBAAMC,KAAI,MAAM,KAAK,OAAO,KAAK,GAAG;AACpC,gBAAMC,KAAI,MAAM,KAAK,OAAO,KAAK,GAAG;AACpC,gBAAMC,KAAI,KAAKD,KAAID,EAAC;AACpB,cAAIE,OAAMX;AAAK,mBAAO,KAAK,OAAM;AACjC,gBAAMY,KAAI,KAAK,KAAKV,OAAM,EAAE;AAC5B,gBAAMW,KAAI,KAAK,KAAKX,OAAM,EAAE;AAC5B,gBAAMY,KAAID,KAAID;AACd,gBAAMJ,KAAIE,KAAID;AACd,gBAAMM,KAAIF,KAAID;AACd,gBAAMI,MAAK,KAAKF,KAAIH,EAAC;AACrB,gBAAMM,MAAK,KAAKT,KAAIO,EAAC;AACrB,gBAAMG,MAAK,KAAKJ,KAAIC,EAAC;AACrB,gBAAMI,MAAK,KAAKR,KAAIH,EAAC;AACrB,iBAAO,IAAI,MAAMQ,KAAIC,KAAIE,KAAID,GAAE;QACjC;AACA,cAAM,IAAI,KAAK,KAAK,EAAE;AACtB,cAAM,IAAI,KAAK,KAAK,EAAE;AACtB,cAAM,IAAI,KAAK,KAAK,IAAI,EAAE;AAC1B,cAAM,IAAI,KAAK,KAAK,EAAE;AACtB,cAAM,IAAI,MAAM,KAAK,OAAO,KAAK,MAAM,IAAI,CAAC;AAC5C,cAAM,IAAI,IAAI;AACd,cAAMV,KAAI,IAAI;AACd,cAAM,IAAI,KAAK,IAAI,IAAI,CAAC;AACxB,cAAM,KAAK,KAAK,IAAI,CAAC;AACrB,cAAM,KAAK,KAAKA,KAAI,CAAC;AACrB,cAAM,KAAK,KAAK,IAAI,CAAC;AACrB,cAAM,KAAK,KAAK,IAAIA,EAAC;AAErB,eAAO,IAAI,MAAM,IAAI,IAAI,IAAI,EAAE;MACjC;MAEA,SAAS,OAAY;AACnB,eAAO,KAAK,IAAI,MAAM,OAAM,CAAE;MAChC;MAEQ,KAAK,GAAS;AACpB,eAAO,KAAK,WAAW,MAAM,GAAG,MAAM,UAAU;MAClD;;MAGA,SAAS,QAAc;AACrB,cAAM,IAAI;AACV,QAAG,SAAS,UAAU,GAAGP,MAAK,WAAW;AACzC,cAAM,EAAE,GAAG,EAAC,IAAK,KAAK,KAAK,CAAC;AAC5B,eAAO,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;MACnC;;;;;;MAOA,eAAe,QAAgB,MAAM,MAAM,MAAI;AAC7C,cAAM,IAAI;AACV,QAAG,SAAS,UAAU,GAAGD,MAAK,WAAW;AACzC,YAAI,MAAMA;AAAK,iBAAO;AACtB,YAAI,KAAK,IAAG,KAAM,MAAMC;AAAK,iBAAO;AACpC,eAAO,KAAK,iBAAiB,MAAM,GAAG,MAAM,YAAY,GAAG;MAC7D;;;;;MAMA,eAAY;AACV,eAAO,KAAK,eAAe,QAAQ,EAAE,IAAG;MAC1C;;;MAIA,gBAAa;AACX,eAAO,KAAK,aAAa,MAAM,WAAW,EAAE,IAAG;MACjD;;;MAIA,SAAS,IAAW;AAClB,eAAO,aAAa,MAAM,EAAE;MAC9B;MAEA,gBAAa;AACX,cAAM,EAAE,GAAGmB,UAAQ,IAAK;AACxB,YAAIA,cAAanB;AAAK,iBAAO;AAC7B,eAAO,KAAK,eAAemB,SAAQ;MACrC;;;MAIA,OAAO,QAAQ,KAAU,SAAS,OAAK;AACrC,cAAM,EAAE,GAAG,EAAC,IAAK;AACjB,cAAM,MAAMhB,IAAG;AACf,cAAM,YAAY,YAAY,KAAK,GAAG;AACtC,cAAM,UAAU,MAAM;AACtB,cAAM,SAAS,IAAI,MAAK;AACxB,cAAM,WAAW,IAAI,MAAM,CAAC;AAC5B,eAAO,MAAM,CAAC,IAAI,WAAW,CAAC;AAC9B,cAAM,IAAO,gBAAgB,MAAM;AAMnC,cAAM,MAAM,SAAS,OAAOA,IAAG;AAC/B,QAAG,SAAS,cAAc,GAAGJ,MAAK,GAAG;AAIrC,cAAM,KAAK,KAAK,IAAI,CAAC;AACrB,cAAM,IAAI,KAAK,KAAKC,IAAG;AACvB,cAAM,IAAI,KAAK,IAAI,KAAK,CAAC;AACzB,YAAI,EAAE,SAAS,OAAO,EAAC,IAAKK,SAAQ,GAAG,CAAC;AACxC,YAAI,CAAC;AAAS,gBAAM,IAAI,MAAM,qCAAqC;AACnE,cAAM,UAAU,IAAIL,UAASA;AAC7B,cAAM,iBAAiB,WAAW,SAAU;AAC5C,YAAI,CAAC,UAAU,MAAMD,QAAO;AAE1B,gBAAM,IAAI,MAAM,8BAA8B;AAChD,YAAI,kBAAkB;AAAQ,cAAI,KAAK,CAAC,CAAC;AACzC,eAAO,MAAM,WAAW,EAAE,GAAG,EAAC,CAAE;MAClC;MACA,OAAO,eAAe,SAAY;AAChC,eAAO,qBAAqB,OAAO,EAAE;MACvC;MACA,aAAU;AACR,cAAM,EAAE,GAAG,EAAC,IAAK,KAAK,SAAQ;AAC9B,cAAM,QAAW,gBAAgB,GAAGI,IAAG,KAAK;AAC5C,cAAM,MAAM,SAAS,CAAC,KAAK,IAAIH,OAAM,MAAO;AAC5C,eAAO;MACT;MACA,QAAK;AACH,eAAU,WAAW,KAAK,WAAU,CAAE;MACxC;;AA5OgB,UAAA,OAAO,IAAI,MAAM,MAAM,IAAI,MAAM,IAAIA,MAAK,KAAK,MAAM,KAAK,MAAM,EAAE,CAAC;AACnE,UAAA,OAAO,IAAI,MAAMD,MAAKC,MAAKA,MAAKD,IAAG;AA6OrD,UAAM,EAAE,MAAM,GAAG,MAAM,EAAC,IAAK;AAC7B,UAAM,OAAO,KAAK,OAAO,cAAc,CAAC;AAExC,aAAS,KAAK,GAAS;AACrB,aAAO,IAAI,GAAG,WAAW;IAC3B;AAFS;AAIT,aAAS,QAAQ,MAAgB;AAC/B,aAAO,KAAQ,gBAAgB,IAAI,CAAC;IACtC;AAFS;AAKT,aAAS,qBAAqB,KAAQ;AACpC,YAAM,MAAMI,IAAG;AACf,YAAM,YAAY,eAAe,KAAK,GAAG;AAGzC,YAAM,SAAS,YAAY,sBAAsB,MAAM,GAAG,GAAG,IAAI,GAAG;AACpE,YAAM,OAAOG,mBAAkB,OAAO,MAAM,GAAG,GAAG,CAAC;AACnD,YAAM,SAAS,OAAO,MAAM,KAAK,IAAI,GAAG;AACxC,YAAM,SAAS,QAAQ,IAAI;AAC3B,YAAM,QAAQ,EAAE,SAAS,MAAM;AAC/B,YAAM,aAAa,MAAM,WAAU;AACnC,aAAO,EAAE,MAAM,QAAQ,QAAQ,OAAO,WAAU;IAClD;AAZS;AAeT,aAAS,aAAa,SAAY;AAChC,aAAO,qBAAqB,OAAO,EAAE;IACvC;AAFS;AAKT,aAAS,mBAAmB,UAAe,IAAI,WAAU,MAAO,MAAkB;AAChF,YAAM,MAAS,YAAY,GAAG,IAAI;AAClC,aAAO,QAAQ,MAAM,OAAO,KAAK,YAAY,WAAW,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;IAC/E;AAHS;AAMT,aAAS,KAAK,KAAU,SAAc,UAA6B,CAAA,GAAE;AACnE,YAAM,YAAY,WAAW,GAAG;AAChC,UAAI;AAAS,cAAM,QAAQ,GAAG;AAC9B,YAAM,EAAE,QAAQ,QAAQ,WAAU,IAAK,qBAAqB,OAAO;AACnE,YAAM,IAAI,mBAAmB,QAAQ,SAAS,QAAQ,GAAG;AACzD,YAAM,IAAI,EAAE,SAAS,CAAC,EAAE,WAAU;AAClC,YAAM,IAAI,mBAAmB,QAAQ,SAAS,GAAG,YAAY,GAAG;AAChE,YAAM,IAAI,KAAK,IAAI,IAAI,MAAM;AAC7B,MAAG,SAAS,eAAe,GAAGP,MAAK,WAAW;AAC9C,YAAM,MAAS,YAAY,GAAM,gBAAgB,GAAGI,IAAG,KAAK,CAAC;AAC7D,aAAO,YAAY,UAAU,KAAKA,IAAG,QAAQ,CAAC;IAChD;AAXS;AAaT,UAAM,aAAkD;AAMxD,aAAS,OAAO,KAAU,KAAUiB,YAAgB,UAAU,YAAU;AACtE,YAAM,EAAE,SAAS,OAAM,IAAK;AAC5B,YAAM,MAAMjB,IAAG;AACf,YAAM,YAAY,aAAa,KAAK,IAAI,GAAG;AAC3C,YAAM,YAAY,WAAW,GAAG;AAChC,MAAAiB,aAAY,YAAY,aAAaA,YAAW,GAAG;AACnD,UAAI,WAAW;AAAW,cAAM,UAAU,MAAM;AAChD,UAAI;AAAS,cAAM,QAAQ,GAAG;AAE9B,YAAM,IAAO,gBAAgB,IAAI,MAAM,KAAK,IAAI,GAAG,CAAC;AACpD,UAAI,GAAG,GAAG;AACV,UAAI;AAIF,YAAI,MAAM,QAAQA,YAAW,MAAM;AACnC,YAAI,MAAM,QAAQ,IAAI,MAAM,GAAG,GAAG,GAAG,MAAM;AAC3C,aAAK,EAAE,eAAe,CAAC;MACzB,SAAS,OAAO;AACd,eAAO;MACT;AACA,UAAI,CAAC,UAAU,EAAE,aAAY;AAAI,eAAO;AAExC,YAAM,IAAI,mBAAmB,SAAS,EAAE,WAAU,GAAI,EAAE,WAAU,GAAI,GAAG;AACzE,YAAM,MAAM,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;AAGrC,aAAO,IAAI,SAAS,EAAE,EAAE,cAAa,EAAG,OAAO,MAAM,IAAI;IAC3D;AA5BS;AA8BT,MAAE,eAAe,CAAC;AAElB,UAAMC,SAAQ;MACZ;;MAEA,kBAAkB,6BAAkBjB,aAAYD,IAAG,KAAK,GAAtC;;;;;;;MAQlB,WAAW,aAAa,GAAG,QAAsB,MAAM,MAAI;AACzD,cAAM,eAAe,UAAU;AAC/B,cAAM,SAAS,OAAO,CAAC,CAAC;AACxB,eAAO;MACT;;AAGF,WAAO;MACL;MACA;MACA;MACA;MACA,eAAe;MACf,OAAAkB;;EAEJ;AApbgB;;;AChGhB,MAAM,YAAY,OAChB,+EAA+E;AAGjF,MAAM,kBAAkC,uBACtC,+EAA+E;AAIjF,MAAMC,OAAM,OAAO,CAAC;AAApB,MAAuBC,OAAM,OAAO,CAAC;AAArC,MAAwCC,OAAM,OAAO,CAAC;AAAtD,MAAyDC,OAAM,OAAO,CAAC;AAEvE,MAAMC,OAAM,OAAO,CAAC;AAApB,MAAuBC,OAAM,OAAO,CAAC;AAErC,WAAS,oBAAoB,GAAS;AAEpC,UAAM,OAAO,OAAO,EAAE,GAAG,OAAO,OAAO,EAAE,GAAG,OAAO,OAAO,EAAE,GAAG,OAAO,OAAO,EAAE;AAC/E,UAAMC,KAAI;AACV,UAAM,KAAM,IAAI,IAAKA;AACrB,UAAM,KAAM,KAAK,IAAKA;AACtB,UAAM,KAAM,KAAK,IAAIJ,MAAKI,EAAC,IAAI,KAAMA;AACrC,UAAM,KAAM,KAAK,IAAIL,MAAKK,EAAC,IAAI,IAAKA;AACpC,UAAM,MAAO,KAAK,IAAIF,MAAKE,EAAC,IAAI,KAAMA;AACtC,UAAM,MAAO,KAAK,KAAK,MAAMA,EAAC,IAAI,MAAOA;AACzC,UAAM,MAAO,KAAK,KAAK,MAAMA,EAAC,IAAI,MAAOA;AACzC,UAAM,MAAO,KAAK,KAAK,MAAMA,EAAC,IAAI,MAAOA;AACzC,UAAM,OAAQ,KAAK,KAAK,MAAMA,EAAC,IAAI,MAAOA;AAC1C,UAAM,OAAQ,KAAK,MAAM,MAAMA,EAAC,IAAI,MAAOA;AAC3C,UAAM,OAAQ,KAAK,MAAM,MAAMA,EAAC,IAAI,MAAOA;AAC3C,UAAM,YAAa,KAAK,MAAMJ,MAAKI,EAAC,IAAI,IAAKA;AAE7C,WAAO,EAAE,WAAW,GAAE;EACxB;AAlBS;AAoBT,WAAS,kBAAkB,OAAiB;AAG1C,UAAM,CAAC,KAAK;AAEZ,UAAM,EAAE,KAAK;AAEb,UAAM,EAAE,KAAK;AACb,WAAO;EACT;AATS;AAYT,WAAS,QAAQ,GAAW,GAAS;AACnC,UAAMA,KAAI;AACV,UAAM,KAAK,IAAI,IAAI,IAAI,GAAGA,EAAC;AAC3B,UAAM,KAAK,IAAI,KAAK,KAAK,GAAGA,EAAC;AAE7B,UAAMC,OAAM,oBAAoB,IAAI,EAAE,EAAE;AACxC,QAAI,IAAI,IAAI,IAAI,KAAKA,MAAKD,EAAC;AAC3B,UAAM,MAAM,IAAI,IAAI,IAAI,GAAGA,EAAC;AAC5B,UAAM,QAAQ;AACd,UAAM,QAAQ,IAAI,IAAI,iBAAiBA,EAAC;AACxC,UAAM,WAAW,QAAQ;AACzB,UAAM,WAAW,QAAQ,IAAI,CAAC,GAAGA,EAAC;AAClC,UAAM,SAAS,QAAQ,IAAI,CAAC,IAAI,iBAAiBA,EAAC;AAClD,QAAI;AAAU,UAAI;AAClB,QAAI,YAAY;AAAQ,UAAI;AAC5B,QAAI,aAAa,GAAGA,EAAC;AAAG,UAAI,IAAI,CAAC,GAAGA,EAAC;AACrC,WAAO,EAAE,SAAS,YAAY,UAAU,OAAO,EAAC;EAClD;AAjBS;AA+BT,MAAM,KAAsB,uBAAM,MAAM,WAAW,QAAW,IAAI,GAAE;AAEpE,MAAM,kBAAmC,wBACtC;;IAEC,GAAG,OAAO,EAAE;;;;IAGZ,GAAG,OAAO,+EAA+E;;IAEzF;;;IAGA,GAAG,OAAO,8EAA8E;;IAExF,GAAGE;;IAEH,IAAI,OAAO,+EAA+E;IAC1F,IAAI,OAAO,+EAA+E;IAC1F,MAAM;IACN;IACA;;;;IAIA;MACU;AAaP,MAAM,UAAoC,uBAAM,eAAe,eAAe,GAAE;;;AClIvF,MAAM,WAA2B,oBAAI,YAAY;IAC/C;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IACpF;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IACpF;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IACpF;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IACpF;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IACpF;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IACpF;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IACpF;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;GACrF;AAID,MAAM,YAA4B,oBAAI,YAAY;IAChD;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;IAAY;GACrF;AAMD,MAAM,WAA2B,oBAAI,YAAY,EAAE;AAC7C,MAAO,SAAP,cAAsB,OAAc;IApC1C,OAoC0C;;;IAYxC,cAAA;AACE,YAAM,IAAI,IAAI,GAAG,KAAK;AAVd,WAAA,IAAY,UAAU,CAAC,IAAI;AAC3B,WAAA,IAAY,UAAU,CAAC,IAAI;AAC3B,WAAA,IAAY,UAAU,CAAC,IAAI;AAC3B,WAAA,IAAY,UAAU,CAAC,IAAI;AAC3B,WAAA,IAAY,UAAU,CAAC,IAAI;AAC3B,WAAA,IAAY,UAAU,CAAC,IAAI;AAC3B,WAAA,IAAY,UAAU,CAAC,IAAI;AAC3B,WAAA,IAAY,UAAU,CAAC,IAAI;IAIrC;IACU,MAAG;AACX,YAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,EAAC,IAAK;AACnC,aAAO,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAChC;;IAEU,IACR,GAAW,GAAW,GAAW,GAAW,GAAW,GAAW,GAAW,GAAS;AAEtF,WAAK,IAAI,IAAI;AACb,WAAK,IAAI,IAAI;AACb,WAAK,IAAI,IAAI;AACb,WAAK,IAAI,IAAI;AACb,WAAK,IAAI,IAAI;AACb,WAAK,IAAI,IAAI;AACb,WAAK,IAAI,IAAI;AACb,WAAK,IAAI,IAAI;IACf;IACU,QAAQC,OAAgB,QAAc;AAE9C,eAAS,IAAI,GAAG,IAAI,IAAI,KAAK,UAAU;AAAG,iBAAS,CAAC,IAAIA,MAAK,UAAU,QAAQ,KAAK;AACpF,eAAS,IAAI,IAAI,IAAI,IAAI,KAAK;AAC5B,cAAM,MAAM,SAAS,IAAI,EAAE;AAC3B,cAAM,KAAK,SAAS,IAAI,CAAC;AACzB,cAAM,KAAK,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,IAAK,QAAQ;AACnD,cAAM,KAAK,KAAK,IAAI,EAAE,IAAI,KAAK,IAAI,EAAE,IAAK,OAAO;AACjD,iBAAS,CAAC,IAAK,KAAK,SAAS,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAK;MACjE;AAEA,UAAI,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,EAAC,IAAK;AACjC,eAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,cAAM,SAAS,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE,IAAI,KAAK,GAAG,EAAE;AACpD,cAAM,KAAM,IAAI,SAAS,IAAI,GAAG,GAAG,CAAC,IAAI,SAAS,CAAC,IAAI,SAAS,CAAC,IAAK;AACrE,cAAM,SAAS,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE,IAAI,KAAK,GAAG,EAAE;AACpD,cAAM,KAAM,SAAS,IAAI,GAAG,GAAG,CAAC,IAAK;AACrC,YAAI;AACJ,YAAI;AACJ,YAAI;AACJ,YAAK,IAAI,KAAM;AACf,YAAI;AACJ,YAAI;AACJ,YAAI;AACJ,YAAK,KAAK,KAAM;MAClB;AAEA,UAAK,IAAI,KAAK,IAAK;AACnB,UAAK,IAAI,KAAK,IAAK;AACnB,UAAK,IAAI,KAAK,IAAK;AACnB,UAAK,IAAI,KAAK,IAAK;AACnB,UAAK,IAAI,KAAK,IAAK;AACnB,UAAK,IAAI,KAAK,IAAK;AACnB,UAAK,IAAI,KAAK,IAAK;AACnB,UAAK,IAAI,KAAK,IAAK;AACnB,WAAK,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACjC;IACU,aAAU;AAClB,eAAS,KAAK,CAAC;IACjB;IACA,UAAO;AACL,WAAK,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAC/B,WAAK,OAAO,KAAK,CAAC;IACpB;;AAsBK,MAAM,SAAgC,gCAAgB,MAAM,IAAI,OAAM,CAAE;;;AC/H/E,MAAM,eACJ;AAEF,MAAO,uBAAQ;;;ACIf,WAAS,iBAAiB,cAAc;AACtC,QAAI,CAAC,gBAAgB,OAAO,iBAAiB;AAC3C,YAAM,IAAI,MAAM,wCAAmC,YAAY,QAAG;AACpE,QAAI,aAAa,MAAM,WAAW;AAChC,YAAM,IAAI;AAAA,QACR,kCAA6B,aAAa,MAAM,WAAW,CAAC;AAAA,MAC9D;AACF,UAAM,KAAK,aAAa,MAAM,QAAQ;AACtC,UAAM,MAAM,KAAK,GAAG,CAAC,EAAE,SAAS;AAChC,UAAM,QACF,aAAa,SAAS,QAAQ,KAAK,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,KAAK,MAAO;AAEzE,WAAO,IAAI,WAAW;AAAA,MACpB,GAAG,IAAI,WAAW,GAAG;AAAA,MACrB,GAAG,aACA,MAAM,SAAS,EACf,IAAI,CAAC,MAAM,qBAAa,QAAQ,CAAC,CAAC,EAClC,OAAO,CAAC,KAAK,MAAM;AAClB,cAAM,IAAI,IAAI,CAAC,MAAM;AACnB,gBAAM,IAAI,IAAI,KAAK;AACnB,cAAI,KAAK;AACT,iBAAO;AAAA,QACT,CAAC;AACD,eAAO;AAAA,MACT,GAAG,IAAI,WAAW,IAAI,CAAC,EACtB,QAAQ,EACR;AAAA,QAEG,kBAAC,cAAc,CAAC;AAAA;AAAA,UAEb,YAAY,aAAa;AAAA,WAC5B,KAAK;AAAA,MACT;AAAA,IACJ,CAAC;AAAA,EACH;AAlCS;AAoCT,MAAO,2BAAQ;;;ACzCf,MAAM,oBAAoB,6BAAM;AAC9B,UAAM,UAAU,MAAM,GAAG,EAAE,KAAK,EAAE;AAClC,aAAS,IAAI,GAAG,IAAI,qBAAa,QAAQ,EAAE;AACzC,cAAQ,qBAAa,WAAW,CAAC,CAAC,IAAI;AAExC,WAAO;AAAA,EACT,GAN0B;AAQ1B,MAAO,4BAAQ;;;ACXf,MAAM,YAAY,0BAAkB;AAepC,WAAS,iBAAiB,YAAY;AACpC,UAAM,SAAS,CAAC;AAEhB,eAAW,QAAQ,YAAY;AAC7B,UAAI,QAAQ;AACZ,eAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,EAAE,GAAG;AAEtC,cAAM,KAAK,UAAU,OAAO,CAAC,CAAC,KAAK,KAAK;AACxC,eAAO,CAAC,IAAI,qBAAa,WAAW,IAAI,EAAE;AAC1C,gBAAS,IAAI,KAAM;AAAA,MACrB;AACA,aAAO,OAAO;AACZ,eAAO,KAAK,qBAAa,WAAW,QAAQ,EAAE,CAAC;AAC/C,gBAAS,QAAQ,KAAM;AAAA,MACzB;AAAA,IACF;AAEA,eAAW,QAAQ;AACjB,UAAI,KAAM;AAAA,UACL,QAAO,KAAK,IAAI,WAAW,CAAC,CAAC;AAEpC,WAAO,QAAQ;AAEf,WAAO,OAAO,aAAa,GAAG,MAAM;AAAA,EACtC;AAxBS;AA0BT,MAAO,2BAAQ;;;AC9Bf,MAAM,aAAa,OAAO,WAAW;AACrC,MAAM,MAAM,OAAO,gBAAgB,aAAa,IAAI,YAAY,IAAI;AACpE,MAAM,MAAM,OAAO,gBAAgB,aAAa,IAAI,YAAY,IAAI;AACpE,MAAM,QAAQ;AACd,MAAM,SAAS,MAAM,UAAU,MAAM,KAAK,KAAK;AAC/C,MAAM,UAAU,CAAC,MAAM;AACnB,QAAI,MAAM,CAAC;AACX,MAAE,QAAQ,CAAC,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC;AAC9B,WAAO;AAAA,EACX,GAAG,MAAM;AACT,MAAM,QAAQ;AACd,MAAM,UAAU,OAAO,aAAa,KAAK,MAAM;AAC/C,MAAM,WAAW,OAAO,WAAW,SAAS,aACtC,WAAW,KAAK,KAAK,UAAU,IAC/B,CAAC,OAAO,IAAI,WAAW,MAAM,UAAU,MAAM,KAAK,IAAI,CAAC,CAAC;AAC9D,MAAM,aAAa,wBAAC,QAAQ,IACvB,QAAQ,MAAM,EAAE,EAAE,QAAQ,UAAU,CAAC,OAAO,MAAM,MAAM,MAAM,GAAG,GADnD;AAEnB,MAAM,WAAW,wBAAC,MAAM,EAAE,QAAQ,qBAAqB,EAAE,GAAxC;AAIjB,MAAM,eAAe,wBAAC,QAAQ;AAE1B,QAAI,KAAK,IAAI,IAAI,IAAI,MAAM;AAC3B,UAAM,MAAM,IAAI,SAAS;AACzB,aAAS,IAAI,GAAG,IAAI,IAAI,UAAS;AAC7B,WAAK,KAAK,IAAI,WAAW,GAAG,KAAK,QAC5B,KAAK,IAAI,WAAW,GAAG,KAAK,QAC5B,KAAK,IAAI,WAAW,GAAG,KAAK;AAC7B,cAAM,IAAI,UAAU,yBAAyB;AACjD,YAAO,MAAM,KAAO,MAAM,IAAK;AAC/B,aAAO,OAAO,OAAO,KAAK,EAAE,IACtB,OAAO,OAAO,KAAK,EAAE,IACrB,OAAO,OAAO,IAAI,EAAE,IACpB,OAAO,MAAM,EAAE;AAAA,IACzB;AACA,WAAO,MAAM,IAAI,MAAM,GAAG,MAAM,CAAC,IAAI,MAAM,UAAU,GAAG,IAAI;AAAA,EAChE,GAhBqB;AAsBrB,MAAM,QAAQ,OAAO,SAAS,aAAa,CAAC,QAAQ,KAAK,GAAG,IACtD,aAAa,CAAC,QAAQ,OAAO,KAAK,KAAK,QAAQ,EAAE,SAAS,QAAQ,IAC9D;AACV,MAAM,kBAAkB,aAClB,CAAC,QAAQ,OAAO,KAAK,GAAG,EAAE,SAAS,QAAQ,IAC3C,CAAC,QAAQ;AAEP,UAAM,UAAU;AAChB,QAAI,OAAO,CAAC;AACZ,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,IAAI,GAAG,KAAK,SAAS;AACjD,WAAK,KAAK,QAAQ,MAAM,MAAM,IAAI,SAAS,GAAG,IAAI,OAAO,CAAC,CAAC;AAAA,IAC/D;AACA,WAAO,MAAM,KAAK,KAAK,EAAE,CAAC;AAAA,EAC9B;AAMJ,MAAM,iBAAiB,wBAAC,KAAK,UAAU,UAAU,UAAU,WAAW,gBAAgB,GAAG,CAAC,IAAI,gBAAgB,GAAG,GAA1F;AAIvB,MAAM,UAAU,wBAAC,MAAM;AACnB,QAAI,EAAE,SAAS,GAAG;AACd,UAAI,KAAK,EAAE,WAAW,CAAC;AACvB,aAAO,KAAK,MAAO,IACb,KAAK,OAAS,QAAQ,MAAQ,OAAO,CAAE,IACnC,QAAQ,MAAQ,KAAK,EAAK,IACzB,QAAQ,MAAS,OAAO,KAAM,EAAK,IAChC,QAAQ,MAAS,OAAO,IAAK,EAAK,IAClC,QAAQ,MAAQ,KAAK,EAAK;AAAA,IAC5C,OACK;AACD,UAAI,KAAK,SACF,EAAE,WAAW,CAAC,IAAI,SAAU,QAC5B,EAAE,WAAW,CAAC,IAAI;AACzB,aAAQ,QAAQ,MAAS,OAAO,KAAM,CAAK,IACrC,QAAQ,MAAS,OAAO,KAAM,EAAK,IACnC,QAAQ,MAAS,OAAO,IAAK,EAAK,IAClC,QAAQ,MAAQ,KAAK,EAAK;AAAA,IACpC;AAAA,EACJ,GAnBgB;AAoBhB,MAAM,UAAU;AAMhB,MAAM,OAAO,wBAAC,MAAM,EAAE,QAAQ,SAAS,OAAO,GAAjC;AAEb,MAAM,UAAU,aACV,CAAC,MAAM,OAAO,KAAK,GAAG,MAAM,EAAE,SAAS,QAAQ,IAC/C,MACI,CAAC,MAAM,gBAAgB,IAAI,OAAO,CAAC,CAAC,IACpC,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;AAM9B,MAAM,SAAS,wBAAC,KAAK,UAAU,UAAU,UACnC,WAAW,QAAQ,GAAG,CAAC,IACvB,QAAQ,GAAG,GAFF;AAWf,MAAM,UAAU;AAChB,MAAM,UAAU,wBAAC,SAAS;AACtB,YAAQ,KAAK,QAAQ;AAAA,MACjB,KAAK;AACD,YAAI,MAAO,IAAO,KAAK,WAAW,CAAC,MAAM,MACjC,KAAO,KAAK,WAAW,CAAC,MAAM,MAC9B,KAAO,KAAK,WAAW,CAAC,MAAM,IAC/B,KAAO,KAAK,WAAW,CAAC,GAAI,SAAS,KAAK;AACjD,eAAQ,SAAS,WAAW,MAAM,KAAM,IAClC,SAAS,SAAS,QAAS,KAAM;AAAA,MAC3C,KAAK;AACD,eAAO,SAAU,KAAO,KAAK,WAAW,CAAC,MAAM,MACvC,KAAO,KAAK,WAAW,CAAC,MAAM,IAC/B,KAAO,KAAK,WAAW,CAAC,CAAE;AAAA,MACrC;AACI,eAAO,SAAU,KAAO,KAAK,WAAW,CAAC,MAAM,IACxC,KAAO,KAAK,WAAW,CAAC,CAAE;AAAA,IACzC;AAAA,EACJ,GAjBgB;AAuBhB,MAAM,OAAO,wBAAC,MAAM,EAAE,QAAQ,SAAS,OAAO,GAAjC;AAIb,MAAM,eAAe,wBAAC,QAAQ;AAE1B,UAAM,IAAI,QAAQ,QAAQ,EAAE;AAC5B,QAAI,CAAC,MAAM,KAAK,GAAG;AACf,YAAM,IAAI,UAAU,mBAAmB;AAC3C,WAAO,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE;AACtC,QAAI,KAAK,MAAM,IAAI,IAAI;AACvB,aAAS,IAAI,GAAG,IAAI,IAAI,UAAS;AAC7B,YAAM,OAAO,IAAI,OAAO,GAAG,CAAC,KAAK,KAC3B,OAAO,IAAI,OAAO,GAAG,CAAC,KAAK,MAC1B,KAAK,OAAO,IAAI,OAAO,GAAG,CAAC,MAAM,KACjC,KAAK,OAAO,IAAI,OAAO,GAAG,CAAC;AAClC,aAAO,OAAO,KAAK,QAAQ,OAAO,KAAK,GAAG,IACpC,OAAO,KAAK,QAAQ,OAAO,KAAK,KAAK,OAAO,IAAI,GAAG,IAC/C,QAAQ,OAAO,KAAK,KAAK,OAAO,IAAI,KAAK,MAAM,GAAG;AAAA,IAChE;AACA,WAAO;AAAA,EACX,GAjBqB;AAuBrB,MAAM,QAAQ,OAAO,SAAS,aAAa,CAAC,QAAQ,KAAK,SAAS,GAAG,CAAC,IAChE,aAAa,CAAC,QAAQ,OAAO,KAAK,KAAK,QAAQ,EAAE,SAAS,QAAQ,IAC9D;AAEV,MAAM,gBAAgB,aAChB,CAAC,MAAM,SAAS,OAAO,KAAK,GAAG,QAAQ,CAAC,IACxC,CAAC,MAAM,SAAS,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,IAAI,OAAK,EAAE,WAAW,CAAC,CAAC,CAAC;AAIlE,MAAM,eAAe,wBAAC,MAAM,cAAc,OAAO,CAAC,CAAC,GAA9B;AAErB,MAAM,UAAU,aACV,CAAC,MAAM,OAAO,KAAK,GAAG,QAAQ,EAAE,SAAS,MAAM,IAC/C,MACI,CAAC,MAAM,IAAI,OAAO,cAAc,CAAC,CAAC,IAClC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;AAC9B,MAAM,SAAS,wBAAC,MAAM,SAAS,EAAE,QAAQ,SAAS,CAAC,OAAO,MAAM,MAAM,MAAM,GAAG,CAAC,GAAjE;AAMf,MAAM,SAAS,wBAAC,QAAQ,QAAQ,OAAO,GAAG,CAAC,GAA5B;;;AC7MR,MAAM,WAAW;AAUjB,MAAM,uBAAuB,6BAClC,OAAO,iBAAiB,cACpB,eACA;AAAA,IACA,SAAS,wBAAC,QAAQ,YAAY,IAAI,GAAG,KAAK,MAAjC;AAAA,IACT,SAAS,wBAAC,KAAK,UAAU,YAAY,IAAI,KAAK,KAAK,GAA1C;AAAA,IACT,YAAY,wBAAC,QAAQ,YAAY,OAAO,GAAG,GAA/B;AAAA,IACZ,OAAO,6BAAM,YAAY,MAAM,GAAxB;AAAA,EACT,GARgC;AAU7B,MAAM,cAAc,oBAAI,IAAoB;AAEnD,MAAI,iBAAiC,qBAAqB;AAGnD,MAAM,UAAU;AAAA,IACrB,YAAY,wBAAC,kBAAkC;AAC7C,uBAAiB;AAAA,IACnB,GAFY;AAAA,IAIZ,KAAK,wBAAC,KAAa,UAAe;AAChC,UAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,uBAAe,WAAW,WAAW,GAAG;AAAA,MAC1C,OAAO;AACL,uBAAe,QAAQ,WAAW,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,MAC9D;AAAA,IACF,GANK;AAAA,IAQL,KAAK,wBAAC,QAAqB;AACzB,YAAM,QAAQ,eAAe,QAAQ,WAAW,GAAG;AACnD,UAAI,UAAU,KAAM,QAAO;AAC3B,UAAI;AACF,eAAO,KAAK,MAAM,KAAK;AAAA,MACzB,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF,GARK;AAAA,IAUL,QAAQ,wBAAC,QAAgB,eAAe,WAAW,GAAG,GAA9C;AAAA,IACR,OAAO,6BAAM,eAAe,MAAM,GAA3B;AAAA,EACT;;;ACnCO,WAAS,MAAM,MAA0B;AAC9C,WAAO,MAAM,KAAK,IAAI,EACnB,IAAI,OAAK,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EACxC,KAAK,EAAE;AAAA,EACZ;AAJgB;AAMT,WAAS,QAAQ,KAAyB;AAC/C,QAAI,IAAI,SAAS,EAAG,OAAM,IAAI,MAAM,gCAAgC;AACpE,UAAM,QAAQ,IAAI,WAAW,IAAI,SAAS,CAAC;AAC3C,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK,GAAG;AACtC,YAAM,IAAE,CAAC,IAAI,SAAS,IAAI,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE;AAAA,IAC/C;AACA,WAAO;AAAA,EACT;AAPgB;AAST,WAAS,cAAc,QAA4B;AACxD,WAAO,aAAqB,MAAM;AAAA,EACpC;AAFgB;AAIT,WAAS,cAAc,UAA8B;AAC1D,WAAO,eAAuB,QAAQ;AAAA,EACxC;AAFgB;AAIT,WAAS,SAAS,QAAgB;AACvC,QAAI;AACF,aAAO,OAAe,MAAM;AAAA,IAC9B,SAAS,GAAG;AACV,cAAQ,MAAM,yBAAyB,CAAC;AACxC,aAAO;AAAA,IACT;AAAA,EACF;AAPgB;AAST,WAAS,WAAW,QAAgB;AACzC,QAAI;AACF,aAAO,OAAe,MAAM;AAAA,IAC9B,SAAS,GAAG;AACV,cAAQ,MAAM,yBAAyB,CAAC;AACxC,aAAO;AAAA,IACT;AAAA,EACF;AAPgB;AAST,WAAS,YAAY,MAAqC,MAAqB;AAEpF,QAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,UAAI,EAAE,OAAO,CAAC,KAAK,MAAM,MAAM;AAC7B,eAAO,OAAO,KAAK,IAAI,CAAC,KAAK,MAAM;AAAA,MACrC,CAAC;AAAA,IACH;AAEA,QAAI,OAAO,KAAK,UAAU;AACxB,YAAM,QAAQ,EAAE,MAAM,4BAA4B;AAClD,UAAI,OAAO;AACT,cAAM,SAAS,MAAM,CAAC,EAAE,QAAQ,SAAS,EAAE;AAC3C,cAAM,WAAW,MAAM,CAAC;AACxB,YAAI,UAAU;AACZ,kBAAQ,SAAS,YAAY,GAAG;AAAA,YAC9B,KAAK;AACH,qBAAO,YAAI,MAAM,EAAE,IAAI,YAAI,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC;AAAA,YACnD,KAAK;AACH,qBAAO,YAAI,MAAM,EAAE,IAAI,YAAI,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC;AAAA,YACnD,KAAK;AACH,qBAAO,YAAI,MAAM,EAAE,IAAI,YAAI,EAAE,EAAE,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC;AAAA,YAClD,KAAK;AAAA,YACL,KAAK;AACH,qBAAO,YAAI,MAAM,EAAE,QAAQ,CAAC;AAAA,YAC9B;AACE,oBAAM,IAAI,MAAM,iBAAiB,QAAQ,EAAE;AAAA,UAC/C;AAAA,QACF,OAAO;AACL,iBAAO,YAAI,MAAM,EAAE,QAAQ,CAAC;AAAA,QAC9B;AAAA,MACF;AAAA,IACF;AACA,WAAO,YAAI,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC;AAAA,EAC9B;AAjCgB;AAmCT,WAAS,MAAM,KAAa,OAAY;AAC7C,YAAQ,IAAI,KAAK,KAAK;AAAA,EACxB;AAFgB;AAIT,WAAS,MAAM,KAAkB;AACtC,WAAO,QAAQ,IAAI,GAAG;AAAA,EACxB;AAFgB;AAIT,WAAS,SAAS,KAAK;AAC5B,WAAO,KAAK,MAAM,KAAK,UAAU,GAAG,CAAC;AAAA,EACvC;AAFgB;AAIT,WAAS,gBAAgB,MAAM;AACpC,QAAI;AACF,aAAO,KAAK,MAAM,KAAK,CAAC,CAAC;AAAA,IAC3B,QAAQ;AACN,UAAI,KAAK,SAAS,GAAG;AACnB,eAAO,KAAK,CAAC;AAAA,MACf;AACA,aAAO,KAAK,CAAC;AAAA,IACf;AAAA,EACF;AATgB;AAWT,WAAS,mBAAmB,OAAmB;AACpD,QAAI;AACF,YAAM,UAAU,IAAI,YAAY;AAChC,aAAO,KAAK;AAAA,QACV,QAAQ,OAAO,iBAAiB,aAAa,QAAQ,IAAI,WAAW,KAAK,CAAC;AAAA,MAC5E;AAAA,IACF,SAAS,GAAG;AACV,UAAI;AACF,eAAO,iBAAiB,aAAa,QAAQ,IAAI,WAAW,KAAK;AAAA,MACnE,SAASC,IAAG;AACV,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAbgB;AAeT,WAAS,eAAeC,UAAS;AACtC,WACEA,SAAQ,WAAW,KACnBA,SAAQ,CAAC,EAAE,SAAS,kBACpB,YAAIA,SAAQ,CAAC,GAAG,WAAW,GAAG,EAAE,GAAG,CAAC;AAAA,EAExC;AANgB;;;AC1HT,MAAM,gBAAgB,wBAAC,QAC5B;AAAA,IACE,IAAI,SAAS,GAAG,KACX,MAAM;AACL,YAAM,CAAC,OAAO,OAAO,IAAI,IAAI,MAAM,GAAG;AACtC,UAAI,UAAU,WAAW;AACvB,cAAM,IAAI,MAAM,sBAAsB,KAAK,EAAE;AAAA,MAC/C;AACA,aAAO;AAAA,IACT,GAAG,IACH;AAAA,EACN,GAX2B;AAatB,MAAM,cAAc,wBAAC,QAAoB,WAAW,yBAAS,GAAG,CAAC,IAA7C;AAEpB,WAAS,qBAAqB,YAAoB;AACvD,UAAM,SAAS,cAAc,UAAU,EAAE,MAAM,GAAG,EAAE;AACpD,UAAMC,aAAY,QAAQ,aAAa,MAAM;AAC7C,WAAO,YAAYA,UAAS;AAAA,EAC9B;AAJgB;AAMT,WAAS,uBAAuB;AACrC,UAAM,aAAa,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC;AAC5D,WAAO,YAAY,UAAU;AAAA,EAC/B;AAHgB;AAKT,WAAS,SAAS,WAAuB,YAAoB,MAAiB;AACnF,UAAM,SAAS,cAAc,UAAU,EAAE,MAAM,GAAG,EAAE;AACpD,UAAM,YAAY,QAAQ,KAAK,WAAW,MAAM;AAEhD,QAAI,MAAM,cAAc;AACtB,aAAO,yBAAS,SAAS;AAAA,IAC3B;AAEA,WAAO;AAAA,EACT;AATgB;AAWT,WAAS,UAAU,OAAmB,YAAoB;AAC/D,UAAM,OAAO,OAAO,KAAK;AACzB,WAAO,SAAS,MAAM,UAAU;AAAA,EAClC;AAHgB;;;AC5CT,MAAI,WAAW,CAAC,MAAM,OAAO,OAAO,OAAO,QAAQ,MAAM,OAAO,OAAO,OAAO,QAAQ,OAAO,KAAK;;;ACAzG,MAAI;AAAA;AAAA,IAA8B,WAAY;AAC1C,eAASC,gBAAe;AACpB,aAAK,SAAS;AACd,aAAK,cAAc;AACnB,aAAK,SAAS,IAAI,YAAY,KAAK,WAAW;AAC9C,aAAK,OAAO,IAAI,SAAS,KAAK,MAAM;AAAA,MACxC;AALS,aAAAA,eAAA;AAMT,MAAAA,cAAa,UAAU,sBAAsB,SAAU,cAAc;AACjE,YAAI,KAAK,cAAc,KAAK,SAAS,cAAc;AAC/C,eAAK,cAAc,KAAK,IAAI,KAAK,cAAc,GAAG,KAAK,cAAc,YAAY;AACjF,cAAI,aAAa,IAAI,YAAY,KAAK,WAAW;AACjD,cAAI,WAAW,UAAU,EAAE,IAAI,IAAI,WAAW,KAAK,MAAM,CAAC;AAC1D,eAAK,SAAS;AACd,eAAK,OAAO,IAAI,SAAS,UAAU;AAAA,QACvC;AAAA,MACJ;AACA,MAAAA,cAAa,UAAU,kBAAkB,WAAY;AACjD,eAAO,IAAI,WAAW,KAAK,MAAM,EAAE,MAAM,GAAG,KAAK,MAAM;AAAA,MAC3D;AACA,MAAAA,cAAa,UAAU,cAAc,SAAU,OAAO,MAAM;AACxD,YAAI,QAAQ,KAAK,UAAU,CAAC;AAC5B,YAAI,OAAO,SAAS,KAAK,IAAI;AAC7B,aAAK,oBAAoB,IAAI;AAC7B,YAAI,SAAS,KAAK,CAAC,MAAM,MAAM,WAAW,OAAO,KAAK,IAAI,KAAK,CAAC,MAAM,MAAM,SAAS,OAAO,KAAK,IAAI,UAAU,OAAO,KAAK;AAC3H,aAAK,KAAK,MAAM,EAAE,KAAK,QAAQ,OAAO,IAAI;AAC1C,aAAK,UAAU;AAAA,MACnB;AACA,MAAAA,cAAa,UAAU,cAAc,SAAU,MAAM;AACjD,aAAK,oBAAoB,KAAK,MAAM;AACpC,YAAI,WAAW,KAAK,MAAM,EAAE,IAAI,IAAI,WAAW,IAAI,GAAG,KAAK,MAAM;AACjE,aAAK,UAAU,KAAK;AAAA,MACxB;AACA,aAAOA;AAAA,IACX,EAAE;AAAA;AAEF,MAAI;AAAA;AAAA,IAA8B,WAAY;AAC1C,eAASC,cAAa,KAAK;AACvB,aAAK,SAAS;AACd,aAAK,cAAc,IAAI;AACvB,aAAK,SAAS,IAAI,YAAY,IAAI,MAAM;AACxC,YAAI,WAAW,KAAK,MAAM,EAAE,IAAI,GAAG;AACnC,aAAK,OAAO,IAAI,SAAS,KAAK,MAAM;AAAA,MACxC;AANS,aAAAA,eAAA;AAOT,MAAAA,cAAa,UAAU,uBAAuB,SAAU,MAAM;AAC1D,YAAI,KAAK,SAAS,OAAO,KAAK,OAAO,YAAY;AAC7C,gBAAM,IAAI,MAAM,sDAAsD;AAAA,QAC1E;AAAA,MACJ;AACA,MAAAA,cAAa,UAAU,gBAAgB,SAAU,MAAM;AACnD,YAAI,QAAQ,KAAK,UAAU,CAAC;AAC5B,YAAI,OAAO,SAAS,KAAK,IAAI;AAC7B,aAAK,qBAAqB,IAAI;AAC9B,YAAI,SAAS,KAAK,CAAC,MAAM,MAAM,WAAW,OAAO,KAAK,IAAI,KAAK,CAAC,MAAM,MAAM,SAAS,OAAO,KAAK,IAAI,UAAU,OAAO,KAAK;AAC3H,YAAI,MAAM,KAAK,KAAK,MAAM,EAAE,KAAK,QAAQ,IAAI;AAC7C,aAAK,UAAU;AACf,eAAO;AAAA,MACX;AACA,MAAAA,cAAa,UAAU,gBAAgB,SAAU,MAAM;AACnD,aAAK,qBAAqB,IAAI;AAC9B,YAAI,MAAM,KAAK,OAAO,MAAM,KAAK,QAAQ,KAAK,SAAS,IAAI;AAC3D,aAAK,UAAU;AACf,eAAO;AAAA,MACX;AACA,aAAOA;AAAA,IACX,EAAE;AAAA;;;AChEF,MAAI,YAAyC,2BAAY;AACrD,QAAI,gBAAgB,gCAAU,GAAG,GAAG;AAChC,sBAAgB,OAAO,kBAClB,EAAE,WAAW,CAAC,EAAE,aAAa,SAAS,SAAUC,IAAGC,IAAG;AAAE,QAAAD,GAAE,YAAYC;AAAA,MAAG,KAC1E,SAAUD,IAAGC,IAAG;AAAE,iBAAS,KAAKA,GAAG,KAAI,OAAO,UAAU,eAAe,KAAKA,IAAG,CAAC,EAAG,CAAAD,GAAE,CAAC,IAAIC,GAAE,CAAC;AAAA,MAAG;AACpG,aAAO,cAAc,GAAG,CAAC;AAAA,IAC7B,GALoB;AAMpB,WAAO,SAAU,GAAG,GAAG;AACnB,UAAI,OAAO,MAAM,cAAc,MAAM;AACjC,cAAM,IAAI,UAAU,yBAAyB,OAAO,CAAC,IAAI,+BAA+B;AAC5F,oBAAc,GAAG,CAAC;AAClB,eAAS,KAAK;AAAE,aAAK,cAAc;AAAA,MAAG;AAA7B;AACT,QAAE,YAAY,MAAM,OAAO,OAAO,OAAO,CAAC,KAAK,GAAG,YAAY,EAAE,WAAW,IAAI,GAAG;AAAA,IACtF;AAAA,EACJ,EAAG;AAEI,WAAS,YAAY,OAAO;AAE/B,WAAQ,MAAM,QAAQ,KAAK,KACtB,CAAC,CAAC,SACC,OAAO,UAAU,YACjB,YAAY,SACZ,OAAQ,MAAM,WAAY,aACzB,MAAM,WAAW,KACb,MAAM,SAAS,KACX,MAAM,SAAS,KAAM;AAAA,EAC1C;AAVgB;AAWT,WAAS,YAAY,OAAO,MAAM,WAAW;AAChD,QAAI,OAAQ,UAAW,MAAM;AACzB,YAAM,IAAI,MAAM,YAAY,OAAO,MAAM,OAAO,EAAE,OAAO,OAAQ,OAAQ,GAAG,EAAE,OAAO,OAAO,OAAO,EAAE,OAAO,UAAU,KAAK,GAAG,CAAC,CAAC;AAAA,IACpI;AAAA,EACJ;AAJgB;AAKT,WAAS,cAAc,OAAO,WAAW;AAC5C,QAAI,YAAY,CAAC,UAAU,UAAU,UAAU,SAAS,EAAE,SAAS,OAAQ,KAAM;AACjF,QAAI,YAAY,OAAQ,UAAW,YAAY,UAAU,QAAQ,cAAc;AAC/E,QAAI,CAAC,aAAa,CAAC,WAAW;AAC1B,YAAM,IAAI,MAAM,kDAAkD,OAAO,OAAQ,OAAQ,GAAG,EAAE,OAAO,OAAO,OAAO,EAAE,OAAO,UAAU,KAAK,GAAG,CAAC,CAAC;AAAA,IACpJ;AAAA,EACJ;AANgB;AAOT,WAAS,iBAAiB,QAAQ,UAAU,WAAW;AAC1D,QAAI,WAAW,UAAU;AACrB,YAAM,IAAI,MAAM,gBAAgB,OAAO,QAAQ,gCAAgC,EAAE,OAAO,UAAU,MAAM,EAAE,OAAO,UAAU,KAAK,GAAG,CAAC,CAAC;AAAA,IACzI;AAAA,EACJ;AAJgB;AAKT,WAAS,YAAY,OAAO,WAAW;AAC1C,QAAI,OAAQ,UAAW,YAAY,UAAU,MAAM;AAC/C,YAAM,IAAI,MAAM,uBAAuB,OAAO,OAAQ,OAAQ,GAAG,EAAE,OAAO,OAAO,OAAO,EAAE,OAAO,UAAU,KAAK,GAAG,CAAC,CAAC;AAAA,IACzH;AAAA,EACJ;AAJgB;AAMhB,MAAI,qBAAqB,SAAS,OAAO,CAAC,QAAQ,QAAQ,CAAC;AAC3D,MAAI,oBAAoB,CAAC,UAAU,QAAQ,SAAS,OAAO,OAAO,QAAQ;AAC1E,MAAI;AAAA;AAAA,IAA6B,SAAU,QAAQ;AAC/C,gBAAUC,cAAa,MAAM;AAC7B,eAASA,aAAY,QAAQ,UAAU;AACnC,YAAI,UAAU,mBAAmB,OAAO,KAAK,UAAU,MAAM,GAAG,YAAY,EAAE,OAAO,QAAQ;AAC7F,eAAO,OAAO,KAAK,MAAM,OAAO,KAAK;AAAA,MACzC;AAHS,aAAAA,cAAA;AAIT,aAAOA;AAAA,IACX,EAAE,KAAK;AAAA;AAEA,WAAS,gBAAgB,QAAQ;AACpC,QAAI,OAAQ,WAAY,YAAY,mBAAmB,SAAS,MAAM,GAAG;AACrE;AAAA,IACJ;AACA,QAAI,UAAU,OAAQ,WAAY,UAAU;AACxC,UAAI,OAAO,OAAO,KAAK,MAAM;AAC7B,UAAI,KAAK,WAAW,KAAK,kBAAkB,SAAS,KAAK,CAAC,CAAC,GAAG;AAC1D,YAAI,MAAM,KAAK,CAAC;AAChB,YAAI,QAAQ;AACR,iBAAO,gBAAgB,OAAO,GAAG,CAAC;AACtC,YAAI,QAAQ;AACR,iBAAO,qBAAqB,OAAO,GAAG,CAAC;AAC3C,YAAI,QAAQ;AACR,iBAAO,sBAAsB,OAAO,GAAG,CAAC;AAC5C,YAAI,QAAQ;AACR,iBAAO,gBAAgB,OAAO,GAAG,CAAC;AACtC,YAAI,QAAQ;AACR,iBAAO,oBAAoB,OAAO,GAAG,CAAC;AAC1C,YAAI,QAAQ;AACR,iBAAO,uBAAuB,OAAO,GAAG,CAAC;AAAA,MACjD;AAAA,IACJ;AACA,UAAM,IAAI,YAAY,QAAQ,kBAAkB,KAAK,IAAI,IAAI,SAAS,mBAAmB,KAAK,IAAI,CAAC;AAAA,EACvG;AAvBgB;AAwBhB,WAAS,qBAAqB,QAAQ;AAClC,QAAI,CAAC,MAAM,QAAQ,MAAM;AACrB,YAAM,IAAI,YAAY,QAAQ,OAAO;AACzC,aAAS,KAAK,GAAG,WAAW,QAAQ,KAAK,SAAS,QAAQ,MAAM;AAC5D,UAAI,MAAM,SAAS,EAAE;AACrB,UAAI,OAAO,QAAQ,YAAY,EAAE,YAAY,MAAM;AAC/C,cAAM,IAAI,MAAM,qCAAqC;AAAA,MACzD;AACA,UAAI,OAAO,IAAI,WAAW,YAAY,OAAO,KAAK,IAAI,MAAM,EAAE,WAAW,GAAG;AACxE,cAAM,IAAI,MAAM,kDAAkD;AAAA,MACtE;AACA,sBAAgB,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC1C;AAAA,EACJ;AAbS;AAcT,WAAS,sBAAsB,QAAQ;AACnC,QAAI,OAAO,WAAW;AAClB,YAAM,IAAI,YAAY,QAAQ,gBAAgB;AAClD,QAAI,OAAO,OAAO,OAAO,OAAO,QAAQ,UAAU;AAC9C,YAAM,IAAI,MAAM,mBAAmB,OAAO,MAAM,CAAC;AAAA,IACrD;AACA,QAAI,UAAU;AACV,aAAO,gBAAgB,OAAO,IAAI;AACtC,UAAM,IAAI,YAAY,QAAQ,gBAAgB;AAAA,EAClD;AATS;AAUT,WAAS,oBAAoB,QAAQ;AACjC,QAAI,OAAO,WAAW,YAAY,SAAS,UAAU,WAAW,QAAQ;AACpE,sBAAgB,OAAO,GAAG;AAC1B,sBAAgB,OAAO,KAAK;AAAA,IAChC,OACK;AACD,YAAM,IAAI,YAAY,QAAQ,gBAAgB;AAAA,IAClD;AAAA,EACJ;AARS;AAST,WAAS,uBAAuB,QAAQ;AACpC,QAAI,OAAO,WAAW;AAClB,YAAM,IAAI,YAAY,QAAQ,QAAQ;AAC1C,aAAS,OAAO,QAAQ;AACpB,sBAAgB,OAAO,GAAG,CAAC;AAAA,IAC/B;AAAA,EACJ;AANS;;;ACnHT,MAAI;AAAA;AAAA,IAAiC,WAAY;AAC7C,eAASC,iBAAgB,YAAY;AACjC,aAAK,UAAU,IAAI,aAAa;AAChC,aAAK,YAAY,CAAC,OAAO;AACzB,aAAK,aAAa;AAAA,MACtB;AAJS,aAAAA,kBAAA;AAKT,MAAAA,iBAAgB,UAAU,SAAS,SAAU,OAAO,QAAQ;AACxD,aAAK,aAAa,OAAO,MAAM;AAC/B,eAAO,KAAK,QAAQ,gBAAgB;AAAA,MACxC;AACA,MAAAA,iBAAgB,UAAU,eAAe,SAAU,OAAO,QAAQ;AAC9D,YAAI,OAAO,WAAW,UAAU;AAC5B,cAAI,SAAS,SAAS,MAAM;AACxB,mBAAO,KAAK,eAAe,OAAO,MAAM;AAC5C,cAAI,WAAW;AACX,mBAAO,KAAK,cAAc,KAAK;AACnC,cAAI,WAAW;AACX,mBAAO,KAAK,eAAe,KAAK;AAAA,QACxC;AACA,YAAI,OAAO,WAAW,UAAU;AAC5B,cAAI,YAAY;AACZ,mBAAO,KAAK,cAAc,OAAO,MAAM;AAC3C,cAAI,UAAU;AACV,mBAAO,KAAK,YAAY,OAAO,MAAM;AACzC,cAAI,WAAW;AACX,mBAAO,KAAK,aAAa,OAAO,MAAM;AAC1C,cAAI,SAAS;AACT,mBAAO,KAAK,WAAW,OAAO,MAAM;AACxC,cAAI,SAAS;AACT,mBAAO,KAAK,WAAW,OAAO,MAAM;AACxC,cAAI,YAAY;AACZ,mBAAO,KAAK,cAAc,OAAO,MAAM;AAAA,QAC/C;AAAA,MACJ;AACA,MAAAA,iBAAgB,UAAU,iBAAiB,SAAU,OAAO,QAAQ;AAChE,YAAI,OAAO,SAAS,OAAO,UAAU,CAAC,CAAC;AACvC,YAAI,QAAQ,MAAM,UAAU,OAAO;AAC/B,eAAK,cAAoB,YAAY,OAAO,UAAU,KAAK,SAAS;AACpE,eAAK,QAAQ,YAAY,OAAO,MAAM;AAAA,QAC1C,OACK;AACD,eAAK,cAAoB,cAAc,OAAO,KAAK,SAAS;AAC5D,eAAK,cAAc,OAAO,KAAK,GAAG,IAAI;AAAA,QAC1C;AAAA,MACJ;AACA,MAAAA,iBAAgB,UAAU,gBAAgB,SAAU,OAAO,MAAM;AAC7D,YAAI,aAAa,OAAO;AACxB,YAAI,SAAS,IAAI,WAAW,UAAU;AACtC,iBAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACjC,iBAAO,CAAC,IAAI,OAAO,QAAQ,OAAO,GAAI,CAAC;AACvC,kBAAQ,SAAS,OAAO,CAAC;AAAA,QAC7B;AACA,aAAK,QAAQ,YAAY,IAAI,WAAW,MAAM,CAAC;AAAA,MACnD;AACA,MAAAA,iBAAgB,UAAU,gBAAgB,SAAU,OAAO;AACvD,aAAK,cAAoB,YAAY,OAAO,UAAU,KAAK,SAAS;AACpE,YAAI,SAAS;AAEb,YAAI,YAAY,CAAC;AACjB,iBAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACpC,cAAI,WAAW,OAAO,WAAW,CAAC;AAClC,cAAI,WAAW,KAAM;AACjB,sBAAU,KAAK,QAAQ;AAAA,UAC3B,WACS,WAAW,MAAO;AACvB,sBAAU,KAAK,MAAQ,YAAY,GAAI,MAAQ,WAAW,EAAK;AAAA,UACnE,WACS,WAAW,SAAU,YAAY,OAAQ;AAC9C,sBAAU,KAAK,MAAQ,YAAY,IAAK,MAAS,YAAY,IAAK,IAAO,MAAQ,WAAW,EAAK;AAAA,UACrG,OACK;AACD;AACA,uBAAW,UAAa,WAAW,SAAU,KAAO,OAAO,WAAW,CAAC,IAAI;AAC3E,sBAAU,KAAK,MAAQ,YAAY,IAAK,MAAS,YAAY,KAAM,IAAO,MAAS,YAAY,IAAK,IAAO,MAAQ,WAAW,EAAK;AAAA,UACvI;AAAA,QACJ;AAEA,aAAK,QAAQ,YAAY,UAAU,QAAQ,KAAK;AAChD,aAAK,QAAQ,YAAY,IAAI,WAAW,SAAS,CAAC;AAAA,MACtD;AACA,MAAAA,iBAAgB,UAAU,iBAAiB,SAAU,OAAO;AACxD,aAAK,cAAoB,YAAY,OAAO,WAAW,KAAK,SAAS;AACrE,aAAK,QAAQ,YAAY,QAAQ,IAAI,GAAG,IAAI;AAAA,MAChD;AACA,MAAAA,iBAAgB,UAAU,gBAAgB,SAAU,OAAO,QAAQ;AAC/D,YAAI,UAAU,QAAQ,UAAU,QAAW;AACvC,eAAK,QAAQ,YAAY,GAAG,IAAI;AAAA,QACpC,OACK;AACD,eAAK,QAAQ,YAAY,GAAG,IAAI;AAChC,eAAK,aAAa,OAAO,OAAO,MAAM;AAAA,QAC1C;AAAA,MACJ;AACA,MAAAA,iBAAgB,UAAU,cAAc,SAAU,OAAO,QAAQ;AAC7D,aAAK,cAAoB,YAAY,OAAO,KAAK,SAAS;AAC1D,YAAI,WAAW,OAAO,KAAK,KAAK,EAAE,CAAC;AACnC,iBAAS,IAAI,GAAG,IAAI,OAAO,MAAM,EAAE,QAAQ,KAAK;AAC5C,cAAI,cAAc,OAAO,MAAM,EAAE,CAAC;AAClC,cAAI,aAAa,OAAO,KAAK,YAAY,MAAM,EAAE,CAAC,GAAG;AACjD,iBAAK,QAAQ,YAAY,GAAG,IAAI;AAChC,mBAAO,KAAK,cAAc,OAAO,WAAW;AAAA,UAChD;AAAA,QACJ;AACA,cAAM,IAAI,MAAM,aAAa,OAAO,UAAU,8BAA8B,EAAE,OAAO,KAAK,UAAU,MAAM,GAAG,MAAM,EAAE,OAAO,KAAK,UAAU,KAAK,GAAG,CAAC,CAAC;AAAA,MACzJ;AACA,MAAAA,iBAAgB,UAAU,eAAe,SAAU,OAAO,QAAQ;AAC9D,YAAU,YAAY,KAAK;AACvB,iBAAO,KAAK,iBAAiB,OAAO,MAAM;AAC9C,YAAI,iBAAiB;AACjB,iBAAO,KAAK,cAAc,OAAO,MAAM;AAC3C,cAAM,IAAI,MAAM,2BAA2B,OAAO,OAAQ,OAAQ,GAAG,EAAE,OAAO,OAAO,OAAO,EAAE,OAAO,KAAK,UAAU,KAAK,GAAG,CAAC,CAAC;AAAA,MAClI;AACA,MAAAA,iBAAgB,UAAU,mBAAmB,SAAU,OAAO,QAAQ;AAClE,YAAI,OAAO,MAAM,KAAK;AAClB,UAAM,iBAAiB,MAAM,QAAQ,OAAO,MAAM,KAAK,KAAK,SAAS;AAAA,QACzE,OACK;AAED,eAAK,QAAQ,YAAY,MAAM,QAAQ,KAAK;AAAA,QAChD;AAEA,iBAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACnC,eAAK,aAAa,MAAM,CAAC,GAAG,OAAO,MAAM,IAAI;AAAA,QACjD;AAAA,MACJ;AACA,MAAAA,iBAAgB,UAAU,gBAAgB,SAAU,OAAO,QAAQ;AAC/D,YAAI,OAAO,MAAM,KAAK;AAClB,UAAM,iBAAiB,MAAM,YAAY,OAAO,MAAM,KAAK,KAAK,SAAS;AAAA,QAC7E,OACK;AAED,eAAK,QAAQ,YAAY,MAAM,YAAY,KAAK;AAAA,QACpD;AAEA,aAAK,QAAQ,YAAY,IAAI,WAAW,KAAK,CAAC;AAAA,MAClD;AACA,MAAAA,iBAAgB,UAAU,aAAa,SAAU,OAAO,QAAQ;AAC5D,aAAK,cAAoB,YAAY,OAAO,UAAU,KAAK,SAAS;AACpE,YAAI,QAAQ,iBAAiB;AAC7B,YAAI,SAAS,QAAQ,MAAM,KAAK,MAAM,OAAO,CAAC,IAAI,OAAO,OAAO,KAAK;AAErE,aAAK,QAAQ,YAAY,OAAO,QAAQ,KAAK;AAE7C,iBAAS,KAAK,GAAG,WAAW,QAAQ,KAAK,SAAS,QAAQ,MAAM;AAC5D,cAAI,UAAU,SAAS,EAAE;AACzB,eAAK,aAAa,SAAS,OAAO,GAAG;AAAA,QACzC;AAAA,MACJ;AACA,MAAAA,iBAAgB,UAAU,aAAa,SAAU,OAAO,QAAQ;AAC5D,aAAK,cAAoB,YAAY,OAAO,UAAU,KAAK,SAAS;AACpE,YAAI,QAAQ,iBAAiB;AAC7B,YAAI,OAAO,QAAQ,MAAM,KAAK,MAAM,KAAK,CAAC,IAAI,OAAO,KAAK,KAAK;AAE/D,aAAK,QAAQ,YAAY,KAAK,QAAQ,KAAK;AAE3C,iBAAS,KAAK,GAAG,SAAS,MAAM,KAAK,OAAO,QAAQ,MAAM;AACtD,cAAI,MAAM,OAAO,EAAE;AACnB,eAAK,aAAa,KAAK,OAAO,IAAI,GAAG;AACrC,eAAK,aAAa,QAAQ,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,OAAO,IAAI,KAAK;AAAA,QAC3E;AAAA,MACJ;AACA,MAAAA,iBAAgB,UAAU,gBAAgB,SAAU,OAAO,QAAQ;AAC/D,aAAK,cAAoB,YAAY,OAAO,UAAU,KAAK,SAAS;AACpE,iBAAS,KAAK,GAAG,KAAK,OAAO,KAAK,OAAO,MAAM,GAAG,KAAK,GAAG,QAAQ,MAAM;AACpE,cAAI,MAAM,GAAG,EAAE;AACf,eAAK,UAAU,KAAK,GAAG;AACvB,eAAK,aAAa,MAAM,GAAG,GAAG,OAAO,OAAO,GAAG,CAAC;AAChD,eAAK,UAAU,IAAI;AAAA,QACvB;AAAA,MACJ;AACA,aAAOA;AAAA,IACX,EAAE;AAAA;;;AC5KF,MAAI;AAAA;AAAA,IAAmC,WAAY;AAC/C,eAASC,mBAAkB,aAAa;AACpC,aAAK,SAAS,IAAI,aAAa,WAAW;AAAA,MAC9C;AAFS,aAAAA,oBAAA;AAGT,MAAAA,mBAAkB,UAAU,SAAS,SAAU,QAAQ;AACnD,eAAO,KAAK,aAAa,MAAM;AAAA,MACnC;AACA,MAAAA,mBAAkB,UAAU,eAAe,SAAU,QAAQ;AACzD,YAAI,OAAO,WAAW,UAAU;AAC5B,cAAI,SAAS,SAAS,MAAM;AACxB,mBAAO,KAAK,eAAe,MAAM;AACrC,cAAI,WAAW;AACX,mBAAO,KAAK,cAAc;AAC9B,cAAI,WAAW;AACX,mBAAO,KAAK,eAAe;AAAA,QACnC;AACA,YAAI,OAAO,WAAW,UAAU;AAC5B,cAAI,YAAY;AACZ,mBAAO,KAAK,cAAc,MAAM;AACpC,cAAI,UAAU;AACV,mBAAO,KAAK,YAAY,MAAM;AAClC,cAAI,WAAW;AACX,mBAAO,KAAK,aAAa,MAAM;AACnC,cAAI,SAAS;AACT,mBAAO,KAAK,WAAW,MAAM;AACjC,cAAI,SAAS;AACT,mBAAO,KAAK,WAAW,MAAM;AACjC,cAAI,YAAY;AACZ,mBAAO,KAAK,cAAc,MAAM;AAAA,QACxC;AACA,cAAM,IAAI,MAAM,qBAAqB,OAAO,MAAM,CAAC;AAAA,MACvD;AACA,MAAAA,mBAAkB,UAAU,iBAAiB,SAAU,QAAQ;AAC3D,YAAI,OAAO,SAAS,OAAO,UAAU,CAAC,CAAC;AACvC,YAAI,QAAQ,MAAM,UAAU,OAAO;AAC/B,iBAAO,KAAK,OAAO,cAAc,MAAM;AAAA,QAC3C;AACA,eAAO,KAAK,cAAc,MAAM,OAAO,WAAW,GAAG,CAAC;AAAA,MAC1D;AACA,MAAAA,mBAAkB,UAAU,gBAAgB,SAAU,MAAM,QAAQ;AAChE,YAAI,WAAW,QAAQ;AAAE,mBAAS;AAAA,QAAO;AACzC,YAAI,aAAa,OAAO;AACxB,YAAI,SAAS,IAAI,WAAW,KAAK,OAAO,cAAc,UAAU,CAAC;AACjE,YAAI,OAAO,OAAO,YAAY,SAAU,GAAG,GAAG;AAAE,iBAAO,IAAI,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAAA,QAAG,GAAG,EAAE;AACjG,YAAI,UAAU,OAAO,aAAa,CAAC,GAAG;AAClC,iBAAO,OAAO,OAAO,MAAM,OAAO,KAAK,OAAO,IAAI,CAAC,CAAC;AAAA,QACxD;AACA,eAAO,OAAO,KAAK,OAAO,IAAI,CAAC;AAAA,MACnC;AACA,MAAAA,mBAAkB,UAAU,gBAAgB,WAAY;AACpD,YAAI,MAAM,KAAK,eAAe,KAAK;AACnC,YAAI,SAAS,IAAI,WAAW,KAAK,OAAO,cAAc,GAAG,CAAC;AAG1D,YAAI,aAAa,CAAC;AAClB,iBAAS,IAAI,GAAG,IAAI,KAAK,EAAE,GAAG;AAC1B,cAAI,OAAO,OAAO,CAAC;AACnB,cAAI,OAAO,KAAM;AACb,uBAAW,KAAK,IAAI;AAAA,UACxB,WACS,OAAO,KAAM;AAClB,uBAAW,MAAO,OAAO,OAAS,IAAM,OAAO,EAAE,CAAC,IAAI,EAAK;AAAA,UAC/D,WACS,OAAO,KAAM;AAClB,uBAAW,MAAO,OAAO,OAAS,MAAQ,OAAO,EAAE,CAAC,IAAI,OAAS,IAAM,OAAO,EAAE,CAAC,IAAI,EAAK;AAAA,UAC9F,OACK;AACD,gBAAI,aAAc,OAAO,MAAS,MAAQ,OAAO,EAAE,CAAC,IAAI,OAAS,MAAQ,OAAO,EAAE,CAAC,IAAI,OAAS,IAAM,OAAO,EAAE,CAAC,IAAI;AACpH,uBAAW,KAAK,SAAS;AAAA,UAC7B;AAAA,QACJ;AAEA,eAAO,OAAO,cAAc,MAAM,QAAQ,UAAU;AAAA,MACxD;AACA,MAAAA,mBAAkB,UAAU,iBAAiB,WAAY;AACrD,eAAO,KAAK,OAAO,cAAc,IAAI,IAAI;AAAA,MAC7C;AACA,MAAAA,mBAAkB,UAAU,gBAAgB,SAAU,QAAQ;AAC1D,YAAI,SAAS,KAAK,OAAO,cAAc,IAAI;AAC3C,YAAI,WAAW,GAAG;AACd,iBAAO,KAAK,aAAa,OAAO,MAAM;AAAA,QAC1C;AACA,YAAI,WAAW,GAAG;AACd,gBAAM,IAAI,MAAM,kBAAkB,OAAO,MAAM,CAAC;AAAA,QACpD;AACA,eAAO;AAAA,MACX;AACA,MAAAA,mBAAkB,UAAU,cAAc,SAAU,QAAQ;AACxD,YAAI;AACJ,YAAI,aAAa,KAAK,OAAO,cAAc,IAAI;AAC/C,YAAI,aAAa,OAAO,MAAM,EAAE,QAAQ;AACpC,gBAAM,IAAI,MAAM,eAAe,OAAO,YAAY,mBAAmB,CAAC;AAAA,QAC1E;AACA,YAAI,SAAS,OAAO,MAAM,EAAE,UAAU,EAAE;AACxC,YAAI,MAAM,OAAO,KAAK,MAAM,EAAE,CAAC;AAC/B,eAAO,KAAK,CAAC,GAAG,GAAG,GAAG,IAAI,KAAK,aAAa,OAAO,GAAG,CAAC,GAAG;AAAA,MAC9D;AACA,MAAAA,mBAAkB,UAAU,eAAe,SAAU,QAAQ;AACzD,YAAI,SAAS,CAAC;AACd,YAAI,MAAM,OAAO,MAAM,MAAM,OAAO,MAAM,MAAM,KAAK,eAAe,KAAK;AACzE,iBAAS,IAAI,GAAG,IAAI,KAAK,EAAE,GAAG;AAC1B,iBAAO,KAAK,KAAK,aAAa,OAAO,MAAM,IAAI,CAAC;AAAA,QACpD;AACA,eAAO;AAAA,MACX;AACA,MAAAA,mBAAkB,UAAU,aAAa,SAAU,QAAQ;AACvD,YAAI,MAAM,KAAK,eAAe,KAAK;AACnC,YAAI,SAAS,oBAAI,IAAI;AACrB,iBAAS,IAAI,GAAG,IAAI,KAAK,EAAE,GAAG;AAC1B,iBAAO,IAAI,KAAK,aAAa,OAAO,GAAG,CAAC;AAAA,QAC5C;AACA,eAAO;AAAA,MACX;AACA,MAAAA,mBAAkB,UAAU,aAAa,SAAU,QAAQ;AACvD,YAAI,MAAM,KAAK,eAAe,KAAK;AACnC,YAAI,SAAS,oBAAI,IAAI;AACrB,iBAAS,IAAI,GAAG,IAAI,KAAK,EAAE,GAAG;AAC1B,cAAI,MAAM,KAAK,aAAa,OAAO,IAAI,GAAG;AAC1C,cAAI,QAAQ,KAAK,aAAa,OAAO,IAAI,KAAK;AAC9C,iBAAO,IAAI,KAAK,KAAK;AAAA,QACzB;AACA,eAAO;AAAA,MACX;AACA,MAAAA,mBAAkB,UAAU,gBAAgB,SAAU,QAAQ;AAC1D,YAAI,SAAS,CAAC;AACd,iBAAS,OAAO,OAAO,QAAQ;AAC3B,iBAAO,GAAG,IAAI,KAAK,aAAa,OAAO,OAAO,GAAG,CAAC;AAAA,QACtD;AACA,eAAO;AAAA,MACX;AACA,aAAOA;AAAA,IACX,EAAE;AAAA;;;AClIK,WAAS,UAAU,QAAQ,OAAO,UAAU;AAC/C,QAAI,aAAa,QAAQ;AAAE,iBAAW;AAAA,IAAM;AAC5C,QAAI;AACA,MAAM,gBAAgB,MAAM;AAChC,QAAI,aAAa,IAAI,gBAAgB,QAAQ;AAC7C,WAAO,WAAW,OAAO,OAAO,MAAM;AAAA,EAC1C;AANgB;AAOT,WAAS,YAAY,QAAQ,QAAQ,UAAU;AAClD,QAAI,aAAa,QAAQ;AAAE,iBAAW;AAAA,IAAM;AAC5C,QAAI;AACA,MAAM,gBAAgB,MAAM;AAChC,QAAI,eAAe,IAAI,kBAAkB,MAAM;AAC/C,WAAO,aAAa,OAAO,MAAM;AAAA,EACrC;AANgB;;;ACVhB;AAAA;AAAA;AAAA;AAAA;AAEO,MAAM,kBAAkB,IAAK,MAAM,YAAY;AAAA,IAFtD,OAEsD;AAAA;AAAA;AAAA,IACpD,mBAA2B;AAAA,MACzB,QAAQ;AAAA,QACN,MAAM,EAAE,OAAO,EAAE,MAAM,MAAM,KAAK,GAAG,EAAE;AAAA,MACzC;AAAA,IACF;AAAA,IACA,qBAA6B;AAAA,MAC3B,QAAQ;AAAA,QACN,MAAM,EAAE,OAAO,EAAE,MAAM,MAAM,KAAK,GAAG,EAAE;AAAA,MACzC;AAAA,IACF;AAAA,IACA,YAAoB;AAAA,MAClB,MAAM;AAAA,QACJ,EAAE,QAAQ,EAAE,kBAAkB,KAAK,iBAAiB,EAAE;AAAA,QACtD,EAAE,QAAQ,EAAE,oBAAoB,KAAK,mBAAmB,EAAE;AAAA,MAC5D;AAAA,IACF;AAAA,IACA,cAAsB;AAAA,MACpB,QAAQ;AAAA,QACN,MAAM,EAAE,OAAO,EAAE,MAAM,MAAM,KAAK,GAAG,EAAE;AAAA,MACzC;AAAA,IACF;AAAA,IACA,gBAAwB;AAAA,MACtB,QAAQ;AAAA,QACN,MAAM,EAAE,OAAO,EAAE,MAAM,MAAM,KAAK,GAAG,EAAE;AAAA,MACzC;AAAA,IACF;AAAA,IACA,YAAoB;AAAA,MAClB,MAAM;AAAA,QACJ,EAAE,QAAQ,EAAE,YAAY,KAAK,YAAY,EAAE;AAAA,QAC3C,EAAE,QAAQ,EAAE,cAAc,KAAK,cAAc,EAAE;AAAA,MACjD;AAAA,IACF;AAAA,IACA,yBAAiC;AAAA,MAC/B,QAAQ;AAAA,QACN,WAAW,EAAE,QAAQ,OAAO;AAAA,QAC5B,YAAY;AAAA,QACZ,aAAa,EAAE,OAAO,EAAE,MAAM,SAAS,EAAE;AAAA,MAC3C;AAAA,IACF;AAAA,IACA,uBAA+B;AAAA,MAC7B,QAAQ,CAAC;AAAA,IACX;AAAA,IACA,sBAA8B;AAAA,MAC5B,MAAM;AAAA,QACJ,EAAE,QAAQ,EAAE,cAAc,KAAK,uBAAuB,EAAE;AAAA,QACxD,EAAE,QAAQ,EAAE,YAAY,KAAK,qBAAqB,EAAE;AAAA,MACtD;AAAA,IACF;AAAA,IACA,YAAoB;AAAA,MAClB,QAAQ;AAAA,QACN,OAAO;AAAA,QACP,YAAY,KAAK;AAAA,MACnB;AAAA,IACF;AAAA,IACA,gBAAwB;AAAA,MACtB,QAAQ,CAAC;AAAA,IACX;AAAA,IACA,iBAAyB;AAAA,MACvB,QAAQ;AAAA,QACN,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,EAAE;AAAA,MAChC;AAAA,IACF;AAAA,IACA,eAAuB;AAAA,MACrB,QAAQ;AAAA,QACN,YAAY;AAAA,QACZ,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,EAAE;AAAA,QAC9B,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,WAAmB;AAAA,MACjB,QAAQ;AAAA,QACN,SAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,QAAgB;AAAA,MACd,QAAQ;AAAA,QACN,OAAO;AAAA,QACP,WAAW,KAAK;AAAA,MAClB;AAAA,IACF;AAAA,IACA,SAAiB;AAAA,MACf,QAAQ;AAAA,QACN,WAAW,KAAK;AAAA,QAChB,WAAW,KAAK;AAAA,MAClB;AAAA,IACF;AAAA,IACA,YAAoB;AAAA,MAClB,QAAQ;AAAA,QACN,WAAW,KAAK;AAAA,MAClB;AAAA,IACF;AAAA,IACA,gBAAwB;AAAA,MACtB,QAAQ;AAAA,QACN,eAAe;AAAA,MACjB;AAAA,IACF;AAAA,IACA,gBAAwB;AAAA,MACtB,MAAM;AAAA,QACJ,EAAE,QAAQ,EAAE,eAAe,KAAK,cAAc,EAAE;AAAA,QAChD,EAAE,QAAQ,EAAE,gBAAgB,KAAK,eAAe,EAAE;AAAA,QAClD,EAAE,QAAQ,EAAE,cAAc,KAAK,aAAa,EAAE;AAAA,QAC9C,EAAE,QAAQ,EAAE,UAAU,KAAK,SAAS,EAAE;AAAA,QACtC,EAAE,QAAQ,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,QAChC,EAAE,QAAQ,EAAE,QAAQ,KAAK,OAAO,EAAE;AAAA,QAClC,EAAE,QAAQ,EAAE,WAAW,KAAK,UAAU,EAAE;AAAA,QACxC,EAAE,QAAQ,EAAE,eAAe,KAAK,cAAc,EAAE;AAAA,MAClD;AAAA,IACF;AAAA,IACA,iBAAyB;AAAA,MACvB,QAAQ;AAAA,QACN,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,cAAc,EAAE;AAAA,QAC/C,OAAO;AAAA,QACP,gBAAgB;AAAA,QAChB,WAAW,KAAK;AAAA,MAClB;AAAA,IACF;AAAA,IACA,iBAAyB;AAAA,MACvB,QAAQ;AAAA,QACN,gBAAgB,KAAK;AAAA,QACrB,WAAW,KAAK;AAAA,MAClB;AAAA,IACF;AAAA,IACA,SAAiB;AAAA,MACf,MAAM;AAAA,QACJ,EAAE,QAAQ,EAAE,eAAe,KAAK,cAAc,EAAE;AAAA,QAChD,EAAE,QAAQ,EAAE,gBAAgB,KAAK,eAAe,EAAE;AAAA,QAClD,EAAE,QAAQ,EAAE,cAAc,KAAK,aAAa,EAAE;AAAA,QAC9C,EAAE,QAAQ,EAAE,UAAU,KAAK,SAAS,EAAE;AAAA,QACtC,EAAE,QAAQ,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,QAChC,EAAE,QAAQ,EAAE,QAAQ,KAAK,OAAO,EAAE;AAAA,QAClC,EAAE,QAAQ,EAAE,WAAW,KAAK,UAAU,EAAE;AAAA,QACxC,EAAE,QAAQ,EAAE,eAAe,KAAK,cAAc,EAAE;AAAA,QAChD,EAAE,QAAQ,EAAE,gBAAgB,KAAK,eAAe,EAAE;AAAA,MACpD;AAAA,IACF;AAAA,IACA,cAAsB;AAAA,MACpB,QAAQ;AAAA,QACN,UAAU;AAAA,QACV,WAAW,KAAK;AAAA,QAChB,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,WAAW,EAAE,OAAO,EAAE,MAAM,MAAM,KAAK,GAAG,EAAE;AAAA,QAC5C,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,EAAE;AAAA,MAC1C;AAAA,IACF;AAAA,IACA,oBAA4B;AAAA,MAC1B,QAAQ;AAAA,QACN,aAAa,KAAK;AAAA,QAClB,WAAW,KAAK;AAAA,MAClB;AAAA,IACF;AAAA,EACF,EAAG;AAEI,MAAM,iBAAiB,6BAAM,iBAAN;;;AC3IvB,MAAM,WAAW,wBAAC,OAA8C;AACrE,WAAO,KAAK,MAAM,KAAK;AAAA,MAAU;AAAA,MAAI,CAAC,KAAK,UACzC,OAAO,UAAU,WAAW,MAAM,SAAS,IAAI;AAAA,IACjD,CAAC;AAAA,EACH,GAJwB;AAOjB,MAAM,sBAAsB,wBAAC,OAAiC;AACnE,WAAO,KAAK,UAAU,SAAS,EAAE,CAAC;AAAA,EACpC,GAFmC;AAI5B,WAAS,eAAe,iBAAmC;AAChE,WAAO;AAAA,MACL,UAAU,gBAAgB;AAAA,MAC1B,WAAW;AAAA,QACT,YAAY;AAAA,UACV,MAAM,cAAc,gBAAgB,SAAS;AAAA,QAC/C;AAAA,MACF;AAAA,MACA,OAAO,OAAO,gBAAgB,KAAK;AAAA,MACnC,YAAY,gBAAgB;AAAA,MAC5B,WAAW,yBAAW,gBAAgB,SAAS;AAAA,MAC/C,SAAS,gBAAgB,QAAQ,IAAI,SAAS;AAAA,IAChD;AAAA,EACF;AAbgB;AAeT,WAAS,qBAAqB,iBAAmC;AACtE,YAAQ,IAAI,mCAAmC;AAE/C,UAAM,cAAc,eAAe,eAAe;AAClD,YAAQ,IAAI,2CAA2C,WAAW;AAElE,WAAO,UAAe,OAAO,aAAa,WAAW;AAAA,EACvD;AAPgB;AAST,WAAS,2BAA2B,iBAAmC,WAAW;AACvF,YAAQ,IAAI,4CAA4C,eAAe;AACvE,YAAQ,IAAI,uBAAuB,SAAS;AAC5C,YAAQ,IAAI,8BAA8B,yBAAW,SAAS,EAAE,MAAM;AAEtE,UAAM,iBAAiB,eAAe,eAAe;AACrD,YAAQ,IAAI,0DAA0D,cAAc;AAEpF,UAAM,yBAAiD;AAAA,MACrD,aAAa;AAAA,MACb,WAAW;AAAA,QACT,kBAAkB;AAAA,UAChB,MAAM,yBAAW,SAAS;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAEA,UAAM,gBAAgB,UAAe,OAAO,mBAAmB,wBAAwB,IAAI;AAC3F,YAAQ,IAAI,kDAAkD,aAAa;AAE3E,WAAO;AAAA,EACT;AArBgB;AAuBT,WAAS,UAAU,QAAqB;AAC7C,YAAQ,OAAO,MAAM;AAAA,MACnB,KAAK,iBAAiB;AACpB,eAAO;AAAA,UACL,eAAe,CAAC;AAAA,QAClB;AAAA,MACF;AAAA,MACA,KAAK,kBAAkB;AACrB,eAAO;AAAA,UACL,gBAAgB;AAAA,YACd,MAAM,cAAc,OAAO,UAAU;AAAA,UACvC;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK,gBAAgB;AACnB,eAAO;AAAA,UACL,cAAc;AAAA,YACZ,YAAY,OAAO;AAAA,YACnB,MAAO,OAAO,eAAe,QAAQ,OAAO,eAAe,SACzD,cAAc,OAAO,UAAU,IAC9B,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,OAAO,IAAI,CAAC;AAAA,YACvD,KAAK,OAAO,OAAO,OAAO,iBAAiB;AAAA,YAC3C,SAAS,OAAO,OAAO,WAAW,GAAG;AAAA,UACvC;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK,YAAY;AACf,eAAO;AAAA,UACL,UAAU;AAAA,YACR,SAAS,OAAO,OAAO,OAAO;AAAA,UAChC;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK,SAAS;AACZ,eAAO;AAAA,UACL,OAAO;AAAA,YACL,OAAO,OAAO,OAAO,KAAK;AAAA,YAC1B,WAAW;AAAA,cACT,YAAY;AAAA,gBACV,MAAM,cAAc,OAAO,SAAS;AAAA,cACtC;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK,UAAU;AACb,eAAO;AAAA,UACL,QAAQ;AAAA,YACN,WAAW;AAAA,cACT,YAAY;AAAA,gBACV,MAAM,cAAc,OAAO,SAAS;AAAA,cACtC;AAAA,YACF;AAAA,YACA,WAAW;AAAA,cACT,OAAO,OAAO,OAAO,UAAU,KAAK;AAAA,cACpC,YACE,OAAO,UAAU,eAAe,eAC5B,EAAE,YAAY,CAAC,EAAE,IACjB;AAAA,gBACA,cAAc;AAAA,kBACZ,WAAW,OAAO,UAAU,YACxB,OAAO,OAAO,UAAU,SAAS,IACjC;AAAA,kBACJ,YAAY,OAAO,UAAU;AAAA,kBAC7B,aAAa,OAAO,UAAU;AAAA,gBAChC;AAAA,cACF;AAAA,YACN;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK,aAAa;AAChB,eAAO;AAAA,UACL,WAAW;AAAA,YACT,WAAW;AAAA,cACT,YAAY;AAAA,gBACV,MAAM,cAAc,OAAO,SAAS;AAAA,cACtC;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK,iBAAiB;AACpB,eAAO;AAAA,UACL,eAAe;AAAA,YACb,eAAe,OAAO;AAAA,UACxB;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK,kBAAkB;AACrB,eAAO;AAAA,UACL,gBAAgB;AAAA,YACd,gBAAgB,UAAU,OAAO,cAAc;AAAA,YAC/C,WAAW;AAAA,cACT,kBAAkB,yBAAW,OAAO,SAAS;AAAA,YAC/C;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA,SAAS;AACP,cAAM,IAAI,MAAM,6BAA6B,OAAO,IAAI;AAAA,MAC1D;AAAA,IACF;AAAA,EACF;AAvGgB;AAyGT,MAAM,SAAS,eAAe;;;A5B9KrC,MAAM,MAAM;AAAA,IACV,OAAO;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,EACF;;;A6BfA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACgFO,MAAM,gBAAN,MAAM,eAAc;AAAA,IAhF3B,OAgF2B;AAAA;AAAA;AAAA;AAAA,IAEzB,UAAoC;AAAA;AAAA,IAGpC;AAAA;AAAA,IAGA;AAAA;AAAA,IAGA,WAAW,oBAAI,IAAI;AAAA;AAAA,IAGnB;AAAA;AAAA,IAGA;AAAA;AAAA,IAGA;AAAA;AAAA,IAGA,OAAO,mBAAmB;AAAA;AAAA;AAAA;AAAA,IAK1B,YAAY;AAAA,MACV,YAAY,eAAc;AAAA,MAC1B,eAAe;AAAA,MACf;AAAA,MACA;AAAA,MACA,cAAc,OAAO,SAAS;AAAA,IAChC,IAA8B,CAAC,GAAG;AAChC,WAAK,gBAAgB;AACrB,WAAK,aAAa;AAClB,WAAK,iBAAiB;AACtB,WAAK,eAAe;AACpB,WAAK,SAAS,aAAa,CAAC;AAC5B,aAAO,iBAAiB,WAAW,KAAK,eAAe,KAAK,IAAI,CAAC;AAAA,IACnE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,cAAc,MAAM;AAElB,UAAI,KAAK,SAAS;AAChB,aAAK,QAAQ,OAAO;AAAA,MACtB;AAGA,YAAM,MAAM,IAAI,IAAI,MAAM,KAAK,UAAU;AAGzC,YAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,aAAO,MAAM,IAAI,SAAS;AAC1B,aAAO,QAAQ;AACf,aAAO,MAAM,SAAS;AACtB,aAAO,MAAM,SAAS;AACtB,aAAO,MAAM,WAAW;AACxB,aAAO,MAAM,UAAU;AACvB,aAAO,MAAM,MAAM;AACnB,aAAO,MAAM,OAAO;AACpB,aAAO,MAAM,QAAQ;AACrB,aAAO,MAAM,SAAS;AACtB,eAAS,KAAK,YAAY,MAAM;AAEhC,WAAK,UAAU;AACf,aAAO;AAAA,IACT;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,eAAeC,QAAO;AAEpB,UAAI,KAAK,kBAAkB,OAAOA,OAAM,WAAW,KAAK,eAAe;AACrE;AAAA,MACF;AAEA,YAAM,EAAE,IAAI,MAAM,QAAQ,QAAQ,IAAIA,OAAM;AAC5C,UAAI,SAAS,iBAAkB;AAG/B,UAAI,WAAW,SAAS;AACtB,aAAK,SAAS,OAAO;AACrB,aAAK,UAAU;AACf;AAAA,MACF;AAGA,UAAI,SAAS,OAAO;AAClB,aAAK,SAAS,EAAE,GAAG,KAAK,QAAQ,GAAG,QAAQ,MAAM;AACjD,aAAK,iBAAiB,KAAK,MAAM;AAAA,MACnC;AAGA,YAAM,UAAU,KAAK,SAAS,IAAI,EAAE;AACpC,UAAI,SAAS;AACX,aAAK,SAAS,OAAO,EAAE;AACvB,aAAK,SAAS,OAAO;AACrB,aAAK,UAAU;AACf,gBAAQ,OAAuB;AAAA,MACjC;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA,MAAM,aAAa,MAAM,QAAQ,QAAoC;AACnE,aAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,cAAM,KAAK,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC;AAC7C,aAAK,SAAS,IAAI,IAAI,OAAO;AAE7B,cAAM,SAAS,KAAK,cAAc,IAAI;AAEtC,eAAO,SAAS,MAAM;AACpB,iBAAO,eAAe;AAAA,YACpB;AAAA,cACE,MAAM;AAAA,cACN;AAAA,cACA,QAAQ;AAAA,gBACN;AAAA,gBACA,GAAG;AAAA,gBACH,OAAO,KAAK;AAAA,gBACZ,aAAa,OAAO,eAAe,KAAK;AAAA,cAC1C;AAAA,YACF;AAAA,YACA,KAAK;AAAA,UACP;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,WAAW;AACT,aAAO,EAAE,GAAG,KAAK,OAAO;AAAA,IAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,SAASC,QAAO;AACd,WAAK,SAASA;AAAA,IAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA,MAAM,OAAOC,SAAsB;AACjC,aAAO,KAAK,aAAa,eAAe,UAAUA,OAAM;AAAA,IAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,MAAM,iBAAiBA,SAAoC;AACzD,aAAO,KAAK,aAAa,cAAc,oBAAoBA,OAAM;AAAA,IACnE;AAAA;AAAA;AAAA;AAAA,IAKA,UAAU;AACR,aAAO,oBAAoB,WAAW,KAAK,cAAc;AACzD,WAAK,SAAS,OAAO;AACrB,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;;;ADpQO,MAAM,aAAa;AAEnB,MAAM,qBAAqB;AAC3B,MAAM,WAAW;AAAA,IACtB,SAAS;AAAA,MACP,WAAW;AAAA,MACX,SAAS;AAAA,IACX;AAAA,IACA,SAAS;AAAA,MACP,WAAW;AAAA,MACX,SAAS;AAAA,IACX;AAAA,EACF;AAmDO,MAAI,UAAyB,MAAM,QAAQ,KAAK;AAAA,IACrD,GAAG,SAAS,kBAAkB;AAAA,EAChC;AAGO,MAAI,SAAmB,MAAM,OAAO,KAAK,CAAC;AAG1C,MAAM,uBAAuB,wBAACC,WAA8B;AACjE,YAAQ,IAAI,yBAAyBA,MAAK;AAC1C,UAAM,EAAE,WAAAC,YAAW,cAAc,WAAW,IAAID;AAChD,WAAO;AAAA,MACL,WAAWC,cAAa;AAAA,MACxB,cAAc,gBAAgB;AAAA,MAC9B,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,IACrC,CAAC;AAAA,EACH,GARoC;AAU7B,MAAM,wBAAwB,6BAA0B;AAC7D,WAAO;AAAA,MACL,WAAW,OAAO;AAAA,MAClB,WAAW,OAAO;AAAA,MAClB,cAAc,OAAO;AAAA,MACrB,WAAW,QAAQ;AAAA,IACrB;AAAA,EACF,GAPqC;AAU9B,MAAI,WAAW,IAAI,cAAc;AAAA,IACtC,eAAe;AAAA,IACf,WAAW,sBAAsB;AAAA,IACjC,WAAW;AAAA,EACb,CAAC;AAGD,MAAI;AACF,WAAO,YAAY,OAAO,aACtB,qBAAqB,OAAO,UAAU,IACtC;AAAA,EACN,SAAS,GAAG;AACV,YAAQ,MAAM,8BAA8B,CAAC;AAC7C,WAAO,aAAa;AACpB,UAAM,SAAS,IAAI;AAAA,EACrB;AAGO,MAAI,aAAwB,MAAM,WAAW,KAAK,CAAC;AAGnD,MAAM,uBAA4C;AAAA,IACvD,SAAS,CAAC;AAAA,IACV,IAAI,CAAC;AAAA,EACP;AAGO,MAAM,SAAS;AAAA,IACpB,iBAAiB;AAAA,MACf,SAAS,oBAAI,IAAI;AAAA,MACjB,IAAI,oBAAI,IAAI;AAAA,IACd;AAAA,IAEA,wBAAwB,wBAACA,eAAsB;AAC7C,UAAI,OAAO,gBAAgB,QAAQ,SAAS,GAAG;AAC7C,6BAAqB,QAAQ,KAAKA,UAAS;AAC3C;AAAA,MACF;AACA,aAAO,gBAAgB,QAAQ,QAAQ,CAAC,aAAkB;AACxD,YAAI;AACF,mBAASA,UAAS;AAAA,QACpB,SAAS,GAAG;AACV,kBAAQ,MAAM,CAAC;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,IACH,GAZwB;AAAA,IAcxB,mBAAmB,wBAAC,OAAiB;AACnC,UAAI,OAAO,gBAAgB,GAAG,SAAS,GAAG;AACxC,6BAAqB,GAAG,KAAK,EAAE;AAC/B;AAAA,MACF;AACA,aAAO,gBAAgB,GAAG,QAAQ,CAAC,aAAkB;AACnD,YAAI;AACF,mBAAS,EAAE;AAAA,QACb,SAAS,GAAG;AACV,kBAAQ,MAAM,CAAC;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,IACH,GAZmB;AAAA,IAcnB,WAAW,wBAAC,aAA0C;AACpD,aAAO,gBAAgB,QAAQ,IAAI,QAAQ;AAC3C,UAAI,qBAAqB,QAAQ,SAAS,GAAG;AAC3C,cAAM,eAAe,qBAAqB;AAC1C,6BAAqB,UAAU,CAAC;AAChC,qBAAa,QAAQ,OAAO,sBAAsB;AAAA,MACpD;AAAA,IACF,GAPW;AAAA,IASX,MAAM,wBAAC,aAA2C;AAChD,aAAO,gBAAgB,GAAG,IAAI,QAAQ;AACtC,UAAI,qBAAqB,GAAG,SAAS,GAAG;AACtC,cAAM,UAAU,qBAAqB;AACrC,6BAAqB,KAAK,CAAC;AAC3B,gBAAQ,QAAQ,OAAO,iBAAiB;AAAA,MAC1C;AAAA,IACF,GAPM;AAAA,EAQR;AAKO,MAAM,SAAS,wBAAC,aAAgC;AACrD,UAAM,WAAW;AACjB,aAAS,EAAC,GAAG,QAAQ,GAAG,SAAQ;AAEhC,UAAM,SAAS;AAAA,MACb,WAAW,OAAO;AAAA,MAClB,YAAY,OAAO;AAAA,MACnB,cAAc,OAAO;AAAA,MACrB,qBAAqB,OAAO;AAAA,IAC9B,CAAC;AAED,QACE,SAAS,eAAe,YAAY,KACpC,SAAS,eAAe,SAAS,YACjC;AACA,aAAO,YAAY,SAAS,aACxB,qBAAqB,SAAS,UAAoB,IAClD;AACJ,YAAM,SAAS,IAAI;AAAA,IACrB;AAEA,QAAI,SAAS,cAAc,SAAS,WAAW;AAC7C,aAAO,uBAAuB,SAAS,SAAmB;AAAA,IAC5D;AAEA,QACG,SAAS,eAAe,cAAc,KACrC,SAAS,iBAAiB,SAAS,gBACpC,SAAS,eAAe,WAAW,KAClC,SAAS,cAAc,SAAS,aACjC,SAAS,eAAe,YAAY,KACnC,SAAS,eAAe,SAAS,YACnC;AACA,eAAS,SAAS,sBAAsB,CAAC;AAAA,IAC3C;AAAA,EACF,GAnCsB;AAqCf,MAAM,kBAAkB,wBAAC,aAAuB;AACrD,UAAM,OAAO,SAAS;AACtB,eAAW,IAAI,IAAI;AAAA,MACjB,GAAI,WAAW,IAAI,KAAK,CAAC;AAAA,MACzB,GAAG;AAAA,MACH,iBAAiB,KAAK,IAAI;AAAA,IAC5B;AACA,UAAM,aAAa,UAAU;AAC7B,WAAO,kBAAkB,WAAW,IAAI,CAAC;AAAA,EAC3C,GAT+B;AAWxB,MAAM,YAAY,6BAAqB;AAC5C,WAAO;AAAA,EACT,GAFyB;AAIlB,MAAM,eAAe,6BAAiB;AAC3C,WAAO;AAAA,EACT,GAF4B;AAKrB,MAAM,YAAY,wBAAC,YAAiC;AACzD,cAAU,EAAE,GAAG,SAAS,QAAQ,SAAS,GAAG,GAAG,QAAQ;AACvD,UAAM,UAAU,OAAO;AAAA,EACzB,GAHyB;AAKlB,MAAM,iBAAiB,6BAAY;AACxC,iBAAa,CAAC;AACd,UAAM,aAAa,UAAU;AAAA,EAC/B,GAH8B;;;AE9M9B,cAAI,KAAK;AACF,MAAM,kBAAkB,MAAO,KAAK,KAAK;AAkCzC,WAAS,YAAY,QAA6B,SAAkB;AACzE,QAAI,YAAY,WAAW,YAAY,cAAc;AACnD,aAAO,EAAE,GAAG,QAAQ,UAAU,QAAQ;AAAA,IACxC;AACA,WAAO,UAAU,EAAE,GAAG,QAAQ,UAAU,QAAQ,IAAI,EAAE,GAAG,QAAQ,UAAU,aAAa;AAAA,EAC1F;AALgB;AAOhB,iBAAsB,QAAQ,QAAgB,QAAqC;AACjF,UAAMC,UAAS,UAAU;AACzB,QAAI,CAACA,SAAQ,SAAS;AACpB,YAAM,IAAI,MAAM,iEAAiE;AAAA,IACnF;AACA,UAAM,WAAW,MAAM,MAAMA,QAAO,SAAS;AAAA,MAC3C,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,SAAS;AAAA,QACT,IAAI,YAAY,KAAK,IAAI,CAAC;AAAA,QAC1B;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AACD,UAAM,SAAS,MAAM,SAAS,KAAK;AACnC,QAAI,OAAO,OAAO;AAChB,YAAM,IAAI,MAAM,KAAK,UAAU,OAAO,KAAK,CAAC;AAAA,IAC9C;AACA,WAAO;AAAA,EACT;AApBsB;AAsBf,WAAS,YAAY,MAAc;AACxC,UAAM,YAAY,aAAa;AAC/B,YAAQ,MAAM;AAAA,MACZ,SAAS,UAAU,IAAI,GAAG;AAAA,MAC1B,mBAAmB,UAAU,IAAI,GAAG,IAAI;AAAA,MACxC,YAAY;AAAA,IACd,CAAC,EACE,KAAM,YAAU;AACf,YAAM,eAAe,QAAQ,QAAQ,QAAQ;AAC7C,sBAAgB;AAAA,QACd;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,QACA,cAAc,eAAe,aAAa,WAAW,YAAY,CAAC,IAAI;AAAA,QACtE,YAAY;AAAA,MACd,CAAC;AAAA,IACH,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,sBAAgB;AAAA,QACd;AAAA,QACA,QAAQ;AAAA,QACR,OAAO,aAAa,MAAM,OAAO,KAAK,MAAM;AAAA,QAC5C,YAAY;AAAA,MACd,CAAC;AAAA,IACH,CAAC;AAAA,EACL;AAzBgB;AA2BhB,iBAAsB,YAAY,gBAAwB,WAA+B,MAAc;AAGrG,gBAAY,aAAa;AAEzB,QAAI;AACF,YAAM,YAAY,MAAM,QAAQ,WAAW;AAAA,QACzC,kBAAkB;AAAA,QAClB,YAAY;AAAA,MACd,CAAC;AAED,sBAAgB,EAAE,MAAM,QAAQ,YAAY,YAAY,MAAM,CAAC;AAC/D,kBAAY,IAAI;AAEhB,aAAO;AAAA,IACT,SAAS,OAAO;AACd,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU;AAC9D,sBAAgB;AAAA,QACd;AAAA,QACA,QAAQ;AAAA,QACR,OAAO,aAAa,YAAY,KAAK;AAAA,QACrC,YAAY;AAAA,MACd,CAAC;AACD,YAAM,IAAI,MAAM,YAAY;AAAA,IAC9B;AAAA,EACF;AAzBsB;AA4Cf,WAAS,eAAuB;AACrC,UAAM,aAAa,OAAO,gBAAgB,IAAI,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE;AACrE,WAAO,MAAM,KAAK,IAAI,CAAC,IAAI,SAAS,YAAY,EAAE,EAAE,SAAS,EAAE,CAAC;AAAA,EAClE;AAHgB;AAKT,MAAM,YAAY,6BAAM,OAAO,WAAb;AAClB,MAAM,YAAY,6BAAM,OAAO,WAAb;AAElB,MAAM,SAAS,wBAAC,cAAoC;AACzD,UAAM,UAAU,UAAU;AAC1B,QAAI,WAAW;AACb,UAAI,UAAU,aAAa,QAAQ,cAAc,UAAU,WAAW;AACpE,kBAAU,UAAU,SAAS;AAC7B,eAAO,EAAE,WAAW,MAAM,YAAY,MAAM,cAAc,KAAK,CAAC;AAChE,cAAM,SAAS,IAAI;AACnB,uBAAe;AAAA,MACjB;AACA,gBAAU,EAAE,GAAG,UAAU,GAAG,GAAG,UAAU,CAAC;AAAA,IAC5C;AACA,WAAO,UAAU;AAAA,EACnB,GAZsB;AAcf,MAAM,aAAa,6BAAoC;AAC5D,QAAI,CAAC,OAAO,WAAW;AACrB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,GAL0B;AAenB,MAAM,0BAA0B,wBAAC,SAAe;AACrD,WAAO,UAAU;AAAA,EACnB,GAFuC;AAOhC,MAAM,WAAW,6BAAM;AAC5B,UAAM,UAAU,UAAU,EAAE;AAC5B,UAAM,UAAU,UAAU,EAAE;AAC5B,UAAM,YAAY,UAAU,EAAE;AAC9B,UAAM,YAAY,UAAU,EAAE;AAC9B,UAAM,cAAc,UAAU,EAAE;AAEhC,UAAM,UAAU,UAAU;AAC1B,UAAM,WAAW,OAAO;AACxB,UAAMC,aAAY,wBAAwB;AAE1C,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAAA;AAAA,IACF;AAAA,EACF,GArBwB;AAuBjB,MAAM,gBAAgB,8BAAO,EAAE,WAAW,MAA8B;AAC7E,UAAM,aAAa,qBAAqB;AACxC,WAAO,EAAE,qBAAqB,YAAY,WAAW,MAAM,WAAW,CAAC;AACvE,UAAM,SAAS,qBAAqB,UAAU;AAE9C,UAAM,SAAS,MAAM,SAAS,OAAO;AAAA,MACnC,WAAW,UAAU,EAAE;AAAA,MACvB;AAAA,MACA,WAAW;AAAA,IACb,CAAC;AAED,QAAI,OAAO,OAAO;AAChB,YAAM,IAAI,MAAM,iBAAiB,OAAO,KAAK,EAAE;AAAA,IACjD;AACA,QAAI,OAAO,KAAK;AACd,UAAI,OAAO,WAAW,aAAa;AACjC,mBAAW,MAAM;AACf,iBAAO,SAAS,OAAO,OAAO;AAAA,QAChC,GAAG,GAAG;AAAA,MACR;AAAA,IACF,WAAW,OAAO,WAAW;AAC3B,aAAO,EAAE,WAAW,OAAO,UAAU,CAAC;AAAA,IACxC;AAAA,EACF,GAvB6B;AAyBtB,MAAM,OAAO,8BAAO;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,MAMrB;AACJ,UAAM,cAAc,eAAe,OAAO,SAAS,KAAK,UAAU,IAAI,CAAC,IAAI;AAC3E,UAAM,cAAc,MAAM;AAAA,MACxB;AAAA,MACA;AAAA,QACE;AAAA,UACE,cAAc;AAAA,UACd,YAAY;AAAA,UACZ,aAAa;AAAA,UACb,aAAa;AAAA,QACf;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,WAAO,mBAAmB,YAAY,OAAO,MAAM;AAAA,EACrD,GA5BoB;AA8Bb,MAAM,eAAe,8BAAO;AAAA,IACH,WAAAC;AAAA,IACA;AAAA,EACF,MAGxB;AACJ,WAAO;AAAA,MACL;AAAA,MACA,YAAY,EAAE,cAAc,gBAAgB,YAAYA,WAAU,GAAG,OAAO;AAAA,IAC9E;AAAA,EACF,GAX4B;AAarB,MAAM,aAAa,8BAAO,EAAE,QAAQ,MAAgD;AACzF,WAAO,QAAQ,SAAS,YAAY,CAAC,GAAG,OAAO,CAAC;AAAA,EAClD,GAF0B;AAInB,MAAM,iBAAiB,8BAAO;AAAA,IACH,WAAAA;AAAA,IACA,WAAAD;AAAA,IACA;AAAA,EACF,MAIG;AACjC,WAAO;AAAA,MACL;AAAA,MACA;AAAA,QACE,EAAE,cAAc,mBAAmB,YAAYC,YAAW,YAAYD,WAAU;AAAA,QAChF;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAhB8B;AAkBvB,MAAM,UAAU,8BAAO,EAAE,QAAQ,WAAAC,WAAU,MAA6C;AAC7F,WAAO,QAAQ,MAAM,CAAC,QAAQA,UAAS,CAAC;AAAA,EAC1C,GAFuB;AAIhB,MAAM,iBAAiB,6BAAM;AAClC,WAAO,aAAa;AAAA,EACtB,GAF8B;AAIvB,MAAM,UAAU,6BAAM;AAC3B,WAAO,EAAE,WAAW,MAAM,YAAY,MAAM,YAAY,KAAK,CAAC;AAC9D,cAAU,SAAS,kBAAkB,CAAC;AAAA,EACxC,GAHuB;AAKhB,MAAM,SAAS,8BAAO;AAAA,IACE;AAAA,IACA,SAAAC;AAAA,IACA;AAAA,EACF,MAIvB;AACJ,UAAM,WAAW,OAAO;AACxB,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,cAAc;AAE7C,UAAMF,aAAY,OAAO,aAAa;AACtC,UAAM,UAAU,OAAO;AAEvB,UAAM,OAAO,aAAa;AAE1B,QAAI,CAAC,WAAW,eAAe,OAAO,uBAAuB,CAAC,eAAeE,QAAO,GAAG;AACrF,YAAM,SAAS,EAAE,UAAU,YAAY,SAAAA,SAAQ;AAC/C,sBAAgB,EAAE,QAAQ,WAAW,MAAM,IAAI,QAAQ,YAAY,MAAM,CAAC;AAE1E,YAAM,MAAM,IAAI,IAAI,OAAO,WAAW,cAAc,OAAO,SAAS,OAAO,EAAE;AAC7E,UAAI,aAAa,IAAI,SAAS,IAAI;AAGlC,YAAM,iBAAiB,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACjE,qBAAe,QAAQ,CAAC,OAAO,QAAQ;AACrC,YAAI,CAAC,IAAI,aAAa,IAAI,GAAG,GAAG;AAC9B,cAAI,aAAa,IAAI,KAAK,KAAK;AAAA,QACjC;AAAA,MACF,CAAC;AAMD,UAAI,aAAa,OAAO,WAAW;AACnC,UAAI,aAAa,OAAO,cAAc;AAEtC,UAAI;AACF,cAAM,SAAyB,MAAM,SAAS,iBAAiB;AAAA,UAC7D,cAAc,CAAC,MAAM;AAAA,UACrB,aAAa,IAAI,SAAS;AAAA,QAC5B,CAAC;AAED,YAAI,OAAO,KAAK;AACd,cAAI,OAAO,WAAW,aAAa;AACjC,uBAAW,MAAM;AACf,qBAAO,SAAS,OAAO,OAAO;AAAA,YAChC,GAAG,GAAG;AAAA,UACR;AAAA,QACF,WAAW,OAAO,UAAU,QAAQ;AAClC,iBAAO,SAAS;AAAA,YAAQ,CAAC,MACvB,gBAAgB;AAAA,cACd;AAAA,cACA,QAAQ;AAAA,cACR,QAAQ;AAAA,cACR,QAAQ,EAAE,YAAY;AAAA,cACtB,YAAY;AAAA,YACd,CAAC;AAAA,UACH;AAAA,QACF,WAAW,OAAO,UAAU;AAC1B,0BAAgB,EAAE,MAAM,QAAQ,kBAAkB,YAAY,KAAK,CAAC;AAAA,QACtE,WAAW,OAAO,OAAO;AACvB,0BAAgB;AAAA,YACd;AAAA,YACA,QAAQ;AAAA,YACR,OAAO,aAAa,OAAO,KAAK;AAAA,YAChC,YAAY;AAAA,UACd,CAAC;AAAA,QACH;AAEA,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,gBAAQ,MAAM,6CAA6C,GAAG;AAC9D,wBAAgB;AAAA,UACd;AAAA,UACA,QAAQ;AAAA,UACR,OAAO,aAAc,IAAc,OAAO;AAAA,UAC1C,YAAY;AAAA,QACd,CAAC;AAED,eAAO,QAAQ,OAAO,GAAG;AAAA,MAC3B;AAAA,IACF;AAEA,QAAI,QAAQ,MAAM,OAAO;AACzB,QAAI,SAAS,MAAM;AACjB,YAAM,YAAY,MAAM,eAAe,EAAE,WAAW,UAAU,WAAWF,WAAU,CAAC;AACpF,UAAI,UAAU,OAAO,OAAO;AAC1B,cAAM,IAAI,MAAM,qBAAqB,UAAU,OAAO,KAAK,qCAAqC,QAAQ,mBAAmBA,UAAS,EAAE;AAAA,MACxI;AACA,cAAQ,UAAU,OAAO;AACzB,YAAM,SAAS,KAAK;AAAA,IACtB;AAEA,QAAI,iBAAiB,MAAM,OAAO;AAClC,QACE,CAAC,kBACD,WAAW,eAAe,OAAO,iBAAiB,IAAI,MAAM,kBAAkB,KAAK,IAAI,GACvF;AACA,YAAM,cAAc,MAAM,WAAW,EAAE,SAAS,QAAQ,CAAC;AACzD,uBAAiB;AAAA,QACf,QAAQ;AAAA,UACN,MAAM,YAAY,OAAO,OAAO;AAAA,UAChC,mBAAmB,YAAY,OAAO,OAAO;AAAA,QAC/C;AAAA,MACF;AACA,YAAM,SAAS,cAAc;AAAA,IAC/B;AAEA,aAAS;AACT,UAAM,SAAS,KAAK;AAEpB,UAAM,YAAY,eAAe,OAAO;AAExC,UAAM,sBAAwC;AAAA,MAC5C;AAAA,MACA,WAAAA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAAE;AAAA,IACF;AAEA,UAAM,UAAU,qBAAqB,mBAAmB;AACxD,UAAM,cAAc,OAAO,OAAO;AAClC,UAAM,WAAW,yBAAS,WAAW;AAErC,UAAM,kBAAkB,SAAS,aAAa,SAAS,EAAE,cAAc,KAAK,CAAC;AAC7E,UAAM,yBAAyB,2BAA2B,qBAAqB,eAAe;AAC9F,UAAM,iBAAiB,cAAc,sBAAsB;AAE3D,oBAAgB;AAAA,MACd,QAAQ;AAAA,MACR;AAAA,MACA,IAAI;AAAA,MACJ,WAAW;AAAA,MACX;AAAA,MACA,QAAQ;AAAA,MACR,YAAY;AAAA,IACd,CAAC;AAED,QAAI;AACF,aAAO,MAAM,YAAY,gBAAgB,WAAW,IAAI;AAAA,IAC1D,SAAS,OAAO;AACd,cAAQ,MAAM,8BAA8B,OAAO,qBAAqB,cAAc;AAAA,IACxF;AAAA,EACF,GApJsB;AAuJf,MAAMC,OAAM;AAAA,IACjB,OAAO,CAAC;AAAA;AAAA,IACR,OAAwB,IAAI;AAAA,IAC5B,aAA8B,IAAI,YAAY,eAAe;AAAA,EAC/D;AAEA,aAAW,OAAOC,cAAkB;AAClC,IAAAD,KAAI,MAAM,GAAG,IAAIC,aAAiB,GAAG;AAAA,EACvC;AAGO,MAAM,QAAQD,KAAI;AAElB,MAAM,QAAQ,CAAC;AAEtB,aAAW,OAAO,eAAc;AAC9B,UAAM,GAAG,IAAI,cAAa,GAAG;AAAA,EAC/B;AAIO,MAAM,QAAQ,MAAM,QAAQ;AACnC,SAAO,MAAM,QAAQ;AAGrB,MAAI;AACF,QAAI,OAAO,WAAW,aAAa;AACjC,YAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,YAAM,QAAQ,IAAI,aAAa,IAAI,YAAY;AAC/C,YAAM,SAAS,IAAI,aAAa,IAAI,YAAY;AAChD,YAAM,UAAU,IAAI,aAAa,IAAI,WAAW;AAChD,YAAM,SAAS,IAAI,aAAa,IAAI,cAAc;AAClD,YAAM,gBAAgB,SAAS,mBAAmB,MAAM,IAAI;AAE5D,YAAM,WAAW,IAAI,aAAa,IAAI,mBAAmB;AACzD,YAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;AAE1C,UAAI,WAAW,QAAQ;AACrB,gBAAQ,KAAK,IAAI,MAAM;AAAA,QAAyB,OAAO;AAAA,WAAc,aAAa,EAAE,CAAC;AAAA,MACvF;AAEA,UAAI,SAAS,QAAQ;AACnB,YAAI,WAAW,OAAO,WAAW;AAC/B,iBAAO,EAAE,WAAW,MAAM,CAAC;AAAA,QAC7B,OAAO;AAGL,cAAI,WAAW,MAAM,YAAY;AAC/B,oBAAQ,KAAK,4CAA4C,QAAQ,OAAO,SAAS;AAAA,UACnF;AACA,cAAI,aAAa,OAAO,YAAY;AAAA,QACtC;AAAA,MACF;AAEA,UAAI,YAAY,OAAO;AACrB,cAAM,UAAU,WAAW,SAAS,MAAM,GAAG,IAAI,CAAC;AAClD,cAAM,QAAQ,QAAQ,MAAM,MAAM,GAAG,IAAI,CAAC;AAC1C,YAAI,MAAM,SAAS,QAAQ,QAAQ;AACjC,gBAAM,QAAQ,CAAC,OAAO;AACpB,4BAAgB,EAAE,MAAM,IAAI,QAAQ,kBAAkB,YAAY,KAAK,CAAC;AAAA,UAC1E,CAAC;AAAA,QACH,WAAW,MAAM,WAAW,QAAQ,QAAQ;AAC1C,gBAAM,QAAQ,CAAC,IAAI,MAAM;AACvB,4BAAgB;AAAA,cACd,MAAM;AAAA,cACN,QAAQ;AAAA,cACR,QAAQ,QAAQ,CAAC;AAAA,cACjB,YAAY;AAAA,YACd,CAAC;AACD,wBAAY,EAAE;AAAA,UAChB,CAAC;AAAA,QACH,OAAO;AACL,kBAAQ,MAAM,IAAI,MAAM,gDAAgD,GAAG,OAAO,OAAO;AAAA,QAC3F;AAAA,MACF;AAWA,UAAI,aAAa,OAAO,OAAO;AAC/B,UAAI,WAAW,MAAM,aAAa;AAChC,YAAI,aAAa,OAAO,WAAW;AACnC,YAAI,aAAa,OAAO,cAAc;AAAA,MACxC;AAAA,IAUF;AAAA,EACF,SAAS,GAAG;AACV,YAAQ,MAAM,mCAAmC,CAAC;AAAA,EACpD;AAGO,MAAM,UAAU;AAAA,IACrB,cAAc,wBAAC;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,OAMR;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAnBc;AAAA,IAqBd,UAAU,wBAAC,iBAAyB;AAAA,MAClC,MAAM;AAAA,MACN,SAAS;AAAA,IACX,IAHU;AAAA,IAKV,WAAW,wBAAC,EAAC,QAAQ,WAAAH,WAAS,OAA8C;AAAA,MAC1E,MAAM;AAAA,MACN,OAAO;AAAA,MACP,WAAAA;AAAA,IACF,IAJW;AAAA,IAMX,kBAAkB,wBAAC,EAAC,WAAAA,WAAS,OAA8B;AAAA,MACzD,MAAM;AAAA,MACN,WAAWA;AAAA,MACX,WAAW,EAAC,YAAY,aAAY;AAAA,IACtC,IAJkB;AAAA,IAMlB,qBAAqB,wBAAC;AAAA,MACE,WAAAA;AAAA,MACA;AAAA,MACA,WAAAC;AAAA,MACA;AAAA,IACF,OAKf;AAAA,MACL,MAAM;AAAA,MACN,WAAWD;AAAA,MACX,WAAW;AAAA,QACT,YAAY;AAAA,QACZ;AAAA,QACA,YAAYC;AAAA,QACZ;AAAA,MACF;AAAA,IACF,IAnBqB;AAAA,IAqBrB,WAAW,wBAAC,EAAC,WAAAD,WAAS,OAA8B;AAAA,MAClD,MAAM;AAAA,MACN,WAAAA;AAAA,IACF,IAHW;AAAA,IAKX,eAAe,wBAAC,EAAC,cAAa,OAAkC;AAAA,MAC9D,MAAM;AAAA,MACN;AAAA,IACF,IAHe;AAAA,IAKf,eAAe,8BAAO;AAAA,MACpB,MAAM;AAAA,IACR,IAFe;AAAA,IAIf,gBAAgB,wBAAC,EAAC,WAAU,OAA+B;AAAA,MACzD,MAAM;AAAA,MACN;AAAA,IACF,IAHgB;AAAA,EAIlB;","names":["src_exports","exp","Big","src_exports","crypto","crypto","view","_32n","state","view","isBytes","abytes","abytes","isBytes","abytes","isBytes","_0n","_1n","_2n","P","Fp","bitLen","_0n","_1n","_0n","_1n","P","window","wbits","_0n","_1n","_2n","_8n","Fp","randomBytes","uvRatio","adjustScalarBytes","G","A","B","F","C","D","E","H","X3","Y3","T3","Z3","cofactor","publicKey","utils","_0n","_1n","_2n","_3n","_5n","_8n","P","pow","_8n","view","e","actions","publicKey","EncodeBuffer","DecodeBuffer","d","b","ErrorSchema","BorshSerializer","BorshDeserializer","event","state","config","state","accountId","config","publicKey","accountId","actions","exp","src_exports"]} \ No newline at end of file diff --git a/static/ru/agents.md b/static/ru/agents.md index 728f740..41f6435 100644 --- a/static/ru/agents.md +++ b/static/ru/agents.md @@ -17,6 +17,7 @@ - Используйте индексированные API, когда пользователю нужен ответ в продуктовой форме — балансы, активы, история аккаунта или история переводов. - Используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), когда пользователю нужны канонические поля на уровне протокола, вызовы контрактов или отправка транзакций. +- Если вы используете размещённый JS-рантайм на [js.fastnear.com](https://js.fastnear.com), начинайте с низкоуровневых методов вроде `near.view`, `near.queryAccount` и `near.tx.*`, а к `near.recipes.*` обращайтесь только тогда, когда task helper действительно является самым коротким путём к ответу. - Используйте [NEAR Data API](https://docs.fastnear.com/ru/neardata), когда вопрос касается свежих оптимистичных или финализированных блоков и явного опроса. - Используйте [Снапшоты](https://docs.fastnear.com/ru/snapshots) для операторских сценариев, а не для чтения прикладных данных. - Один API-ключ FastNear работает и для RPC, и для API-эндпоинтов. @@ -90,20 +91,24 @@ ## Аутентифицируйтесь один раз, используйте везде -Публичные эндпоинты часто работают и без ключа. Добавьте ключ, если нужны повышенные лимиты, единая аутентифицированная модель или платные сценарии. Один и тот же ключ работает со всеми API FastNear выше, включая обычные и архивные RPC-хосты; передавайте его либо в HTTP-заголовке, либо в URL-параметре: +Начните с API-ключа FastNear и используйте его во всех API FastNear выше, включая обычные и архивные RPC-хосты. Передавайте его либо в HTTP-заголовке, либо в URL-параметре: ```bash title="Заголовок Authorization" +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" + curl "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer ${API_KEY}" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` ```bash title="URL-параметр" -curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" + +curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" ``` -Получить ключ: [dashboard.fastnear.com](https://dashboard.fastnear.com). Операционный режим для неинтерактивных сред: [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) — ключи должны жить в переменных окружения или менеджере секретов, а не в браузерном хранилище, логах чатов или промптах. Полный сценарий и детали заголовков: [Аутентификация и доступ](https://docs.fastnear.com/ru/auth). +Получите API-ключ в [FastNear Dashboard](https://dashboard.fastnear.com). Операционный режим для неинтерактивных сред: [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) — ключи должны жить в переменных окружения или менеджере секретов, а не в браузерном хранилище, логах чатов или промптах. Полный сценарий и детали заголовков: [Аутентификация и доступ](https://docs.fastnear.com/ru/auth). ## Как вынимать чистую документацию в промпт @@ -142,5 +147,5 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/auth.md b/static/ru/agents/auth.md index f0ff966..073ed0b 100644 --- a/static/ru/agents/auth.md +++ b/static/ru/agents/auth.md @@ -4,7 +4,7 @@ Агенты должны аутентифицироваться в FastNear так же, как это делают продовые бэкенды. Не переносите браузерно-демонстрационный режим из UI документации в агента, воркера или среду автоматизации. -Один API-ключ FastNear работает во всех RPC и API-эндпоинтах. Многие публичные чтения работают и без ключа. Для агента важнее не само наличие аутентификации, а где живёт учётная запись, как она прикрепляется к запросам и как не допустить её утечки в промпты, логи или состояние браузера. +Один API-ключ FastNear работает во всех RPC и API-эндпоинтах. Для агента важнее не само наличие аутентификации, а где живёт учётная запись, как она прикрепляется к запросам и как не допустить её утечки в промпты, логи или состояние браузера. ## Если нужно только правило @@ -28,8 +28,8 @@ | Способ | Используйте, когда... | Заметки | | --- | --- | --- | -| `Authorization: Bearer ${API_KEY}` | HTTP-клиентом или бэкендом управляете вы | Лучшее значение по умолчанию для агентов. Меньше шансов утечь в логи URL, аналитику или скопированные ссылки. | -| `?apiKey=${API_KEY}` | используется простой curl или система, которой сложно выставлять заголовки | Тоже допустимо, но URL обычно дальше путешествуют через логи и инструменты. Применяйте осознанно. | +| `Authorization: Bearer ${FASTNEAR_API_KEY}` | HTTP-клиентом или бэкендом управляете вы | Лучшее значение по умолчанию для агентов. Меньше шансов утечь в логи URL, аналитику или скопированные ссылки. | +| `?apiKey=${FASTNEAR_API_KEY}` | используется простой curl или система, которой сложно выставлять заголовки | Тоже допустимо, но URL обычно дальше путешествуют через логи и инструменты. Применяйте осознанно. | Если есть выбор — используйте заголовочную форму. @@ -60,13 +60,13 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { }); ``` -## Когда аутентификации не хватает +## Если в среде выполнения нет ключа -Многие публичные эндпоинты FastNear по-прежнему доступны на чтение без ключа. Если агент может ответить на вопрос пользователя через публичный трафик — делайте так. +Агенту по умолчанию стоит стартовать с настроенным API-ключом FastNear. Некоторые публичные чтения могут сработать и без него, но это не должно быть базовой рабочей моделью. -Когда ключ нужен для повышенных лимитов, платного доступа или аутентифицированного трафика: +Если в настроенной среде ключа пока нет: -- подскажите пользователю создать или забрать ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com) +- подскажите пользователю создать или забрать ключ в [FastNear Dashboard](https://dashboard.fastnear.com) - попросите настроить его в переменной окружения, менеджере секретов или конфигурации бэкенда - не просите вставлять сырой ключ в чат, чтобы агент «носил» его с собой @@ -98,5 +98,5 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/auth/index.md b/static/ru/agents/auth/index.md index f0ff966..073ed0b 100644 --- a/static/ru/agents/auth/index.md +++ b/static/ru/agents/auth/index.md @@ -4,7 +4,7 @@ Агенты должны аутентифицироваться в FastNear так же, как это делают продовые бэкенды. Не переносите браузерно-демонстрационный режим из UI документации в агента, воркера или среду автоматизации. -Один API-ключ FastNear работает во всех RPC и API-эндпоинтах. Многие публичные чтения работают и без ключа. Для агента важнее не само наличие аутентификации, а где живёт учётная запись, как она прикрепляется к запросам и как не допустить её утечки в промпты, логи или состояние браузера. +Один API-ключ FastNear работает во всех RPC и API-эндпоинтах. Для агента важнее не само наличие аутентификации, а где живёт учётная запись, как она прикрепляется к запросам и как не допустить её утечки в промпты, логи или состояние браузера. ## Если нужно только правило @@ -28,8 +28,8 @@ | Способ | Используйте, когда... | Заметки | | --- | --- | --- | -| `Authorization: Bearer ${API_KEY}` | HTTP-клиентом или бэкендом управляете вы | Лучшее значение по умолчанию для агентов. Меньше шансов утечь в логи URL, аналитику или скопированные ссылки. | -| `?apiKey=${API_KEY}` | используется простой curl или система, которой сложно выставлять заголовки | Тоже допустимо, но URL обычно дальше путешествуют через логи и инструменты. Применяйте осознанно. | +| `Authorization: Bearer ${FASTNEAR_API_KEY}` | HTTP-клиентом или бэкендом управляете вы | Лучшее значение по умолчанию для агентов. Меньше шансов утечь в логи URL, аналитику или скопированные ссылки. | +| `?apiKey=${FASTNEAR_API_KEY}` | используется простой curl или система, которой сложно выставлять заголовки | Тоже допустимо, но URL обычно дальше путешествуют через логи и инструменты. Применяйте осознанно. | Если есть выбор — используйте заголовочную форму. @@ -60,13 +60,13 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { }); ``` -## Когда аутентификации не хватает +## Если в среде выполнения нет ключа -Многие публичные эндпоинты FastNear по-прежнему доступны на чтение без ключа. Если агент может ответить на вопрос пользователя через публичный трафик — делайте так. +Агенту по умолчанию стоит стартовать с настроенным API-ключом FastNear. Некоторые публичные чтения могут сработать и без него, но это не должно быть базовой рабочей моделью. -Когда ключ нужен для повышенных лимитов, платного доступа или аутентифицированного трафика: +Если в настроенной среде ключа пока нет: -- подскажите пользователю создать или забрать ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com) +- подскажите пользователю создать или забрать ключ в [FastNear Dashboard](https://dashboard.fastnear.com) - попросите настроить его в переменной окружения, менеджере секретов или конфигурации бэкенда - не просите вставлять сырой ключ в чат, чтобы агент «носил» его с собой @@ -98,5 +98,5 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/choosing-surfaces.md b/static/ru/agents/choosing-surfaces.md index 3d3f1e8..0e21fa4 100644 --- a/static/ru/agents/choosing-surfaces.md +++ b/static/ru/agents/choosing-surfaces.md @@ -260,5 +260,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/choosing-surfaces/index.md b/static/ru/agents/choosing-surfaces/index.md index 3d3f1e8..0e21fa4 100644 --- a/static/ru/agents/choosing-surfaces/index.md +++ b/static/ru/agents/choosing-surfaces/index.md @@ -260,5 +260,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/index.md b/static/ru/agents/index.md index 728f740..41f6435 100644 --- a/static/ru/agents/index.md +++ b/static/ru/agents/index.md @@ -17,6 +17,7 @@ - Используйте индексированные API, когда пользователю нужен ответ в продуктовой форме — балансы, активы, история аккаунта или история переводов. - Используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), когда пользователю нужны канонические поля на уровне протокола, вызовы контрактов или отправка транзакций. +- Если вы используете размещённый JS-рантайм на [js.fastnear.com](https://js.fastnear.com), начинайте с низкоуровневых методов вроде `near.view`, `near.queryAccount` и `near.tx.*`, а к `near.recipes.*` обращайтесь только тогда, когда task helper действительно является самым коротким путём к ответу. - Используйте [NEAR Data API](https://docs.fastnear.com/ru/neardata), когда вопрос касается свежих оптимистичных или финализированных блоков и явного опроса. - Используйте [Снапшоты](https://docs.fastnear.com/ru/snapshots) для операторских сценариев, а не для чтения прикладных данных. - Один API-ключ FastNear работает и для RPC, и для API-эндпоинтов. @@ -90,20 +91,24 @@ ## Аутентифицируйтесь один раз, используйте везде -Публичные эндпоинты часто работают и без ключа. Добавьте ключ, если нужны повышенные лимиты, единая аутентифицированная модель или платные сценарии. Один и тот же ключ работает со всеми API FastNear выше, включая обычные и архивные RPC-хосты; передавайте его либо в HTTP-заголовке, либо в URL-параметре: +Начните с API-ключа FastNear и используйте его во всех API FastNear выше, включая обычные и архивные RPC-хосты. Передавайте его либо в HTTP-заголовке, либо в URL-параметре: ```bash title="Заголовок Authorization" +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" + curl "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer ${API_KEY}" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` ```bash title="URL-параметр" -curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" + +curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" ``` -Получить ключ: [dashboard.fastnear.com](https://dashboard.fastnear.com). Операционный режим для неинтерактивных сред: [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) — ключи должны жить в переменных окружения или менеджере секретов, а не в браузерном хранилище, логах чатов или промптах. Полный сценарий и детали заголовков: [Аутентификация и доступ](https://docs.fastnear.com/ru/auth). +Получите API-ключ в [FastNear Dashboard](https://dashboard.fastnear.com). Операционный режим для неинтерактивных сред: [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) — ключи должны жить в переменных окружения или менеджере секретов, а не в браузерном хранилище, логах чатов или промптах. Полный сценарий и детали заголовков: [Аутентификация и доступ](https://docs.fastnear.com/ru/auth). ## Как вынимать чистую документацию в промпт @@ -142,5 +147,5 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/playbooks.md b/static/ru/agents/playbooks.md index dda9124..a6a1821 100644 --- a/static/ru/agents/playbooks.md +++ b/static/ru/agents/playbooks.md @@ -265,5 +265,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/agents/playbooks/index.md b/static/ru/agents/playbooks/index.md index dda9124..a6a1821 100644 --- a/static/ru/agents/playbooks/index.md +++ b/static/ru/agents/playbooks/index.md @@ -265,5 +265,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api.md b/static/ru/api.md index db1de67..8d937ae 100644 --- a/static/ru/api.md +++ b/static/ru/api.md @@ -68,5 +68,5 @@ https://test.api.fastnear.com - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/examples.md b/static/ru/api/examples.md index 5b947ac..9d35825 100644 --- a/static/ru/api/examples.md +++ b/static/ru/api/examples.md @@ -2,15 +2,19 @@ ## Примеры +Все shell-примеры ниже работают на публичных FastNear API-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ### Свести один аккаунт за один вызов `/v1/account/{id}/full` — это агрегатор аккаунтов в FastNear API: один вызов собирает NEAR-состояние аккаунта, каждый FT-контракт, которого он касался, каждую NFT-коллекцию, которую он получил, и каждый валидаторский пул, в который делегировал. Если у вас уже есть `account_id`, это самый быстрый ответ на вопрос «что это за аккаунт?». ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + "${AUTH_HEADER[@]}" \ | jq '{ account_id, near_balance_yocto: .state.balance, @@ -20,23 +24,26 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -Для `mike.near`: 40 FT-контрактов в списке, 40 NFT-коллекций, 5 валидаторских пулов. Одни только счётчики контрактов говорят, что это активный mainnet-аккаунт. Все примеры ниже погружаются в какую-то одну из этих поверхностей — начинайте отсюда, когда на руках только ID аккаунта. +Для `root.near`: 150 FT-контрактов в списке, 102 NFT-коллекции, 2 валидаторских пула. Одни только счётчики контрактов говорят, что это оживлённый mainnet-аккаунт. Все примеры ниже погружаются в какую-то одну из этих поверхностей — начинайте отсюда, когда на руках только ID аккаунта. ### Определить аккаунт по публичному ключу и сразу получить сводку Найдите, какому аккаунту принадлежит ключ, и прочитайте его активы за один следующий запрос. ```bash -API_BASE_URL=https://api.fastnear.com PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -LOOKUP="$(curl -s "$API_BASE_URL/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')")" +LOOKUP="$(curl -s "https://api.fastnear.com/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')" \ + "${AUTH_HEADER[@]}")" echo "$LOOKUP" | jq '{matched: (.account_ids | length), account_ids}' ACCOUNT_ID="$(echo "$LOOKUP" | jq -r '.account_ids[0]')" -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + "${AUTH_HEADER[@]}" \ | jq '{account_id, state, tokens: (.tokens|length), nfts: (.nfts|length), pools: (.pools|length)}' ``` @@ -47,10 +54,12 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Состояние аккаунта NEAR делится на три ведра, которые UI кошельков обычно сливает в одно: `balance` — это свободная часть (не в стейкинге), `locked` — NEAR, привязанный к валидаторскому стейку или lockup-контракту, а `storage_bytes` подразумевает ещё отдельную долю, пришпиленную к trie по текущей ставке 10^19 yoctoNEAR за байт. Один pipeline над `/full` разводит их по полкам. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + "${AUTH_HEADER[@]}" \ | jq ' (.state.balance | tonumber) as $amount | (.state.locked | tonumber) as $locked @@ -68,7 +77,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -Для `mike.near`: ~2613.49 NEAR всего, всё в свободной части, ~5.58 NEAR закреплено за 558 КБ on-chain-состояния, ~2607.91 NEAR доступно к переводу. Новым аккаунтам это особенно заметно — свежесозданный именованный аккаунт ~182 байта «съедает» ~0.00182 NEAR под storage, и именно поэтому CLI-утилиты не дают отправить полный баланс. +Для `root.near`: ~3914.67 NEAR всего, всё в свободной части, ~0.28677 NEAR закреплено за 28,677 байтами on-chain-состояния, ~3914.38 NEAR доступно к переводу. Новым аккаунтам это особенно заметно — свежесозданный именованный аккаунт ~182 байта «съедает» ~0.00182 NEAR под storage, и именно поэтому CLI-утилиты не дают отправить полный баланс. Наведите тот же pipeline на валидаторский пул вроде `astro-stakers.poolv1.near`, и пропорции перевернутся: ~730 тыс. свободных, ~27.68 млн в `locked`. Этот `locked` — собственный протокольный валидаторский стейк пула, а не средства делегатов (те учитываются внутри состояния контракта пула). Одно и то же поле означает разное на разных типах аккаунтов. @@ -79,10 +88,12 @@ jq считает в IEEE-754 double, поэтому NEAR-значения вы У каждой записи в массивах `tokens`, `nfts` и `pools` внутри `/full` есть собственное `last_update_block_height` — блок, в котором индексер последний раз видел изменение этой строки для этого аккаунта. Максимум по всем трём массивам даёт дешёвый сигнал «последняя активность» без похода в Transactions API. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + "${AUTH_HEADER[@]}" \ | jq ' [ (.tokens // [])[].last_update_block_height, @@ -99,20 +110,22 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -Для `mike.near` относительно текущего tip это возвращает 85 записей по FT-, NFT- и pool-контрактам, 34 с отслеживаемым блоком и самый свежий блок `194711866` — примерно 125 тыс. блоков назад, или около 35 часов при темпе NEAR ~1 блок/сек. Для `root.near`: 254 записи, 158 отслеживаемых. +Для `root.near` это возвращает 254 записи по FT-, NFT- и pool-контрактам, 158 с отслеживаемым блоком и самый свежий блок `194301659`. Этого уже достаточно, чтобы понять, что кошелёк живой, не заходя в историю транзакций. -Это правильный вопрос для «заброшен ли этот кошелёк?» или «двигалось ли что-то после блока X?» — дёшево, один запрос, без истории транзакций. Чтобы достать саму транзакцию, вызвавшую последнее изменение, расширяйте поверхность до [Transactions API](https://docs.fastnear.com/ru/tx). Записи с `last_update_block_height: null` относятся ко времени до per-row-отслеживания индексером (обычно старые airdrops) и здесь игнорируются, а не считаются свежими. +Это правильный вопрос для «был ли этот кошелёк недавно активен?» или «двигалось ли что-то после блока X?» — дёшево, один запрос, без истории транзакций. Чтобы достать саму транзакцию, вызвавшую последнее изменение, расширяйте поверхность до [Transactions API](https://docs.fastnear.com/ru/tx). Записи с `last_update_block_height: null` относятся ко времени до per-row-отслеживания индексером (обычно старые airdrops) и здесь игнорируются, а не считаются свежими. ### Показать NFT-коллекции этого кошелька от конкретного издателя Имена аккаунтов на NEAR кодируют иерархию: `mint.sharddog.near` — это подаккаунт `sharddog.near`, который, в свою очередь, — подаккаунт `near`. Издатели, выпускающие несколько NFT-коллекций, обычно разворачивают каждую как отдельный подаккаунт, поэтому один фильтр по суффиксу над NFT-списком аккаунта вытаскивает всё опубликованное под одним деревом — без внешнего реестра коллекций. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near PUBLISHER=sharddog.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ + "${AUTH_HEADER[@]}" \ | jq --arg publisher "$PUBLISHER" ' ("." + $publisher) as $suffix | { @@ -130,7 +143,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ }' ``` -Для `mike.near` и `sharddog.near` это возвращает четыре контракта-подаккаунта: `comic`, `mintv2`, `mint` и `claim`. Два с ненулевым `last_update_block_height` (`mint` на `115715361` и `claim` на `119718026`) — те, где позиция кошелька действительно менялась. Два других — спящие, типично для одноразовых drop-контрактов, в которые аккаунт что-то получил и больше не возвращался. +Для `root.near` и `sharddog.near` это возвращает четыре контракта-подаккаунта: `ndcconstellationnft`, `mint`, `harvestmoon` и `claim`. Только у `claim` есть ненулевой `last_update_block_height` (`131402024`), так что именно этот контракт явно менял позицию кошелька. Остальные — спящие, что типично для одноразовых drop-контрактов, в которые аккаунт что-то получил и больше не возвращался. Поменяйте `PUBLISHER` на любой аккаунт, чтобы сфокусировать фильтр на другом дереве издателя. @@ -139,12 +152,15 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ Прямые позиции в пулах лежат на `/staking`; liquid staking-токены (stNEAR, LiNEAR и т. п.) лежат на `/ft` как обычные FT. Прочитайте оба эндпоинта и классифицируйте кошелёк — `root.near` оказывается `mixed`. ```bash -API_BASE_URL=https://api.fastnear.com ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -STAKING="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking")" -FT="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft")" +STAKING="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/staking" \ + "${AUTH_HEADER[@]}")" +FT="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/ft" \ + "${AUTH_HEADER[@]}")" jq -n \ --argjson staking "$STAKING" \ @@ -153,11 +169,12 @@ jq -n \ ($staking.pools // []) as $direct | (($ft.tokens // []) | map(select(.contract_id as $id | $providers | index($id)))) as $liquid | { - classification: + classification: ( if ($direct|length)>0 and ($liquid|length)>0 then "mixed" elif ($direct|length)>0 then "direct_only" elif ($liquid|length)>0 then "liquid_only" - else "no_visible_staking_position" end, + else "no_visible_staking_position" end + ), direct_pools: ($direct | map(.pool_id)), liquid_tokens: ($liquid | map({contract_id, balance})) }' @@ -184,5 +201,5 @@ jq -n \ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/examples/index.md b/static/ru/api/examples/index.md index 5b947ac..9d35825 100644 --- a/static/ru/api/examples/index.md +++ b/static/ru/api/examples/index.md @@ -2,15 +2,19 @@ ## Примеры +Все shell-примеры ниже работают на публичных FastNear API-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ### Свести один аккаунт за один вызов `/v1/account/{id}/full` — это агрегатор аккаунтов в FastNear API: один вызов собирает NEAR-состояние аккаунта, каждый FT-контракт, которого он касался, каждую NFT-коллекцию, которую он получил, и каждый валидаторский пул, в который делегировал. Если у вас уже есть `account_id`, это самый быстрый ответ на вопрос «что это за аккаунт?». ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + "${AUTH_HEADER[@]}" \ | jq '{ account_id, near_balance_yocto: .state.balance, @@ -20,23 +24,26 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -Для `mike.near`: 40 FT-контрактов в списке, 40 NFT-коллекций, 5 валидаторских пулов. Одни только счётчики контрактов говорят, что это активный mainnet-аккаунт. Все примеры ниже погружаются в какую-то одну из этих поверхностей — начинайте отсюда, когда на руках только ID аккаунта. +Для `root.near`: 150 FT-контрактов в списке, 102 NFT-коллекции, 2 валидаторских пула. Одни только счётчики контрактов говорят, что это оживлённый mainnet-аккаунт. Все примеры ниже погружаются в какую-то одну из этих поверхностей — начинайте отсюда, когда на руках только ID аккаунта. ### Определить аккаунт по публичному ключу и сразу получить сводку Найдите, какому аккаунту принадлежит ключ, и прочитайте его активы за один следующий запрос. ```bash -API_BASE_URL=https://api.fastnear.com PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -LOOKUP="$(curl -s "$API_BASE_URL/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')")" +LOOKUP="$(curl -s "https://api.fastnear.com/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')" \ + "${AUTH_HEADER[@]}")" echo "$LOOKUP" | jq '{matched: (.account_ids | length), account_ids}' ACCOUNT_ID="$(echo "$LOOKUP" | jq -r '.account_ids[0]')" -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + "${AUTH_HEADER[@]}" \ | jq '{account_id, state, tokens: (.tokens|length), nfts: (.nfts|length), pools: (.pools|length)}' ``` @@ -47,10 +54,12 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Состояние аккаунта NEAR делится на три ведра, которые UI кошельков обычно сливает в одно: `balance` — это свободная часть (не в стейкинге), `locked` — NEAR, привязанный к валидаторскому стейку или lockup-контракту, а `storage_bytes` подразумевает ещё отдельную долю, пришпиленную к trie по текущей ставке 10^19 yoctoNEAR за байт. Один pipeline над `/full` разводит их по полкам. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + "${AUTH_HEADER[@]}" \ | jq ' (.state.balance | tonumber) as $amount | (.state.locked | tonumber) as $locked @@ -68,7 +77,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -Для `mike.near`: ~2613.49 NEAR всего, всё в свободной части, ~5.58 NEAR закреплено за 558 КБ on-chain-состояния, ~2607.91 NEAR доступно к переводу. Новым аккаунтам это особенно заметно — свежесозданный именованный аккаунт ~182 байта «съедает» ~0.00182 NEAR под storage, и именно поэтому CLI-утилиты не дают отправить полный баланс. +Для `root.near`: ~3914.67 NEAR всего, всё в свободной части, ~0.28677 NEAR закреплено за 28,677 байтами on-chain-состояния, ~3914.38 NEAR доступно к переводу. Новым аккаунтам это особенно заметно — свежесозданный именованный аккаунт ~182 байта «съедает» ~0.00182 NEAR под storage, и именно поэтому CLI-утилиты не дают отправить полный баланс. Наведите тот же pipeline на валидаторский пул вроде `astro-stakers.poolv1.near`, и пропорции перевернутся: ~730 тыс. свободных, ~27.68 млн в `locked`. Этот `locked` — собственный протокольный валидаторский стейк пула, а не средства делегатов (те учитываются внутри состояния контракта пула). Одно и то же поле означает разное на разных типах аккаунтов. @@ -79,10 +88,12 @@ jq считает в IEEE-754 double, поэтому NEAR-значения вы У каждой записи в массивах `tokens`, `nfts` и `pools` внутри `/full` есть собственное `last_update_block_height` — блок, в котором индексер последний раз видел изменение этой строки для этого аккаунта. Максимум по всем трём массивам даёт дешёвый сигнал «последняя активность» без похода в Transactions API. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + "${AUTH_HEADER[@]}" \ | jq ' [ (.tokens // [])[].last_update_block_height, @@ -99,20 +110,22 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -Для `mike.near` относительно текущего tip это возвращает 85 записей по FT-, NFT- и pool-контрактам, 34 с отслеживаемым блоком и самый свежий блок `194711866` — примерно 125 тыс. блоков назад, или около 35 часов при темпе NEAR ~1 блок/сек. Для `root.near`: 254 записи, 158 отслеживаемых. +Для `root.near` это возвращает 254 записи по FT-, NFT- и pool-контрактам, 158 с отслеживаемым блоком и самый свежий блок `194301659`. Этого уже достаточно, чтобы понять, что кошелёк живой, не заходя в историю транзакций. -Это правильный вопрос для «заброшен ли этот кошелёк?» или «двигалось ли что-то после блока X?» — дёшево, один запрос, без истории транзакций. Чтобы достать саму транзакцию, вызвавшую последнее изменение, расширяйте поверхность до [Transactions API](https://docs.fastnear.com/ru/tx). Записи с `last_update_block_height: null` относятся ко времени до per-row-отслеживания индексером (обычно старые airdrops) и здесь игнорируются, а не считаются свежими. +Это правильный вопрос для «был ли этот кошелёк недавно активен?» или «двигалось ли что-то после блока X?» — дёшево, один запрос, без истории транзакций. Чтобы достать саму транзакцию, вызвавшую последнее изменение, расширяйте поверхность до [Transactions API](https://docs.fastnear.com/ru/tx). Записи с `last_update_block_height: null` относятся ко времени до per-row-отслеживания индексером (обычно старые airdrops) и здесь игнорируются, а не считаются свежими. ### Показать NFT-коллекции этого кошелька от конкретного издателя Имена аккаунтов на NEAR кодируют иерархию: `mint.sharddog.near` — это подаккаунт `sharddog.near`, который, в свою очередь, — подаккаунт `near`. Издатели, выпускающие несколько NFT-коллекций, обычно разворачивают каждую как отдельный подаккаунт, поэтому один фильтр по суффиксу над NFT-списком аккаунта вытаскивает всё опубликованное под одним деревом — без внешнего реестра коллекций. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near PUBLISHER=sharddog.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ + "${AUTH_HEADER[@]}" \ | jq --arg publisher "$PUBLISHER" ' ("." + $publisher) as $suffix | { @@ -130,7 +143,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ }' ``` -Для `mike.near` и `sharddog.near` это возвращает четыре контракта-подаккаунта: `comic`, `mintv2`, `mint` и `claim`. Два с ненулевым `last_update_block_height` (`mint` на `115715361` и `claim` на `119718026`) — те, где позиция кошелька действительно менялась. Два других — спящие, типично для одноразовых drop-контрактов, в которые аккаунт что-то получил и больше не возвращался. +Для `root.near` и `sharddog.near` это возвращает четыре контракта-подаккаунта: `ndcconstellationnft`, `mint`, `harvestmoon` и `claim`. Только у `claim` есть ненулевой `last_update_block_height` (`131402024`), так что именно этот контракт явно менял позицию кошелька. Остальные — спящие, что типично для одноразовых drop-контрактов, в которые аккаунт что-то получил и больше не возвращался. Поменяйте `PUBLISHER` на любой аккаунт, чтобы сфокусировать фильтр на другом дереве издателя. @@ -139,12 +152,15 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ Прямые позиции в пулах лежат на `/staking`; liquid staking-токены (stNEAR, LiNEAR и т. п.) лежат на `/ft` как обычные FT. Прочитайте оба эндпоинта и классифицируйте кошелёк — `root.near` оказывается `mixed`. ```bash -API_BASE_URL=https://api.fastnear.com ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -STAKING="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking")" -FT="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft")" +STAKING="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/staking" \ + "${AUTH_HEADER[@]}")" +FT="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/ft" \ + "${AUTH_HEADER[@]}")" jq -n \ --argjson staking "$STAKING" \ @@ -153,11 +169,12 @@ jq -n \ ($staking.pools // []) as $direct | (($ft.tokens // []) | map(select(.contract_id as $id | $providers | index($id)))) as $liquid | { - classification: + classification: ( if ($direct|length)>0 and ($liquid|length)>0 then "mixed" elif ($direct|length)>0 then "direct_only" elif ($liquid|length)>0 then "liquid_only" - else "no_visible_staking_position" end, + else "no_visible_staking_position" end + ), direct_pools: ($direct | map(.pool_id)), liquid_tokens: ($liquid | map({contract_id, balance})) }' @@ -184,5 +201,5 @@ jq -n \ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/index.md b/static/ru/api/index.md index db1de67..8d937ae 100644 --- a/static/ru/api/index.md +++ b/static/ru/api/index.md @@ -68,5 +68,5 @@ https://test.api.fastnear.com - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/reference.md b/static/ru/api/reference.md index 2c25971..c2b2441 100644 --- a/static/ru/api/reference.md +++ b/static/ru/api/reference.md @@ -53,5 +53,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/reference/index.md b/static/ru/api/reference/index.md index 2c25971..c2b2441 100644 --- a/static/ru/api/reference/index.md +++ b/static/ru/api/reference/index.md @@ -53,5 +53,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/system/health.md b/static/ru/api/system/health.md index 6782bec..8a56f42 100644 --- a/static/ru/api/system/health.md +++ b/static/ru/api/system/health.md @@ -64,5 +64,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/system/health/index.md b/static/ru/api/system/health/index.md index 6782bec..8a56f42 100644 --- a/static/ru/api/system/health/index.md +++ b/static/ru/api/system/health/index.md @@ -64,5 +64,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/system/status.md b/static/ru/api/system/status.md index bc6a8a2..dfc602d 100644 --- a/static/ru/api/system/status.md +++ b/static/ru/api/system/status.md @@ -99,5 +99,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/system/status/index.md b/static/ru/api/system/status/index.md index bc6a8a2..dfc602d 100644 --- a/static/ru/api/system/status/index.md +++ b/static/ru/api/system/status/index.md @@ -99,5 +99,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/account-ft.md b/static/ru/api/v0/account-ft.md index 684a169..898302f 100644 --- a/static/ru/api/v0/account-ft.md +++ b/static/ru/api/v0/account-ft.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/account-ft/index.md b/static/ru/api/v0/account-ft/index.md index 684a169..898302f 100644 --- a/static/ru/api/v0/account-ft/index.md +++ b/static/ru/api/v0/account-ft/index.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/account-nft.md b/static/ru/api/v0/account-nft.md index 3e2c3bd..e991c28 100644 --- a/static/ru/api/v0/account-nft.md +++ b/static/ru/api/v0/account-nft.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/account-nft/index.md b/static/ru/api/v0/account-nft/index.md index 3e2c3bd..e991c28 100644 --- a/static/ru/api/v0/account-nft/index.md +++ b/static/ru/api/v0/account-nft/index.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/account-staking.md b/static/ru/api/v0/account-staking.md index ffe5615..7583a06 100644 --- a/static/ru/api/v0/account-staking.md +++ b/static/ru/api/v0/account-staking.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/account-staking/index.md b/static/ru/api/v0/account-staking/index.md index ffe5615..7583a06 100644 --- a/static/ru/api/v0/account-staking/index.md +++ b/static/ru/api/v0/account-staking/index.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/public-key-all.md b/static/ru/api/v0/public-key-all.md index 4c748d5..deb2ada 100644 --- a/static/ru/api/v0/public-key-all.md +++ b/static/ru/api/v0/public-key-all.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/public-key-all/index.md b/static/ru/api/v0/public-key-all/index.md index 4c748d5..deb2ada 100644 --- a/static/ru/api/v0/public-key-all/index.md +++ b/static/ru/api/v0/public-key-all/index.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/public-key.md b/static/ru/api/v0/public-key.md index 98960e8..8c786d4 100644 --- a/static/ru/api/v0/public-key.md +++ b/static/ru/api/v0/public-key.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v0/public-key/index.md b/static/ru/api/v0/public-key/index.md index 98960e8..8c786d4 100644 --- a/static/ru/api/v0/public-key/index.md +++ b/static/ru/api/v0/public-key/index.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-ft.md b/static/ru/api/v1/account-ft.md index 5e469eb..25c9502 100644 --- a/static/ru/api/v1/account-ft.md +++ b/static/ru/api/v1/account-ft.md @@ -114,5 +114,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-ft/index.md b/static/ru/api/v1/account-ft/index.md index 5e469eb..25c9502 100644 --- a/static/ru/api/v1/account-ft/index.md +++ b/static/ru/api/v1/account-ft/index.md @@ -114,5 +114,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-full.md b/static/ru/api/v1/account-full.md index 9059a28..afb7698 100644 --- a/static/ru/api/v1/account-full.md +++ b/static/ru/api/v1/account-full.md @@ -190,5 +190,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-full/index.md b/static/ru/api/v1/account-full/index.md index 9059a28..afb7698 100644 --- a/static/ru/api/v1/account-full/index.md +++ b/static/ru/api/v1/account-full/index.md @@ -190,5 +190,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-nft.md b/static/ru/api/v1/account-nft.md index bd381d4..755cc70 100644 --- a/static/ru/api/v1/account-nft.md +++ b/static/ru/api/v1/account-nft.md @@ -106,5 +106,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-nft/index.md b/static/ru/api/v1/account-nft/index.md index bd381d4..755cc70 100644 --- a/static/ru/api/v1/account-nft/index.md +++ b/static/ru/api/v1/account-nft/index.md @@ -106,5 +106,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-staking.md b/static/ru/api/v1/account-staking.md index 608ef2a..ce9eeec 100644 --- a/static/ru/api/v1/account-staking.md +++ b/static/ru/api/v1/account-staking.md @@ -106,5 +106,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/account-staking/index.md b/static/ru/api/v1/account-staking/index.md index 608ef2a..ce9eeec 100644 --- a/static/ru/api/v1/account-staking/index.md +++ b/static/ru/api/v1/account-staking/index.md @@ -106,5 +106,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/ft-top.md b/static/ru/api/v1/ft-top.md index 2500266..fdfa343 100644 --- a/static/ru/api/v1/ft-top.md +++ b/static/ru/api/v1/ft-top.md @@ -105,5 +105,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/ft-top/index.md b/static/ru/api/v1/ft-top/index.md index 2500266..fdfa343 100644 --- a/static/ru/api/v1/ft-top/index.md +++ b/static/ru/api/v1/ft-top/index.md @@ -105,5 +105,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/public-key-all.md b/static/ru/api/v1/public-key-all.md index 29e216c..fd250d7 100644 --- a/static/ru/api/v1/public-key-all.md +++ b/static/ru/api/v1/public-key-all.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/public-key-all/index.md b/static/ru/api/v1/public-key-all/index.md index 29e216c..fd250d7 100644 --- a/static/ru/api/v1/public-key-all/index.md +++ b/static/ru/api/v1/public-key-all/index.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/public-key.md b/static/ru/api/v1/public-key.md index bb680bf..6d42929 100644 --- a/static/ru/api/v1/public-key.md +++ b/static/ru/api/v1/public-key.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/api/v1/public-key/index.md b/static/ru/api/v1/public-key/index.md index bb680bf..6d42929 100644 --- a/static/ru/api/v1/public-key/index.md +++ b/static/ru/api/v1/public-key/index.md @@ -83,5 +83,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/system/health.md b/static/ru/apis/fastnear/system/health.md index e6dc9d4..bb40ff6 100644 --- a/static/ru/apis/fastnear/system/health.md +++ b/static/ru/apis/fastnear/system/health.md @@ -63,5 +63,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/system/health/index.md b/static/ru/apis/fastnear/system/health/index.md index e6dc9d4..bb40ff6 100644 --- a/static/ru/apis/fastnear/system/health/index.md +++ b/static/ru/apis/fastnear/system/health/index.md @@ -63,5 +63,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/system/status.md b/static/ru/apis/fastnear/system/status.md index 785d01c..5eed230 100644 --- a/static/ru/apis/fastnear/system/status.md +++ b/static/ru/apis/fastnear/system/status.md @@ -98,5 +98,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/system/status/index.md b/static/ru/apis/fastnear/system/status/index.md index 785d01c..5eed230 100644 --- a/static/ru/apis/fastnear/system/status/index.md +++ b/static/ru/apis/fastnear/system/status/index.md @@ -98,5 +98,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/account_ft.md b/static/ru/apis/fastnear/v0/account_ft.md index 293cb96..0f1ae96 100644 --- a/static/ru/apis/fastnear/v0/account_ft.md +++ b/static/ru/apis/fastnear/v0/account_ft.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/account_ft/index.md b/static/ru/apis/fastnear/v0/account_ft/index.md index 293cb96..0f1ae96 100644 --- a/static/ru/apis/fastnear/v0/account_ft/index.md +++ b/static/ru/apis/fastnear/v0/account_ft/index.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/account_nft.md b/static/ru/apis/fastnear/v0/account_nft.md index 219f2ed..a90eb82 100644 --- a/static/ru/apis/fastnear/v0/account_nft.md +++ b/static/ru/apis/fastnear/v0/account_nft.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/account_nft/index.md b/static/ru/apis/fastnear/v0/account_nft/index.md index 219f2ed..a90eb82 100644 --- a/static/ru/apis/fastnear/v0/account_nft/index.md +++ b/static/ru/apis/fastnear/v0/account_nft/index.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/account_staking.md b/static/ru/apis/fastnear/v0/account_staking.md index 5d1b951..103f697 100644 --- a/static/ru/apis/fastnear/v0/account_staking.md +++ b/static/ru/apis/fastnear/v0/account_staking.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/account_staking/index.md b/static/ru/apis/fastnear/v0/account_staking/index.md index 5d1b951..103f697 100644 --- a/static/ru/apis/fastnear/v0/account_staking/index.md +++ b/static/ru/apis/fastnear/v0/account_staking/index.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/public_key_lookup.md b/static/ru/apis/fastnear/v0/public_key_lookup.md index 05aa9f2..c5a904f 100644 --- a/static/ru/apis/fastnear/v0/public_key_lookup.md +++ b/static/ru/apis/fastnear/v0/public_key_lookup.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/public_key_lookup/index.md b/static/ru/apis/fastnear/v0/public_key_lookup/index.md index 05aa9f2..c5a904f 100644 --- a/static/ru/apis/fastnear/v0/public_key_lookup/index.md +++ b/static/ru/apis/fastnear/v0/public_key_lookup/index.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/public_key_lookup_all.md b/static/ru/apis/fastnear/v0/public_key_lookup_all.md index df998e1..8d33394 100644 --- a/static/ru/apis/fastnear/v0/public_key_lookup_all.md +++ b/static/ru/apis/fastnear/v0/public_key_lookup_all.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v0/public_key_lookup_all/index.md b/static/ru/apis/fastnear/v0/public_key_lookup_all/index.md index df998e1..8d33394 100644 --- a/static/ru/apis/fastnear/v0/public_key_lookup_all/index.md +++ b/static/ru/apis/fastnear/v0/public_key_lookup_all/index.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_ft.md b/static/ru/apis/fastnear/v1/account_ft.md index 39302f1..e04e673 100644 --- a/static/ru/apis/fastnear/v1/account_ft.md +++ b/static/ru/apis/fastnear/v1/account_ft.md @@ -113,5 +113,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_ft/index.md b/static/ru/apis/fastnear/v1/account_ft/index.md index 39302f1..e04e673 100644 --- a/static/ru/apis/fastnear/v1/account_ft/index.md +++ b/static/ru/apis/fastnear/v1/account_ft/index.md @@ -113,5 +113,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_full.md b/static/ru/apis/fastnear/v1/account_full.md index 4d8e204..d2b1b2b 100644 --- a/static/ru/apis/fastnear/v1/account_full.md +++ b/static/ru/apis/fastnear/v1/account_full.md @@ -189,5 +189,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_full/index.md b/static/ru/apis/fastnear/v1/account_full/index.md index 4d8e204..d2b1b2b 100644 --- a/static/ru/apis/fastnear/v1/account_full/index.md +++ b/static/ru/apis/fastnear/v1/account_full/index.md @@ -189,5 +189,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_nft.md b/static/ru/apis/fastnear/v1/account_nft.md index 1292525..427b7d4 100644 --- a/static/ru/apis/fastnear/v1/account_nft.md +++ b/static/ru/apis/fastnear/v1/account_nft.md @@ -105,5 +105,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_nft/index.md b/static/ru/apis/fastnear/v1/account_nft/index.md index 1292525..427b7d4 100644 --- a/static/ru/apis/fastnear/v1/account_nft/index.md +++ b/static/ru/apis/fastnear/v1/account_nft/index.md @@ -105,5 +105,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_staking.md b/static/ru/apis/fastnear/v1/account_staking.md index 5356377..ccb8052 100644 --- a/static/ru/apis/fastnear/v1/account_staking.md +++ b/static/ru/apis/fastnear/v1/account_staking.md @@ -105,5 +105,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/account_staking/index.md b/static/ru/apis/fastnear/v1/account_staking/index.md index 5356377..ccb8052 100644 --- a/static/ru/apis/fastnear/v1/account_staking/index.md +++ b/static/ru/apis/fastnear/v1/account_staking/index.md @@ -105,5 +105,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/ft_top.md b/static/ru/apis/fastnear/v1/ft_top.md index c1acd6a..ee2fd86 100644 --- a/static/ru/apis/fastnear/v1/ft_top.md +++ b/static/ru/apis/fastnear/v1/ft_top.md @@ -104,5 +104,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/ft_top/index.md b/static/ru/apis/fastnear/v1/ft_top/index.md index c1acd6a..ee2fd86 100644 --- a/static/ru/apis/fastnear/v1/ft_top/index.md +++ b/static/ru/apis/fastnear/v1/ft_top/index.md @@ -104,5 +104,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/public_key_lookup.md b/static/ru/apis/fastnear/v1/public_key_lookup.md index 97caa03..566816c 100644 --- a/static/ru/apis/fastnear/v1/public_key_lookup.md +++ b/static/ru/apis/fastnear/v1/public_key_lookup.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/public_key_lookup/index.md b/static/ru/apis/fastnear/v1/public_key_lookup/index.md index 97caa03..566816c 100644 --- a/static/ru/apis/fastnear/v1/public_key_lookup/index.md +++ b/static/ru/apis/fastnear/v1/public_key_lookup/index.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/public_key_lookup_all.md b/static/ru/apis/fastnear/v1/public_key_lookup_all.md index 1874eb3..16f53e0 100644 --- a/static/ru/apis/fastnear/v1/public_key_lookup_all.md +++ b/static/ru/apis/fastnear/v1/public_key_lookup_all.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/fastnear/v1/public_key_lookup_all/index.md b/static/ru/apis/fastnear/v1/public_key_lookup_all/index.md index 1874eb3..16f53e0 100644 --- a/static/ru/apis/fastnear/v1/public_key_lookup_all/index.md +++ b/static/ru/apis/fastnear/v1/public_key_lookup_all/index.md @@ -82,5 +82,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/all_by_predecessor.md b/static/ru/apis/kv-fastdata/v0/all_by_predecessor.md index 0d35dae..c329791 100644 --- a/static/ru/apis/kv-fastdata/v0/all_by_predecessor.md +++ b/static/ru/apis/kv-fastdata/v0/all_by_predecessor.md @@ -211,5 +211,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/all_by_predecessor/index.md b/static/ru/apis/kv-fastdata/v0/all_by_predecessor/index.md index 0d35dae..c329791 100644 --- a/static/ru/apis/kv-fastdata/v0/all_by_predecessor/index.md +++ b/static/ru/apis/kv-fastdata/v0/all_by_predecessor/index.md @@ -211,5 +211,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/get_history_key.md b/static/ru/apis/kv-fastdata/v0/get_history_key.md index ca7d050..6938ec0 100644 --- a/static/ru/apis/kv-fastdata/v0/get_history_key.md +++ b/static/ru/apis/kv-fastdata/v0/get_history_key.md @@ -168,5 +168,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/get_history_key/index.md b/static/ru/apis/kv-fastdata/v0/get_history_key/index.md index ca7d050..6938ec0 100644 --- a/static/ru/apis/kv-fastdata/v0/get_history_key/index.md +++ b/static/ru/apis/kv-fastdata/v0/get_history_key/index.md @@ -168,5 +168,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/get_latest_key.md b/static/ru/apis/kv-fastdata/v0/get_latest_key.md index 8e41a7d..5e19d0d 100644 --- a/static/ru/apis/kv-fastdata/v0/get_latest_key.md +++ b/static/ru/apis/kv-fastdata/v0/get_latest_key.md @@ -168,5 +168,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/get_latest_key/index.md b/static/ru/apis/kv-fastdata/v0/get_latest_key/index.md index 8e41a7d..5e19d0d 100644 --- a/static/ru/apis/kv-fastdata/v0/get_latest_key/index.md +++ b/static/ru/apis/kv-fastdata/v0/get_latest_key/index.md @@ -168,5 +168,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/history_by_account.md b/static/ru/apis/kv-fastdata/v0/history_by_account.md index 2d9c8da..df0c9f7 100644 --- a/static/ru/apis/kv-fastdata/v0/history_by_account.md +++ b/static/ru/apis/kv-fastdata/v0/history_by_account.md @@ -243,5 +243,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/history_by_account/index.md b/static/ru/apis/kv-fastdata/v0/history_by_account/index.md index 2d9c8da..df0c9f7 100644 --- a/static/ru/apis/kv-fastdata/v0/history_by_account/index.md +++ b/static/ru/apis/kv-fastdata/v0/history_by_account/index.md @@ -243,5 +243,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/history_by_key.md b/static/ru/apis/kv-fastdata/v0/history_by_key.md index 9f6e7d5..082cd03 100644 --- a/static/ru/apis/kv-fastdata/v0/history_by_key.md +++ b/static/ru/apis/kv-fastdata/v0/history_by_key.md @@ -229,5 +229,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/history_by_key/index.md b/static/ru/apis/kv-fastdata/v0/history_by_key/index.md index 9f6e7d5..082cd03 100644 --- a/static/ru/apis/kv-fastdata/v0/history_by_key/index.md +++ b/static/ru/apis/kv-fastdata/v0/history_by_key/index.md @@ -229,5 +229,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/history_by_predecessor.md b/static/ru/apis/kv-fastdata/v0/history_by_predecessor.md index b62baf0..1c467c2 100644 --- a/static/ru/apis/kv-fastdata/v0/history_by_predecessor.md +++ b/static/ru/apis/kv-fastdata/v0/history_by_predecessor.md @@ -246,5 +246,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/history_by_predecessor/index.md b/static/ru/apis/kv-fastdata/v0/history_by_predecessor/index.md index b62baf0..1c467c2 100644 --- a/static/ru/apis/kv-fastdata/v0/history_by_predecessor/index.md +++ b/static/ru/apis/kv-fastdata/v0/history_by_predecessor/index.md @@ -246,5 +246,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/latest_by_account.md b/static/ru/apis/kv-fastdata/v0/latest_by_account.md index f7f352f..cbf86ce 100644 --- a/static/ru/apis/kv-fastdata/v0/latest_by_account.md +++ b/static/ru/apis/kv-fastdata/v0/latest_by_account.md @@ -231,5 +231,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/latest_by_account/index.md b/static/ru/apis/kv-fastdata/v0/latest_by_account/index.md index f7f352f..cbf86ce 100644 --- a/static/ru/apis/kv-fastdata/v0/latest_by_account/index.md +++ b/static/ru/apis/kv-fastdata/v0/latest_by_account/index.md @@ -231,5 +231,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/latest_by_predecessor.md b/static/ru/apis/kv-fastdata/v0/latest_by_predecessor.md index a71abd6..0034078 100644 --- a/static/ru/apis/kv-fastdata/v0/latest_by_predecessor.md +++ b/static/ru/apis/kv-fastdata/v0/latest_by_predecessor.md @@ -234,5 +234,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/latest_by_predecessor/index.md b/static/ru/apis/kv-fastdata/v0/latest_by_predecessor/index.md index a71abd6..0034078 100644 --- a/static/ru/apis/kv-fastdata/v0/latest_by_predecessor/index.md +++ b/static/ru/apis/kv-fastdata/v0/latest_by_predecessor/index.md @@ -234,5 +234,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/multi.md b/static/ru/apis/kv-fastdata/v0/multi.md index 8ea9608..3507608 100644 --- a/static/ru/apis/kv-fastdata/v0/multi.md +++ b/static/ru/apis/kv-fastdata/v0/multi.md @@ -202,5 +202,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/kv-fastdata/v0/multi/index.md b/static/ru/apis/kv-fastdata/v0/multi/index.md index 8ea9608..3507608 100644 --- a/static/ru/apis/kv-fastdata/v0/multi/index.md +++ b/static/ru/apis/kv-fastdata/v0/multi/index.md @@ -202,5 +202,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/system/health.md b/static/ru/apis/neardata/system/health.md index 1189dbb..06d5e15 100644 --- a/static/ru/apis/neardata/system/health.md +++ b/static/ru/apis/neardata/system/health.md @@ -63,5 +63,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/system/health/index.md b/static/ru/apis/neardata/system/health/index.md index 1189dbb..06d5e15 100644 --- a/static/ru/apis/neardata/system/health/index.md +++ b/static/ru/apis/neardata/system/health/index.md @@ -63,5 +63,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block.md b/static/ru/apis/neardata/v0/block.md index b2c9ad7..5c9569d 100644 --- a/static/ru/apis/neardata/v0/block.md +++ b/static/ru/apis/neardata/v0/block.md @@ -1855,5 +1855,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block/index.md b/static/ru/apis/neardata/v0/block/index.md index b2c9ad7..5c9569d 100644 --- a/static/ru/apis/neardata/v0/block/index.md +++ b/static/ru/apis/neardata/v0/block/index.md @@ -1855,5 +1855,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_chunk.md b/static/ru/apis/neardata/v0/block_chunk.md index d7ffffb..761be1f 100644 --- a/static/ru/apis/neardata/v0/block_chunk.md +++ b/static/ru/apis/neardata/v0/block_chunk.md @@ -910,5 +910,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_chunk/index.md b/static/ru/apis/neardata/v0/block_chunk/index.md index d7ffffb..761be1f 100644 --- a/static/ru/apis/neardata/v0/block_chunk/index.md +++ b/static/ru/apis/neardata/v0/block_chunk/index.md @@ -910,5 +910,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_headers.md b/static/ru/apis/neardata/v0/block_headers.md index 3735527..0c4e456 100644 --- a/static/ru/apis/neardata/v0/block_headers.md +++ b/static/ru/apis/neardata/v0/block_headers.md @@ -255,5 +255,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_headers/index.md b/static/ru/apis/neardata/v0/block_headers/index.md index 3735527..0c4e456 100644 --- a/static/ru/apis/neardata/v0/block_headers/index.md +++ b/static/ru/apis/neardata/v0/block_headers/index.md @@ -255,5 +255,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_optimistic.md b/static/ru/apis/neardata/v0/block_optimistic.md index 20dbecb..907aed8 100644 --- a/static/ru/apis/neardata/v0/block_optimistic.md +++ b/static/ru/apis/neardata/v0/block_optimistic.md @@ -1855,5 +1855,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_optimistic/index.md b/static/ru/apis/neardata/v0/block_optimistic/index.md index 20dbecb..907aed8 100644 --- a/static/ru/apis/neardata/v0/block_optimistic/index.md +++ b/static/ru/apis/neardata/v0/block_optimistic/index.md @@ -1855,5 +1855,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_shard.md b/static/ru/apis/neardata/v0/block_shard.md index b09d32e..09c2a1a 100644 --- a/static/ru/apis/neardata/v0/block_shard.md +++ b/static/ru/apis/neardata/v0/block_shard.md @@ -1634,5 +1634,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/block_shard/index.md b/static/ru/apis/neardata/v0/block_shard/index.md index b09d32e..09c2a1a 100644 --- a/static/ru/apis/neardata/v0/block_shard/index.md +++ b/static/ru/apis/neardata/v0/block_shard/index.md @@ -1634,5 +1634,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/first_block.md b/static/ru/apis/neardata/v0/first_block.md index de64f25..e7e13b8 100644 --- a/static/ru/apis/neardata/v0/first_block.md +++ b/static/ru/apis/neardata/v0/first_block.md @@ -1847,5 +1847,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/first_block/index.md b/static/ru/apis/neardata/v0/first_block/index.md index de64f25..e7e13b8 100644 --- a/static/ru/apis/neardata/v0/first_block/index.md +++ b/static/ru/apis/neardata/v0/first_block/index.md @@ -1847,5 +1847,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/last_block_final.md b/static/ru/apis/neardata/v0/last_block_final.md index 1608fac..10648e0 100644 --- a/static/ru/apis/neardata/v0/last_block_final.md +++ b/static/ru/apis/neardata/v0/last_block_final.md @@ -1847,5 +1847,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/last_block_final/index.md b/static/ru/apis/neardata/v0/last_block_final/index.md index 1608fac..10648e0 100644 --- a/static/ru/apis/neardata/v0/last_block_final/index.md +++ b/static/ru/apis/neardata/v0/last_block_final/index.md @@ -1847,5 +1847,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/last_block_optimistic.md b/static/ru/apis/neardata/v0/last_block_optimistic.md index e5212e0..83de3d2 100644 --- a/static/ru/apis/neardata/v0/last_block_optimistic.md +++ b/static/ru/apis/neardata/v0/last_block_optimistic.md @@ -1847,5 +1847,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/neardata/v0/last_block_optimistic/index.md b/static/ru/apis/neardata/v0/last_block_optimistic/index.md index e5212e0..83de3d2 100644 --- a/static/ru/apis/neardata/v0/last_block_optimistic/index.md +++ b/static/ru/apis/neardata/v0/last_block_optimistic/index.md @@ -1847,5 +1847,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/account.md b/static/ru/apis/transactions/v0/account.md index 55fe2a1..700d1db 100644 --- a/static/ru/apis/transactions/v0/account.md +++ b/static/ru/apis/transactions/v0/account.md @@ -418,5 +418,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/account/index.md b/static/ru/apis/transactions/v0/account/index.md index 55fe2a1..700d1db 100644 --- a/static/ru/apis/transactions/v0/account/index.md +++ b/static/ru/apis/transactions/v0/account/index.md @@ -418,5 +418,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/block.md b/static/ru/apis/transactions/v0/block.md index a26190a..b10cc99 100644 --- a/static/ru/apis/transactions/v0/block.md +++ b/static/ru/apis/transactions/v0/block.md @@ -593,5 +593,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/block/index.md b/static/ru/apis/transactions/v0/block/index.md index a26190a..b10cc99 100644 --- a/static/ru/apis/transactions/v0/block/index.md +++ b/static/ru/apis/transactions/v0/block/index.md @@ -593,5 +593,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/blocks.md b/static/ru/apis/transactions/v0/blocks.md index 5bf2fc5..e3f061a 100644 --- a/static/ru/apis/transactions/v0/blocks.md +++ b/static/ru/apis/transactions/v0/blocks.md @@ -272,5 +272,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/blocks/index.md b/static/ru/apis/transactions/v0/blocks/index.md index 5bf2fc5..e3f061a 100644 --- a/static/ru/apis/transactions/v0/blocks/index.md +++ b/static/ru/apis/transactions/v0/blocks/index.md @@ -272,5 +272,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/receipt.md b/static/ru/apis/transactions/v0/receipt.md index c20bbe5..cd5eee3 100644 --- a/static/ru/apis/transactions/v0/receipt.md +++ b/static/ru/apis/transactions/v0/receipt.md @@ -239,5 +239,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/receipt/index.md b/static/ru/apis/transactions/v0/receipt/index.md index c20bbe5..cd5eee3 100644 --- a/static/ru/apis/transactions/v0/receipt/index.md +++ b/static/ru/apis/transactions/v0/receipt/index.md @@ -239,5 +239,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/transactions.md b/static/ru/apis/transactions/v0/transactions.md index 3a4a8f6..f685902 100644 --- a/static/ru/apis/transactions/v0/transactions.md +++ b/static/ru/apis/transactions/v0/transactions.md @@ -102,5 +102,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transactions/v0/transactions/index.md b/static/ru/apis/transactions/v0/transactions/index.md index 3a4a8f6..f685902 100644 --- a/static/ru/apis/transactions/v0/transactions/index.md +++ b/static/ru/apis/transactions/v0/transactions/index.md @@ -102,5 +102,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transfers/v0/transfers.md b/static/ru/apis/transfers/v0/transfers.md index 3cf3886..ce89f05 100644 --- a/static/ru/apis/transfers/v0/transfers.md +++ b/static/ru/apis/transfers/v0/transfers.md @@ -387,5 +387,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/apis/transfers/v0/transfers/index.md b/static/ru/apis/transfers/v0/transfers/index.md index 3cf3886..ce89f05 100644 --- a/static/ru/apis/transfers/v0/transfers/index.md +++ b/static/ru/apis/transfers/v0/transfers/index.md @@ -387,5 +387,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/auth.md b/static/ru/auth.md index e81a7a7..b6f93a1 100644 --- a/static/ru/auth.md +++ b/static/ru/auth.md @@ -2,19 +2,21 @@ # Аутентификация и доступ -Один API-ключ FastNear работает и для [RPC](https://docs.fastnear.com/ru/rpc), и для [API-эндпоинтов](https://docs.fastnear.com/ru/api). Многие публичные чтения работают и без него, но когда ключ нужен, модель остаётся простой: используйте один и тот же ключ везде и передавайте его либо через заголовок `Authorization: Bearer`, либо как URL-параметр `?apiKey=`. +Один API-ключ FastNear работает и для [RPC](https://docs.fastnear.com/ru/rpc), и для [API-эндпоинтов](https://docs.fastnear.com/ru/api). Держите модель аутентификации простой: используйте один и тот же ключ везде и передавайте его либо через заголовок `Authorization: Bearer`, либо как URL-параметр `?apiKey=`. Та же модель действует и на обычных, и на архивных RPC-хостах. Хранение ключа в браузере для UI документации — это удобство документации, а не продовый шаблон. -Войдите на [dashboard.fastnear.com](https://dashboard.fastnear.com), чтобы получить ключ, и отправляйте его в каждом запросе одним из способов ниже. +Получите ключ в [FastNear Dashboard](https://dashboard.fastnear.com) и отправляйте его в каждом запросе одним из способов ниже. Страницы с интерактивными примерами также поддерживают `Copy example URL`, чтобы делиться уже заполненными запросами. Общие URL примеров выполняются автоматически при загрузке, когда в них есть состояние операции, а сохранённые API-ключи и токены никогда не включаются в такие общедоступные URL документации. ## Через заголовок Authorization ```bash +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" + curl "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer ${API_KEY}" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` @@ -22,7 +24,9 @@ curl "https://rpc.mainnet.fastnear.com" \ ## Через URL-параметр `?apiKey=` ```bash -curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" \ +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" + +curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` @@ -31,5 +35,5 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" \ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/auth/index.md b/static/ru/auth/index.md index e81a7a7..b6f93a1 100644 --- a/static/ru/auth/index.md +++ b/static/ru/auth/index.md @@ -2,19 +2,21 @@ # Аутентификация и доступ -Один API-ключ FastNear работает и для [RPC](https://docs.fastnear.com/ru/rpc), и для [API-эндпоинтов](https://docs.fastnear.com/ru/api). Многие публичные чтения работают и без него, но когда ключ нужен, модель остаётся простой: используйте один и тот же ключ везде и передавайте его либо через заголовок `Authorization: Bearer`, либо как URL-параметр `?apiKey=`. +Один API-ключ FastNear работает и для [RPC](https://docs.fastnear.com/ru/rpc), и для [API-эндпоинтов](https://docs.fastnear.com/ru/api). Держите модель аутентификации простой: используйте один и тот же ключ везде и передавайте его либо через заголовок `Authorization: Bearer`, либо как URL-параметр `?apiKey=`. Та же модель действует и на обычных, и на архивных RPC-хостах. Хранение ключа в браузере для UI документации — это удобство документации, а не продовый шаблон. -Войдите на [dashboard.fastnear.com](https://dashboard.fastnear.com), чтобы получить ключ, и отправляйте его в каждом запросе одним из способов ниже. +Получите ключ в [FastNear Dashboard](https://dashboard.fastnear.com) и отправляйте его в каждом запросе одним из способов ниже. Страницы с интерактивными примерами также поддерживают `Copy example URL`, чтобы делиться уже заполненными запросами. Общие URL примеров выполняются автоматически при загрузке, когда в них есть состояние операции, а сохранённые API-ключи и токены никогда не включаются в такие общедоступные URL документации. ## Через заголовок Authorization ```bash +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" + curl "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer ${API_KEY}" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` @@ -22,7 +24,9 @@ curl "https://rpc.mainnet.fastnear.com" \ ## Через URL-параметр `?apiKey=` ```bash -curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" \ +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" + +curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` @@ -31,5 +35,5 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" \ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv.md b/static/ru/fastdata/kv.md index 0df2f0a..2ef9d8b 100644 --- a/static/ru/fastdata/kv.md +++ b/static/ru/fastdata/kv.md @@ -19,14 +19,16 @@ https://kv.test.fastnear.com Если вы уже знаете один точный ключ, начните с последней индексированной строки и остановитесь, как только она ответит на вопрос. ```bash -KV_BASE_URL=https://kv.main.fastnear.com CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ +curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{ latest: ( .entries[0] @@ -112,5 +114,5 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/all-by-predecessor.md b/static/ru/fastdata/kv/all-by-predecessor.md index 413bb30..1dd24e5 100644 --- a/static/ru/fastdata/kv/all-by-predecessor.md +++ b/static/ru/fastdata/kv/all-by-predecessor.md @@ -212,5 +212,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/all-by-predecessor/index.md b/static/ru/fastdata/kv/all-by-predecessor/index.md index 413bb30..1dd24e5 100644 --- a/static/ru/fastdata/kv/all-by-predecessor/index.md +++ b/static/ru/fastdata/kv/all-by-predecessor/index.md @@ -212,5 +212,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/examples.md b/static/ru/fastdata/kv/examples.md index d04b82f..269d82d 100644 --- a/static/ru/fastdata/kv/examples.md +++ b/static/ru/fastdata/kv/examples.md @@ -1,16 +1,56 @@ **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) -## Пример +## Примеры + +Все shell-примеры ниже работают на публичных KV FastData-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + +### Проверить один точный ключ и сразу посмотреть его историю + +Если контракт, `predecessor_id` и точный ключ уже известны, начинайте с узкого запроса. `latest` отвечает на вопрос о текущем состоянии, а `history` показывает, менялась ли именно эта строка со временем. + +```bash +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +LATEST="$(curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + "${AUTH_HEADER[@]}")" + +echo "$LATEST" | jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' + +curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + "${AUTH_HEADER[@]}" \ + | jq '{writes: [.entries[] | {block_height, value}]}' +``` + +Для точного ключа вроде этого follow-edge `latest` даёт текущее индексированное значение одной строкой, а `history` показывает, была ли запись однократной или переключалась со временем. Начинайте отсюда, когда путь в storage уже известен; расширяйтесь до выборок по `predecessor_id` только тогда, когда нужно не доказательство, а поиск. ### Посмотреть индексированные записи одного `predecessor_id` и сузиться до изменившегося ключа `all-by-predecessor` возвращает последние индексированные записи одного аккаунта по каждому контракту, которого он касался. Выберите интересный ключ и прогоните его через `history`, чтобы увидеть, как эта строка менялась со временем. ```bash -KV_BASE_URL=https://kv.main.fastnear.com PREDECESSOR_ID=jemartel.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -FIRST="$(curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ +FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data '{"include_metadata":true,"limit":10}')" @@ -25,11 +65,20 @@ echo "$FIRST" | jq '{ Поднимите самую свежую строку и прогоните её через `history`: ```bash +PREDECESSOR_ID=jemartel.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data '{"include_metadata":true,"limit":10}')" + CURRENT_ACCOUNT_ID="$(echo "$FIRST" | jq -r '.entries[0].current_account_id')" EXACT_KEY="$(echo "$FIRST" | jq -r '.entries[0].key')" ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ +curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{entries: [.entries[] | {block_height, value}]}' ``` @@ -54,5 +103,5 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KE - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/examples/index.md b/static/ru/fastdata/kv/examples/index.md index d04b82f..269d82d 100644 --- a/static/ru/fastdata/kv/examples/index.md +++ b/static/ru/fastdata/kv/examples/index.md @@ -1,16 +1,56 @@ **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) -## Пример +## Примеры + +Все shell-примеры ниже работают на публичных KV FastData-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + +### Проверить один точный ключ и сразу посмотреть его историю + +Если контракт, `predecessor_id` и точный ключ уже известны, начинайте с узкого запроса. `latest` отвечает на вопрос о текущем состоянии, а `history` показывает, менялась ли именно эта строка со временем. + +```bash +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +LATEST="$(curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + "${AUTH_HEADER[@]}")" + +echo "$LATEST" | jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' + +curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + "${AUTH_HEADER[@]}" \ + | jq '{writes: [.entries[] | {block_height, value}]}' +``` + +Для точного ключа вроде этого follow-edge `latest` даёт текущее индексированное значение одной строкой, а `history` показывает, была ли запись однократной или переключалась со временем. Начинайте отсюда, когда путь в storage уже известен; расширяйтесь до выборок по `predecessor_id` только тогда, когда нужно не доказательство, а поиск. ### Посмотреть индексированные записи одного `predecessor_id` и сузиться до изменившегося ключа `all-by-predecessor` возвращает последние индексированные записи одного аккаунта по каждому контракту, которого он касался. Выберите интересный ключ и прогоните его через `history`, чтобы увидеть, как эта строка менялась со временем. ```bash -KV_BASE_URL=https://kv.main.fastnear.com PREDECESSOR_ID=jemartel.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -FIRST="$(curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ +FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data '{"include_metadata":true,"limit":10}')" @@ -25,11 +65,20 @@ echo "$FIRST" | jq '{ Поднимите самую свежую строку и прогоните её через `history`: ```bash +PREDECESSOR_ID=jemartel.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data '{"include_metadata":true,"limit":10}')" + CURRENT_ACCOUNT_ID="$(echo "$FIRST" | jq -r '.entries[0].current_account_id')" EXACT_KEY="$(echo "$FIRST" | jq -r '.entries[0].key')" ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ +curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{entries: [.entries[] | {block_height, value}]}' ``` @@ -54,5 +103,5 @@ curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KE - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/get-history-key.md b/static/ru/fastdata/kv/get-history-key.md index 28551aa..abdc612 100644 --- a/static/ru/fastdata/kv/get-history-key.md +++ b/static/ru/fastdata/kv/get-history-key.md @@ -169,5 +169,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/get-history-key/index.md b/static/ru/fastdata/kv/get-history-key/index.md index 28551aa..abdc612 100644 --- a/static/ru/fastdata/kv/get-history-key/index.md +++ b/static/ru/fastdata/kv/get-history-key/index.md @@ -169,5 +169,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/get-latest-key.md b/static/ru/fastdata/kv/get-latest-key.md index d0f1bf5..51fd904 100644 --- a/static/ru/fastdata/kv/get-latest-key.md +++ b/static/ru/fastdata/kv/get-latest-key.md @@ -169,5 +169,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/get-latest-key/index.md b/static/ru/fastdata/kv/get-latest-key/index.md index d0f1bf5..51fd904 100644 --- a/static/ru/fastdata/kv/get-latest-key/index.md +++ b/static/ru/fastdata/kv/get-latest-key/index.md @@ -169,5 +169,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/history-by-account.md b/static/ru/fastdata/kv/history-by-account.md index 9fa8470..c6c8f1a 100644 --- a/static/ru/fastdata/kv/history-by-account.md +++ b/static/ru/fastdata/kv/history-by-account.md @@ -244,5 +244,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/history-by-account/index.md b/static/ru/fastdata/kv/history-by-account/index.md index 9fa8470..c6c8f1a 100644 --- a/static/ru/fastdata/kv/history-by-account/index.md +++ b/static/ru/fastdata/kv/history-by-account/index.md @@ -244,5 +244,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/history-by-key.md b/static/ru/fastdata/kv/history-by-key.md index ebf2e18..b31487e 100644 --- a/static/ru/fastdata/kv/history-by-key.md +++ b/static/ru/fastdata/kv/history-by-key.md @@ -230,5 +230,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/history-by-key/index.md b/static/ru/fastdata/kv/history-by-key/index.md index ebf2e18..b31487e 100644 --- a/static/ru/fastdata/kv/history-by-key/index.md +++ b/static/ru/fastdata/kv/history-by-key/index.md @@ -230,5 +230,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/history-by-predecessor.md b/static/ru/fastdata/kv/history-by-predecessor.md index af7abcf..e9e9a3e 100644 --- a/static/ru/fastdata/kv/history-by-predecessor.md +++ b/static/ru/fastdata/kv/history-by-predecessor.md @@ -247,5 +247,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/history-by-predecessor/index.md b/static/ru/fastdata/kv/history-by-predecessor/index.md index af7abcf..e9e9a3e 100644 --- a/static/ru/fastdata/kv/history-by-predecessor/index.md +++ b/static/ru/fastdata/kv/history-by-predecessor/index.md @@ -247,5 +247,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/index.md b/static/ru/fastdata/kv/index.md index 0df2f0a..2ef9d8b 100644 --- a/static/ru/fastdata/kv/index.md +++ b/static/ru/fastdata/kv/index.md @@ -19,14 +19,16 @@ https://kv.test.fastnear.com Если вы уже знаете один точный ключ, начните с последней индексированной строки и остановитесь, как только она ответит на вопрос. ```bash -KV_BASE_URL=https://kv.main.fastnear.com CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ +curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{ latest: ( .entries[0] @@ -112,5 +114,5 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/latest-by-account.md b/static/ru/fastdata/kv/latest-by-account.md index 9b3842c..df2cc3a 100644 --- a/static/ru/fastdata/kv/latest-by-account.md +++ b/static/ru/fastdata/kv/latest-by-account.md @@ -232,5 +232,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/latest-by-account/index.md b/static/ru/fastdata/kv/latest-by-account/index.md index 9b3842c..df2cc3a 100644 --- a/static/ru/fastdata/kv/latest-by-account/index.md +++ b/static/ru/fastdata/kv/latest-by-account/index.md @@ -232,5 +232,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/latest-by-predecessor.md b/static/ru/fastdata/kv/latest-by-predecessor.md index 2356664..06a5339 100644 --- a/static/ru/fastdata/kv/latest-by-predecessor.md +++ b/static/ru/fastdata/kv/latest-by-predecessor.md @@ -235,5 +235,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/latest-by-predecessor/index.md b/static/ru/fastdata/kv/latest-by-predecessor/index.md index 2356664..06a5339 100644 --- a/static/ru/fastdata/kv/latest-by-predecessor/index.md +++ b/static/ru/fastdata/kv/latest-by-predecessor/index.md @@ -235,5 +235,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/multi.md b/static/ru/fastdata/kv/multi.md index 9dd1028..b31df34 100644 --- a/static/ru/fastdata/kv/multi.md +++ b/static/ru/fastdata/kv/multi.md @@ -203,5 +203,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/fastdata/kv/multi/index.md b/static/ru/fastdata/kv/multi/index.md index 9dd1028..b31df34 100644 --- a/static/ru/fastdata/kv/multi/index.md +++ b/static/ru/fastdata/kv/multi/index.md @@ -203,5 +203,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index e93e00a..c330148 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -13,7 +13,7 @@ - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Практические примеры KV FastData: scoped-записи, история ключа и переход к точному состоянию. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Практические примеры KV FastData: точные ключи, scoped-записи, история ключа и переход к точному состоянию. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Недавние чтения по блокам и шардам для мониторинга активности контракта, подтверждения оптимистичных наблюдений и проверки изменений на уровне шарда. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. - [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Практические примеры RPC: проверки состояния, инспекция блоков, чтение контрактов и отправка транзакций. diff --git a/static/ru/index.md b/static/ru/index.md index 9c26d95..591bad6 100644 --- a/static/ru/index.md +++ b/static/ru/index.md @@ -20,7 +20,7 @@ URL-параметр ?apiKey=... - Сначала возьмите ключ — войдите на [dashboard.fastnear.com](https://dashboard.fastnear.com) через Google или email, получите бесплатные стартовые кредиты и подключайте месячную подписку или бессрочные резервные кредиты только тогда, когда понадобятся повышенные лимиты. + Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com) и используйте его во всех запросах к FastNear из одного и того же рабочего окружения. Быстрая маршрутизация @@ -97,7 +97,7 @@ Один API-ключ FastNear работает и для RPC, и для REST API. Ключи и оплата - [Dashboard](https://dashboard.fastnear.com) + [FastNear Dashboard](https://dashboard.fastnear.com) Войдите, создайте ключи и переходите на сценарии с более высокими лимитами, когда понадобится. Живые операции @@ -120,5 +120,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/internationalization.md b/static/ru/internationalization.md index 41647ea..65222a6 100644 --- a/static/ru/internationalization.md +++ b/static/ru/internationalization.md @@ -257,5 +257,5 @@ Playwright, relevance scoring и более тяжёлые редакционн - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/internationalization/index.md b/static/ru/internationalization/index.md index 41647ea..65222a6 100644 --- a/static/ru/internationalization/index.md +++ b/static/ru/internationalization/index.md @@ -257,5 +257,5 @@ Playwright, relevance scoring и более тяжёлые редакционн - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index 45654fa..42962f5 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -31,7 +31,7 @@ AI-читабельные Markdown-копии авторских гайдов и URL-параметр ?apiKey=... - Сначала возьмите ключ — войдите на [dashboard.fastnear.com](https://dashboard.fastnear.com) через Google или email, получите бесплатные стартовые кредиты и подключайте месячную подписку или бессрочные резервные кредиты только тогда, когда понадобятся повышенные лимиты. + Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com) и используйте его во всех запросах к FastNear из одного и того же рабочего окружения. Быстрая маршрутизация @@ -108,7 +108,7 @@ AI-читабельные Markdown-копии авторских гайдов и Один API-ключ FastNear работает и для RPC, и для REST API. Ключи и оплата - [Dashboard](https://dashboard.fastnear.com) + [FastNear Dashboard](https://dashboard.fastnear.com) Войдите, создайте ключи и переходите на сценарии с более высокими лимитами, когда понадобится. Живые операции @@ -153,6 +153,7 @@ AI-читабельные Markdown-копии авторских гайдов и - Используйте индексированные API, когда пользователю нужен ответ в продуктовой форме — балансы, активы, история аккаунта или история переводов. - Используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), когда пользователю нужны канонические поля на уровне протокола, вызовы контрактов или отправка транзакций. +- Если вы используете размещённый JS-рантайм на [js.fastnear.com](https://js.fastnear.com), начинайте с низкоуровневых методов вроде `near.view`, `near.queryAccount` и `near.tx.*`, а к `near.recipes.*` обращайтесь только тогда, когда task helper действительно является самым коротким путём к ответу. - Используйте [NEAR Data API](https://docs.fastnear.com/ru/neardata), когда вопрос касается свежих оптимистичных или финализированных блоков и явного опроса. - Используйте [Снапшоты](https://docs.fastnear.com/ru/snapshots) для операторских сценариев, а не для чтения прикладных данных. - Один API-ключ FastNear работает и для RPC, и для API-эндпоинтов. @@ -226,20 +227,24 @@ AI-читабельные Markdown-копии авторских гайдов и ## Аутентифицируйтесь один раз, используйте везде -Публичные эндпоинты часто работают и без ключа. Добавьте ключ, если нужны повышенные лимиты, единая аутентифицированная модель или платные сценарии. Один и тот же ключ работает со всеми API FastNear выше, включая обычные и архивные RPC-хосты; передавайте его либо в HTTP-заголовке, либо в URL-параметре: +Начните с API-ключа FastNear и используйте его во всех API FastNear выше, включая обычные и архивные RPC-хосты. Передавайте его либо в HTTP-заголовке, либо в URL-параметре: ```bash title="Заголовок Authorization" +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" + curl "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer ${API_KEY}" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` ```bash title="URL-параметр" -curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" + +curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" ``` -Получить ключ: [dashboard.fastnear.com](https://dashboard.fastnear.com). Операционный режим для неинтерактивных сред: [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) — ключи должны жить в переменных окружения или менеджере секретов, а не в браузерном хранилище, логах чатов или промптах. Полный сценарий и детали заголовков: [Аутентификация и доступ](https://docs.fastnear.com/ru/auth). +Получите API-ключ в [FastNear Dashboard](https://dashboard.fastnear.com). Операционный режим для неинтерактивных сред: [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) — ключи должны жить в переменных окружения или менеджере секретов, а не в браузерном хранилище, логах чатов или промптах. Полный сценарий и детали заголовков: [Аутентификация и доступ](https://docs.fastnear.com/ru/auth). ## Как вынимать чистую документацию в промпт @@ -287,7 +292,7 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" Агенты должны аутентифицироваться в FastNear так же, как это делают продовые бэкенды. Не переносите браузерно-демонстрационный режим из UI документации в агента, воркера или среду автоматизации. -Один API-ключ FastNear работает во всех RPC и API-эндпоинтах. Многие публичные чтения работают и без ключа. Для агента важнее не само наличие аутентификации, а где живёт учётная запись, как она прикрепляется к запросам и как не допустить её утечки в промпты, логи или состояние браузера. +Один API-ключ FastNear работает во всех RPC и API-эндпоинтах. Для агента важнее не само наличие аутентификации, а где живёт учётная запись, как она прикрепляется к запросам и как не допустить её утечки в промпты, логи или состояние браузера. ## Если нужно только правило @@ -311,8 +316,8 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" | Способ | Используйте, когда... | Заметки | | --- | --- | --- | -| `Authorization: Bearer ${API_KEY}` | HTTP-клиентом или бэкендом управляете вы | Лучшее значение по умолчанию для агентов. Меньше шансов утечь в логи URL, аналитику или скопированные ссылки. | -| `?apiKey=${API_KEY}` | используется простой curl или система, которой сложно выставлять заголовки | Тоже допустимо, но URL обычно дальше путешествуют через логи и инструменты. Применяйте осознанно. | +| `Authorization: Bearer ${FASTNEAR_API_KEY}` | HTTP-клиентом или бэкендом управляете вы | Лучшее значение по умолчанию для агентов. Меньше шансов утечь в логи URL, аналитику или скопированные ссылки. | +| `?apiKey=${FASTNEAR_API_KEY}` | используется простой curl или система, которой сложно выставлять заголовки | Тоже допустимо, но URL обычно дальше путешествуют через логи и инструменты. Применяйте осознанно. | Если есть выбор — используйте заголовочную форму. @@ -343,13 +348,13 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { }); ``` -## Когда аутентификации не хватает +## Если в среде выполнения нет ключа -Многие публичные эндпоинты FastNear по-прежнему доступны на чтение без ключа. Если агент может ответить на вопрос пользователя через публичный трафик — делайте так. +Агенту по умолчанию стоит стартовать с настроенным API-ключом FastNear. Некоторые публичные чтения могут сработать и без него, но это не должно быть базовой рабочей моделью. -Когда ключ нужен для повышенных лимитов, платного доступа или аутентифицированного трафика: +Если в настроенной среде ключа пока нет: -- подскажите пользователю создать или забрать ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com) +- подскажите пользователю создать или забрать ключ в [FastNear Dashboard](https://dashboard.fastnear.com) - попросите настроить его в переменной окружения, менеджере секретов или конфигурации бэкенда - не просите вставлять сырой ключ в чат, чтобы агент «носил» его с собой @@ -996,15 +1001,19 @@ https://test.api.fastnear.com ## Примеры +Все shell-примеры ниже работают на публичных FastNear API-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ### Свести один аккаунт за один вызов `/v1/account/{id}/full` — это агрегатор аккаунтов в FastNear API: один вызов собирает NEAR-состояние аккаунта, каждый FT-контракт, которого он касался, каждую NFT-коллекцию, которую он получил, и каждый валидаторский пул, в который делегировал. Если у вас уже есть `account_id`, это самый быстрый ответ на вопрос «что это за аккаунт?». ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + "${AUTH_HEADER[@]}" \ | jq '{ account_id, near_balance_yocto: .state.balance, @@ -1014,23 +1023,26 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -Для `mike.near`: 40 FT-контрактов в списке, 40 NFT-коллекций, 5 валидаторских пулов. Одни только счётчики контрактов говорят, что это активный mainnet-аккаунт. Все примеры ниже погружаются в какую-то одну из этих поверхностей — начинайте отсюда, когда на руках только ID аккаунта. +Для `root.near`: 150 FT-контрактов в списке, 102 NFT-коллекции, 2 валидаторских пула. Одни только счётчики контрактов говорят, что это оживлённый mainnet-аккаунт. Все примеры ниже погружаются в какую-то одну из этих поверхностей — начинайте отсюда, когда на руках только ID аккаунта. ### Определить аккаунт по публичному ключу и сразу получить сводку Найдите, какому аккаунту принадлежит ключ, и прочитайте его активы за один следующий запрос. ```bash -API_BASE_URL=https://api.fastnear.com PUBLIC_KEY='ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT' +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -LOOKUP="$(curl -s "$API_BASE_URL/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')")" +LOOKUP="$(curl -s "https://api.fastnear.com/v1/public_key/$(jq -rn --arg k "$PUBLIC_KEY" '$k | @uri')" \ + "${AUTH_HEADER[@]}")" echo "$LOOKUP" | jq '{matched: (.account_ids | length), account_ids}' ACCOUNT_ID="$(echo "$LOOKUP" | jq -r '.account_ids[0]')" -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + "${AUTH_HEADER[@]}" \ | jq '{account_id, state, tokens: (.tokens|length), nfts: (.nfts|length), pools: (.pools|length)}' ``` @@ -1041,10 +1053,12 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ Состояние аккаунта NEAR делится на три ведра, которые UI кошельков обычно сливает в одно: `balance` — это свободная часть (не в стейкинге), `locked` — NEAR, привязанный к валидаторскому стейку или lockup-контракту, а `storage_bytes` подразумевает ещё отдельную долю, пришпиленную к trie по текущей ставке 10^19 yoctoNEAR за байт. Один pipeline над `/full` разводит их по полкам. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + "${AUTH_HEADER[@]}" \ | jq ' (.state.balance | tonumber) as $amount | (.state.locked | tonumber) as $locked @@ -1062,7 +1076,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -Для `mike.near`: ~2613.49 NEAR всего, всё в свободной части, ~5.58 NEAR закреплено за 558 КБ on-chain-состояния, ~2607.91 NEAR доступно к переводу. Новым аккаунтам это особенно заметно — свежесозданный именованный аккаунт ~182 байта «съедает» ~0.00182 NEAR под storage, и именно поэтому CLI-утилиты не дают отправить полный баланс. +Для `root.near`: ~3914.67 NEAR всего, всё в свободной части, ~0.28677 NEAR закреплено за 28,677 байтами on-chain-состояния, ~3914.38 NEAR доступно к переводу. Новым аккаунтам это особенно заметно — свежесозданный именованный аккаунт ~182 байта «съедает» ~0.00182 NEAR под storage, и именно поэтому CLI-утилиты не дают отправить полный баланс. Наведите тот же pipeline на валидаторский пул вроде `astro-stakers.poolv1.near`, и пропорции перевернутся: ~730 тыс. свободных, ~27.68 млн в `locked`. Этот `locked` — собственный протокольный валидаторский стейк пула, а не средства делегатов (те учитываются внутри состояния контракта пула). Одно и то же поле означает разное на разных типах аккаунтов. @@ -1073,10 +1087,12 @@ jq считает в IEEE-754 double, поэтому NEAR-значения вы У каждой записи в массивах `tokens`, `nfts` и `pools` внутри `/full` есть собственное `last_update_block_height` — блок, в котором индексер последний раз видел изменение этой строки для этого аккаунта. Максимум по всем трём массивам даёт дешёвый сигнал «последняя активность» без похода в Transactions API. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/full" \ + "${AUTH_HEADER[@]}" \ | jq ' [ (.tokens // [])[].last_update_block_height, @@ -1093,20 +1109,22 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/full" \ }' ``` -Для `mike.near` относительно текущего tip это возвращает 85 записей по FT-, NFT- и pool-контрактам, 34 с отслеживаемым блоком и самый свежий блок `194711866` — примерно 125 тыс. блоков назад, или около 35 часов при темпе NEAR ~1 блок/сек. Для `root.near`: 254 записи, 158 отслеживаемых. +Для `root.near` это возвращает 254 записи по FT-, NFT- и pool-контрактам, 158 с отслеживаемым блоком и самый свежий блок `194301659`. Этого уже достаточно, чтобы понять, что кошелёк живой, не заходя в историю транзакций. -Это правильный вопрос для «заброшен ли этот кошелёк?» или «двигалось ли что-то после блока X?» — дёшево, один запрос, без истории транзакций. Чтобы достать саму транзакцию, вызвавшую последнее изменение, расширяйте поверхность до [Transactions API](https://docs.fastnear.com/ru/tx). Записи с `last_update_block_height: null` относятся ко времени до per-row-отслеживания индексером (обычно старые airdrops) и здесь игнорируются, а не считаются свежими. +Это правильный вопрос для «был ли этот кошелёк недавно активен?» или «двигалось ли что-то после блока X?» — дёшево, один запрос, без истории транзакций. Чтобы достать саму транзакцию, вызвавшую последнее изменение, расширяйте поверхность до [Transactions API](https://docs.fastnear.com/ru/tx). Записи с `last_update_block_height: null` относятся ко времени до per-row-отслеживания индексером (обычно старые airdrops) и здесь игнорируются, а не считаются свежими. ### Показать NFT-коллекции этого кошелька от конкретного издателя Имена аккаунтов на NEAR кодируют иерархию: `mint.sharddog.near` — это подаккаунт `sharddog.near`, который, в свою очередь, — подаккаунт `near`. Издатели, выпускающие несколько NFT-коллекций, обычно разворачивают каждую как отдельный подаккаунт, поэтому один фильтр по суффиксу над NFT-списком аккаунта вытаскивает всё опубликованное под одним деревом — без внешнего реестра коллекций. ```bash -API_BASE_URL=https://api.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near PUBLISHER=sharddog.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ +curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/nft" \ + "${AUTH_HEADER[@]}" \ | jq --arg publisher "$PUBLISHER" ' ("." + $publisher) as $suffix | { @@ -1124,7 +1142,7 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ }' ``` -Для `mike.near` и `sharddog.near` это возвращает четыре контракта-подаккаунта: `comic`, `mintv2`, `mint` и `claim`. Два с ненулевым `last_update_block_height` (`mint` на `115715361` и `claim` на `119718026`) — те, где позиция кошелька действительно менялась. Два других — спящие, типично для одноразовых drop-контрактов, в которые аккаунт что-то получил и больше не возвращался. +Для `root.near` и `sharddog.near` это возвращает четыре контракта-подаккаунта: `ndcconstellationnft`, `mint`, `harvestmoon` и `claim`. Только у `claim` есть ненулевой `last_update_block_height` (`131402024`), так что именно этот контракт явно менял позицию кошелька. Остальные — спящие, что типично для одноразовых drop-контрактов, в которые аккаунт что-то получил и больше не возвращался. Поменяйте `PUBLISHER` на любой аккаунт, чтобы сфокусировать фильтр на другом дереве издателя. @@ -1133,12 +1151,15 @@ curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/nft" \ Прямые позиции в пулах лежат на `/staking`; liquid staking-токены (stNEAR, LiNEAR и т. п.) лежат на `/ft` как обычные FT. Прочитайте оба эндпоинта и классифицируйте кошелёк — `root.near` оказывается `mixed`. ```bash -API_BASE_URL=https://api.fastnear.com ACCOUNT_ID=root.near LIQUID_PROVIDERS_JSON='["meta-pool.near","lst.rhealab.near","linear-protocol.near"]' +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -STAKING="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/staking")" -FT="$(curl -s "$API_BASE_URL/v1/account/$ACCOUNT_ID/ft")" +STAKING="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/staking" \ + "${AUTH_HEADER[@]}")" +FT="$(curl -s "https://api.fastnear.com/v1/account/$ACCOUNT_ID/ft" \ + "${AUTH_HEADER[@]}")" jq -n \ --argjson staking "$STAKING" \ @@ -1147,11 +1168,12 @@ jq -n \ ($staking.pools // []) as $direct | (($ft.tokens // []) | map(select(.contract_id as $id | $providers | index($id)))) as $liquid | { - classification: + classification: ( if ($direct|length)>0 and ($liquid|length)>0 then "mixed" elif ($direct|length)>0 then "direct_only" elif ($liquid|length)>0 then "liquid_only" - else "no_visible_staking_position" end, + else "no_visible_staking_position" end + ), direct_pools: ($direct | map(.pool_id)), liquid_tokens: ($liquid | map({contract_id, balance})) }' @@ -1243,19 +1265,21 @@ jq -n \ # Аутентификация и доступ -Один API-ключ FastNear работает и для [RPC](https://docs.fastnear.com/ru/rpc), и для [API-эндпоинтов](https://docs.fastnear.com/ru/api). Многие публичные чтения работают и без него, но когда ключ нужен, модель остаётся простой: используйте один и тот же ключ везде и передавайте его либо через заголовок `Authorization: Bearer`, либо как URL-параметр `?apiKey=`. +Один API-ключ FastNear работает и для [RPC](https://docs.fastnear.com/ru/rpc), и для [API-эндпоинтов](https://docs.fastnear.com/ru/api). Держите модель аутентификации простой: используйте один и тот же ключ везде и передавайте его либо через заголовок `Authorization: Bearer`, либо как URL-параметр `?apiKey=`. Та же модель действует и на обычных, и на архивных RPC-хостах. Хранение ключа в браузере для UI документации — это удобство документации, а не продовый шаблон. -Войдите на [dashboard.fastnear.com](https://dashboard.fastnear.com), чтобы получить ключ, и отправляйте его в каждом запросе одним из способов ниже. +Получите ключ в [FastNear Dashboard](https://dashboard.fastnear.com) и отправляйте его в каждом запросе одним из способов ниже. Страницы с интерактивными примерами также поддерживают `Copy example URL`, чтобы делиться уже заполненными запросами. Общие URL примеров выполняются автоматически при загрузке, когда в них есть состояние операции, а сохранённые API-ключи и токены никогда не включаются в такие общедоступные URL документации. ## Через заголовок Authorization ```bash +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" + curl "https://rpc.mainnet.fastnear.com" \ - -H "Authorization: Bearer ${API_KEY}" \ + -H "Authorization: Bearer $FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` @@ -1263,7 +1287,9 @@ curl "https://rpc.mainnet.fastnear.com" \ ## Через URL-параметр `?apiKey=` ```bash -curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" \ +: "${FASTNEAR_API_KEY:?Задайте FASTNEAR_API_KEY в окружении перед запуском примера.}" + +curl "https://rpc.mainnet.fastnear.com?apiKey=$FASTNEAR_API_KEY" \ -H "Content-Type: application/json" \ --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' ``` @@ -1296,14 +1322,16 @@ https://kv.test.fastnear.com Если вы уже знаете один точный ключ, начните с последней индексированной строки и остановитесь, как только она ответит на вопрос. ```bash -KV_BASE_URL=https://kv.main.fastnear.com CURRENT_ACCOUNT_ID=social.near PREDECESSOR_ID=james.near KEY='graph/follow/sleet.near' +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" -curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ +curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{ latest: ( .entries[0] @@ -1394,17 +1422,57 @@ curl -s "$KV_BASE_URL/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY **Источник:** [https://docs.fastnear.com/ru/fastdata/kv/examples](https://docs.fastnear.com/ru/fastdata/kv/examples) -## Пример +## Примеры + +Все shell-примеры ниже работают на публичных KV FastData-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + +### Проверить один точный ключ и сразу посмотреть его историю + +Если контракт, `predecessor_id` и точный ключ уже известны, начинайте с узкого запроса. `latest` отвечает на вопрос о текущем состоянии, а `history` показывает, менялась ли именно эта строка со временем. + +```bash +CURRENT_ACCOUNT_ID=social.near +PREDECESSOR_ID=james.near +KEY='graph/follow/sleet.near' +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + +ENCODED_KEY="$(jq -rn --arg key "$KEY" '$key | @uri')" + +LATEST="$(curl -s "https://kv.main.fastnear.com/v0/latest/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + "${AUTH_HEADER[@]}")" + +echo "$LATEST" | jq '{ + latest: ( + .entries[0] + | { + current_account_id, + predecessor_id, + block_height, + key, + value + } + ) +}' + +curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + "${AUTH_HEADER[@]}" \ + | jq '{writes: [.entries[] | {block_height, value}]}' +``` + +Для точного ключа вроде этого follow-edge `latest` даёт текущее индексированное значение одной строкой, а `history` показывает, была ли запись однократной или переключалась со временем. Начинайте отсюда, когда путь в storage уже известен; расширяйтесь до выборок по `predecessor_id` только тогда, когда нужно не доказательство, а поиск. ### Посмотреть индексированные записи одного `predecessor_id` и сузиться до изменившегося ключа `all-by-predecessor` возвращает последние индексированные записи одного аккаунта по каждому контракту, которого он касался. Выберите интересный ключ и прогоните его через `history`, чтобы увидеть, как эта строка менялась со временем. ```bash -KV_BASE_URL=https://kv.main.fastnear.com PREDECESSOR_ID=jemartel.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -FIRST="$(curl -s "$KV_BASE_URL/v0/all/$PREDECESSOR_ID" \ +FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data '{"include_metadata":true,"limit":10}')" @@ -1419,11 +1487,20 @@ echo "$FIRST" | jq '{ Поднимите самую свежую строку и прогоните её через `history`: ```bash +PREDECESSOR_ID=jemartel.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +FIRST="$(curl -s "https://kv.main.fastnear.com/v0/all/$PREDECESSOR_ID" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data '{"include_metadata":true,"limit":10}')" + CURRENT_ACCOUNT_ID="$(echo "$FIRST" | jq -r '.entries[0].current_account_id')" EXACT_KEY="$(echo "$FIRST" | jq -r '.entries[0].key')" ENCODED_KEY="$(jq -rn --arg key "$EXACT_KEY" '$key | @uri')" -curl -s "$KV_BASE_URL/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ +curl -s "https://kv.main.fastnear.com/v0/history/$CURRENT_ACCOUNT_ID/$PREDECESSOR_ID/$ENCODED_KEY" \ + "${AUTH_HEADER[@]}" \ | jq '{entries: [.entries[] | {block_height, value}]}' ``` @@ -1785,14 +1862,18 @@ https://testnet.neardata.xyz NEAR Data возвращает каждый блок полностью гидратированным одним JSON-документом — header плюс per-shard chunks, receipts, результаты исполнения и state changes, — так что один `curl` уже даёт всё необходимое, чтобы отфильтровать нужный контракт без второго запроса. +Все shell-примеры ниже работают на публичных NEAR Data-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ### На каком блоке NEAR сейчас? `/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Прежде чем фильтровать по конкретному контракту, полезно увидеть, как выглядит один блок на уровне протокола: транзакции приходят с разбивкой по shard, поэтому общее число транзакций в блоке — это сумма по shards, а не одно поле верхнего уровня. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ +curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + "${AUTH_HEADER[@]}" \ | jq '{ height: .block.header.height, timestamp_nanosec: .block.header.timestamp_nanosec, @@ -1808,10 +1889,12 @@ curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ `/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Контракт может проявиться либо в `transactions` chunk (когда он `receiver_id`), либо в `receipts` (когда прилетает cross-shard-вызов), поэтому один проход jq по shards покрывает оба случая. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ +curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + "${AUTH_HEADER[@]}" \ | jq --arg contract "$TARGET_CONTRACT" '{ height: .block.header.height, contract: $contract, @@ -1832,8 +1915,9 @@ curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ Optimistic-блоки ходят по `/v0/block_opt/{height}` примерно на секунду впереди `/v0/block/{height}`. Цикл мониторинга может действовать по optimistic-сигналу и ожидать, что тот же ответ придёт на финализированный эндпоинт через один блок — если только стресс сети не расширит разрыв, и тогда финализированный fetch вернёт `null`, а вы подождёте. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi count_touches() { jq --arg contract "$1" ' @@ -1844,13 +1928,15 @@ count_touches() { } OPT_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + curl -s -D - -o /dev/null "${AUTH_HEADER[@]}" "https://mainnet.neardata.xyz/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" OPT_HEIGHT="${OPT_LOCATION##*/}" -echo "optimistic @ $OPT_HEIGHT: $(curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | count_touches "$TARGET_CONTRACT") touches" -FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" +echo "optimistic @ $OPT_HEIGHT: $(curl -s "https://mainnet.neardata.xyz$OPT_LOCATION" \ + "${AUTH_HEADER[@]}" | count_touches "$TARGET_CONTRACT") touches" +FINAL="$(curl -s "https://mainnet.neardata.xyz/v0/block/$OPT_HEIGHT" \ + "${AUTH_HEADER[@]}")" if [ "$(printf '%s' "$FINAL" | jq 'type')" = '"null"' ]; then echo "finalized @ $OPT_HEIGHT: not caught up yet" else @@ -1865,16 +1951,19 @@ fi В большинстве финализированных блоков нет мутации состояния ни для одного конкретного контракта — активность разрежена и привязана к shard. Идите назад от финализированной головы, пока состояние контракта реально не изменится, и откройте этот shard для payload мутации. Вызов уровня блока говорит, *на каком* shard это случилось; вызов уровня shard — *как*. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" +HEAD="$(curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + "${AUTH_HEADER[@]}" | jq '.block.header.height')" FOUND_HEIGHT="" FOUND_SHARD="" for OFFSET in $(seq 0 15); do H=$((HEAD - OFFSET)) - SHARD="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" \ + SHARD="$(curl -s "https://mainnet.neardata.xyz/v0/block/$H" \ + "${AUTH_HEADER[@]}" \ | jq -r --arg contract "$TARGET_CONTRACT" ' .shards[] | select([.state_changes[]? | select(.change.account_id? == $contract)] | length > 0) @@ -1889,7 +1978,8 @@ done if [ -z "$FOUND_HEIGHT" ]; then echo "no state mutation for $TARGET_CONTRACT in the last 16 finalized blocks" else - curl -s "$NEARDATA_BASE_URL/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + curl -s "https://mainnet.neardata.xyz/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + "${AUTH_HEADER[@]}" \ | jq --arg contract "$TARGET_CONTRACT" --argjson height "$FOUND_HEIGHT" --argjson shard_id "$FOUND_SHARD" '{ height: $height, shard_id: $shard_id, @@ -2068,6 +2158,8 @@ https://archival-rpc.testnet.fastnear.com Начинайте с RPC-метода, который отвечает на вопрос. Используйте `tx`, чтобы отследить включение и финальность по хешу транзакции, и расширяйте поверхность только когда нужны дерево receipts, сырой state или трассировка на уровне shard. +Все shell-примеры ниже работают на публичных RPC-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ## Состояние аккаунта ### Показать баланс и storage аккаунта на finality @@ -2075,10 +2167,12 @@ https://archival-rpc.testnet.fastnear.com `view_account` — канонический RPC-запрос для текущего состояния аккаунта. Один вызов возвращает свободный баланс, сумму, заблокированную в валидаторском стейке или lockup-контракте, использованное storage и блок, на котором было сделано чтение. `finality: "final"` гарантирует, что вы читаете стабильное состояние, а не optimistic-представление. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$RPC_URL" \ +curl -s "https://rpc.mainnet.fastnear.com" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -2087,7 +2181,7 @@ curl -s "$RPC_URL" \ | jq '.result | {amount, locked, storage_usage, block_height, block_hash}' ``` -Для `mike.near` это возвращает `amount` (yoctoNEAR в свободной части), `locked: "0"` (ничего в валидаторском стейке или lockup-контракте) и `storage_usage: 558441` — 558 КБ on-chain-состояния. Пара `block_height`/`block_hash` фиксирует точку чтения; чтобы прочитать несколько аккаунтов *на одном и том же* блоке, переиспользуйте возвращённый `block_hash` как `block_id` в последующих запросах. +Для `root.near` это возвращает `amount` (yoctoNEAR в свободной части), `locked: "0"` (ничего в валидаторском стейке или lockup-контракте) и `storage_usage: 28677` — примерно 28.7 КБ on-chain-состояния. Пара `block_height`/`block_hash` фиксирует точку чтения; чтобы прочитать несколько аккаунтов *на одном и том же* блоке, переиспользуйте возвращённый `block_hash` как `block_id` в последующих запросах. ## Включение транзакции и финальность @@ -2096,11 +2190,13 @@ curl -s "$RPC_URL" \ Есть tx hash? Опрашивайте `tx` с минимальным порогом `wait_until`, который отвечает на ваш вопрос. ```bash -RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow SIGNER_ACCOUNT_ID=mike.testnet +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$RPC_URL" \ +curl -s "https://archival-rpc.testnet.fastnear.com" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" --arg signer_id "$SIGNER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "tx", @@ -2128,14 +2224,15 @@ curl -s "$RPC_URL" \ Блок NEAR — это header поверх N shard chunks, а не плоский список транзакций. `block` возвращает headers chunks; сами транзакции лежат уровнем ниже, внутри `chunk`. Шортката `block → tx` нет — блок не несёт хешей транзакций, поэтому `tx` (которому нужен hash) в этой цепочке не участвует. Канонический проход — `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com EMPTY_TX_ROOT=11111111111111111111111111111111 +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -BLOCK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +BLOCK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ | jq -r '.result.sync_info.latest_block_hash')" -CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +CHUNK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg block_hash "$BLOCK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"block",params:{block_id:$block_hash} }')" \ @@ -2145,7 +2242,7 @@ CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ if [ -z "$CHUNK_HASH" ]; then echo "tip block had no transactions in any chunk — rerun on the next head" else - curl -s "$RPC_URL" -H 'content-type: application/json' \ + curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg chunk_hash "$CHUNK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"chunk",params:{chunk_id:$chunk_hash} }')" \ @@ -2183,11 +2280,13 @@ fi Любой ключ с `tx_count: 0` был создан и ни разу не использовался — самый очевидный кандидат на очистку. Следующий по порядку — ключи, заскоупленные на контракт, с которым вы больше не работаете. Фильтр ниже сужает до `social.near`, но чтобы аудитировать другой контракт, меняется только строка `RECEIVER_ID`. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near RECEIVER_ID=social.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$RPC_URL" \ +curl -s "https://rpc.mainnet.fastnear.com" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -2211,7 +2310,7 @@ curl -s "$RPC_URL" \ }' ``` -Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на удаление. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность одного dapp. +Для `root.near` это возвращает 235 ключей всего, включая 34 function-call-ключа на `social.near`; 21 из них были созданы и ни разу не использовались (`tx_count: 0`) и потому являются прямыми кандидатами на удаление. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность одного dapp. Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом (function-call-ключ не может авторизовать `DeleteKey`) и отправьте через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Повторный запуск того же запроса подтвердит, что ключа больше нет. @@ -2224,10 +2323,11 @@ View-метод вроде `get_num` всё равно заставляет уз Контракты на `near-sdk-rs` хранят верхнеуровневую `#[near_bindgen]`-структуру под ключом `STATE`. Передайте `STATE` как `prefix_base64` (`U1RBVEU=` — это base64 тех же четырёх ASCII-байт), и узел вернёт сериализованное значение. ```bash -RPC_URL=https://rpc.testnet.fastnear.com CONTRACT_ID=counter.near-examples.testnet +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +RAW_B64="$(curl -s "https://rpc.testnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"view_state",account_id:$contract,prefix_base64:"U1RBVEU=",finality:"final"} @@ -2252,13 +2352,14 @@ jq -n --arg raw "$RAW_B64" --argjson val "$DECODED_I8" '{raw_bytes_base64: $raw, `social.near` знает две вещи, о которых UI кошелька может только догадываться: сколько storage осталось у каждого аккаунта и разрешена ли делегированному signer запись под этим аккаунтом. Два view-вызова сворачивают вопрос готовности к одному boolean. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near # account you're writing under -SIGNER_ACCOUNT_ID=mike.near # account signing the transaction +ACCOUNT_ID=root.near # account you're writing under +SIGNER_ACCOUNT_ID=root.near # account signing the transaction +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -STORAGE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +STORAGE="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$STORAGE_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"get_account_storage",args_base64:$args,finality:"final"} @@ -2269,7 +2370,7 @@ if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then PERMISSION=true else PERM_ARGS_B64="$(jq -nc --arg pred "$SIGNER_ACCOUNT_ID" --arg key "$ACCOUNT_ID" '{predecessor_id:$pred,key:$key}' | base64 | tr -d '\n')" - PERMISSION="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + PERMISSION="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$PERM_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"is_write_permission_granted",args_base64:$args,finality:"final"} @@ -2287,23 +2388,24 @@ jq -n --argjson storage "$STORAGE" --argjson permission "$PERMISSION" \ }' ``` -Для `mike.near`, подписывающего под собой, это возвращает `storage: {used_bytes: 139803, available_bytes: 83891}`, `permission_granted: true` (владельческая запись) и `ready_to_publish: true`. Если `storage` приходит как `null` или `available_bytes: 0`, аккаунту нужен `storage_deposit` на `social.near`, прежде чем новая запись сможет закрепиться. Если signer отличается от цели, ветка permission спрашивает `is_write_permission_granted({predecessor_id, key})` — тот же on-chain-ответ, который dapp видит, прежде чем писать от имени пользователя. Полную поверхность контракта см. в [SocialDB API](https://github.com/NearSocial/social-db#api). +Для `root.near`, подписывающего под собой, это возвращает `storage: {used_bytes: 136245, available_bytes: 42484}`, `permission_granted: true` (владельческая запись) и `ready_to_publish: true`. Если `storage` приходит как `null` или `available_bytes: 0`, аккаунту нужен `storage_deposit` на `social.near`, прежде чем новая запись сможет закрепиться. Если signer отличается от цели, ветка permission спрашивает `is_write_permission_granted({predecessor_id, key})` — тот же on-chain-ответ, который dapp видит, прежде чем писать от имени пользователя. Полную поверхность контракта см. в [SocialDB API](https://github.com/NearSocial/social-db#api). ### Что `mob.near/widget/Profile` содержит прямо сейчас? SocialDB хранит BOS-виджеты как ключи `/widget/` на `social.near`. Один `keys` с типом возврата `BlockHeight` возвращает каталог плюс якоря последней записи по каждому виджету; один `get` возвращает точный исходник. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com ACCOUNT_ID=mob.near WIDGET_NAME=Profile +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi KEYS_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" '{ keys: [($account_id + "/widget/*")], options: {return_type: "BlockHeight"} }' | base64 | tr -d '\n')" -curl -s "$RPC_URL" -H 'content-type: application/json' \ +curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$KEYS_ARGS" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"keys",args_base64:$args,finality:"final"} @@ -2320,7 +2422,7 @@ GET_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" '{ keys: [($account_id + "/widget/" + $widget)] }' | base64 | tr -d '\n')" -curl -s "$RPC_URL" -H 'content-type: application/json' \ +curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$GET_ARGS" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"get",args_base64:$args,finality:"final"} @@ -2793,18 +2895,48 @@ https://transfers.main.fastnear.com **Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) -## Пример +## Примеры + +Эти shell-примеры работают и на публичных Transfers и Transactions endpoint-ах. Если `FASTNEAR_API_KEY` уже задан в окружении, сниппеты автоматически пробросят его как bearer-заголовок. + +### Какая у этого аккаунта свежая активность по переводам? + +`/v0/transfers` всего с `account_id` и `desc: true` возвращает самые свежие переводы, касающиеся этого аккаунта, по всем типам активов, в обоих направлениях сразу. В каждой строке уже есть `human_amount`, `asset_id` и `transaction_id`, так что этот поток заодно служит быстрым сканом активности до того, как вы достанете фильтры. + +```bash +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + +curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id: $account_id, desc: true, limit: 5}')" \ + | jq '{ + recent: [.transfers[] | { + block_height, + asset_id, + human_amount, + other_account_id, + transfer_type, + tx: .transaction_id + }] + }' +``` + +Для `root.near` последние строки смешивают активы `FtTransfer` и `MtTransfer`. `asset_id` использует URI по NEP-стандартам (`native:near`, `nep141:...`, `nep245:...`), так что одно поле уже подсказывает, к какому стандарту тянуться дальше. Положительный `human_amount` означает, что аккаунт получил; отрицательный — что отправил. `other_account_id: null` — норма для multi-token-форм, где контрагент сидит внутри границы контракта, а не как отдельный аккаунт верхнего уровня. ### Отфильтровать и листать ленту переводов одного аккаунта `/v0/transfers` возвращает отфильтрованную ленту плюс `resume_token`, который вы переиспользуете *без изменения фильтров*, чтобы продолжать листать. В каждой строке уже есть `human_amount`, `usd_amount`, `transaction_id` и `receipt_id` — большинство audit-вопросов закрываются без второго запроса. ```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -FEED="$(curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ +FEED="$(curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ account_id: $account_id, @@ -2826,15 +2958,30 @@ echo "$FEED" | jq '{ Когда одной строке нужна точка исполнения, возьмите её `receipt_id` и сразу обратитесь к `/v0/receipt`: ```bash +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +FEED="$(curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + direction: "receiver", + asset_id: "native:near", + min_amount: "1000000000000000000000000", + desc: true, + limit: 10 + }')")" RECEIPT_ID="$(echo "$FEED" | jq -r '.transfers[0].receipt_id')" -curl -s "$TX_BASE_URL/v0/receipt" \ +curl -s "https://tx.main.fastnear.com/v0/receipt" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ | jq '.receipt | {receipt_id, transaction_hash, receiver_id, predecessor_id, tx_block_height, is_success}' ``` -Это тот же переход, что описан в [Превратить один неказистый receipt ID из логов в человекочитаемую историю](https://docs.fastnear.com/ru/tx/examples#%D0%BF%D1%80%D0%B5%D0%B2%D1%80%D0%B0%D1%82%D0%B8%D1%82%D1%8C-%D0%BE%D0%B4%D0%B8%D0%BD-%D0%BD%D0%B5%D0%BA%D0%B0%D0%B7%D0%B8%D1%81%D1%82%D1%8B%D0%B9-receipt-id-%D0%B8%D0%B7-%D0%BB%D0%BE%D0%B3%D0%BE%D0%B2-%D0%B2-%D1%87%D0%B5%D0%BB%D0%BE%D0%B2%D0%B5%D0%BA%D0%BE%D1%87%D0%B8%D1%82%D0%B0%D0%B5%D0%BC%D1%83%D1%8E-%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D1%8E) — один запрос возвращает и квитанцию, и её родительскую транзакцию целиком. +Это тот же переход, что описан в [Превратить один receipt ID в читаемую историю транзакции](https://docs.fastnear.com/ru/tx/examples#receipt-id-to-readable-story) — один запрос возвращает и квитанцию, и её родительскую транзакцию целиком. ## Частые ошибки @@ -2928,15 +3075,19 @@ https://tx.test.fastnear.com ## Начните здесь +Все shell-примеры ниже работают на публичных Transactions API-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ### У меня один хеш транзакции. Что произошло? Вставьте хеш в `POST /v0/transactions` — один ответ обычно содержит всю историю. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp +TX_HASH=7ZKnhzt2MqMNmsk13dV8GAjGu3Db8aHzSBHeNeu9MJCq +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ @@ -2950,18 +3101,20 @@ curl -s "$TX_BASE_URL/v0/transactions" \ }' ``` -Для зафиксированного хеша `mike.near` отправил один `Transfer` на `global-counter.mike.near` в блоке `194263342`, с передачей в receipt `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq`. Если `receipt_count > 1` или следующий вопрос касается поведения на уровне receipt, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие) или [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt). +Для зафиксированного хеша `root.near` отправил один `Transfer` на `escrow.ai.near` в блоке `188976785`, с передачей в receipt `B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1`. Если `receipt_count > 1` или следующий вопрос касается поведения на уровне receipt, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие) или [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt). ### Какой receipt испустил этот лог или событие? Выведите список всех receipt транзакции с логами и флагом, содержат ли их логи ваш фрагмент. Совпадение доказывается, а не угадывается: у зафиксированной транзакции один receipt логирует `Transfer`, другой — `Refund`, и только сторона `Refund` переключается в `true`. ```bash -TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL LOG_FRAGMENT=Refund +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg fragment "$LOG_FRAGMENT" ' @@ -2981,15 +3134,17 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Фрагмент `Refund` атрибутируется receipt `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` на `wrap.near`, метод `ft_resolve_transfer`. Логи receipt живут на receipts, а не на транзакции, поэтому одного прохода достаточно — более глубокая async-трассировка не нужна. -### Превратить один неказистый receipt ID из логов в человекочитаемую историю +### Превратить один receipt ID в читаемую историю транзакции {#receipt-id-to-readable-story} `POST /v0/receipt` возвращает запись receipt **и** его полную родительскую транзакцию в одном ответе, поэтому единственного запроса хватает на всю историю — дополнительный `/v0/transactions` не нужен. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID=5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq +RECEIPT_ID=B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1 +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/receipt" \ +curl -s "https://tx.main.fastnear.com/v0/receipt" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ | jq '{ @@ -3011,7 +3166,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ }' ``` -Для зафиксированного receipt это возвращает `Action`-receipt от `mike.near` к `global-counter.mike.near`, который успешно выполнился в блоке `194263343`, через один блок после попадания родительской транзакции `AdgNifPY…`, — один `Transfer` (5 NEAR, в сыром `.transaction.transaction.actions` видимо как `5000000000000000000000000` yocto). Если интересным якорем становится родительская транзакция, хеш у вас уже есть — переиспользуйте его в [У меня один хеш транзакции. Что произошло?](#у-меня-один-хеш-транзакции-что-произошло). +Для зафиксированного receipt это возвращает `Action`-receipt от `root.near` к `escrow.ai.near`, который успешно выполнился в блоке `188976786`, через один блок после попадания родительской транзакции `7ZKnhzt2…`, — один `Transfer` (3.5 NEAR, в сыром `.transaction.transaction.actions` видимо как `3500000000000000000000000` yocto). Если интересным якорем становится родительская транзакция, хеш у вас уже есть — переиспользуйте его в [У меня один хеш транзакции. Что произошло?](#у-меня-один-хеш-транзакции-что-произошло). ## Сбои и async @@ -3020,12 +3175,13 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Один batch отправил `CreateAccount → Transfer → AddKey → FunctionCall`, и финальный вызов попал в отсутствующий метод. Индексированная запись транзакции уже несёт упорядоченный batch *и* точный сбой на уровне receipt, поэтому одного запроса хватает, чтобы ответить «что пытались и что сломалось»; проверка через `view_account` затем доказывает, что предыдущие actions откатились. ```bash -TX_BASE_URL=https://tx.test.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.test.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ @@ -3047,7 +3203,12 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Теперь докажите откат предыдущих actions: спросите аккаунт, который batch *пытался* создать: ```bash -curl -s "$RPC_URL" \ +NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + +curl -s "https://rpc.testnet.fastnear.com" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "query", @@ -3063,12 +3224,14 @@ curl -s "$RPC_URL" \ Внешний `execution_outcome.outcome.status` рапортует `SuccessReceiptId`, как только сработал handoff первого receipt, — и ничего не говорит о том, успешны ли дочерние receipts и отработал ли callback на исходном контракте. Один pipeline над `/v0/transactions` отвечает сразу на все три вопроса. ```bash -TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL ORIGIN_CONTRACT_ID=wrap.near CALLBACK_METHOD=ft_resolve_transfer +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ @@ -3106,11 +3269,13 @@ curl -s "$TX_BASE_URL/v0/transactions" \ [OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь подписывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, затем `worker.outlayer.near` присылает результат через `submit_execution_output_and_resolve`. Обе половины несут один и тот же `request_id` — передайте оба tx-хеша в один запрос `/v0/transactions` и извлеките это поле с каждой стороны, чтобы подтвердить пару. ```bash -TX_BASE_URL=https://tx.main.fastnear.com REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg a "$REQUEST_TX" --arg b "$WORKER_TX" '{tx_hashes: [$a, $b]}')" \ | jq '[ @@ -3172,6 +3337,8 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Переходите к Transactions API только тогда, когда вопрос становится историческим: «как Berry Club выглядел в одной более ранней эпохе и какие `draw`-вызовы сделали доску именно такой?» +Эти shell-примеры работают и с публичными RPC и Transactions endpoint-ами. Если `FASTNEAR_API_KEY` уже задан в окружении, FastNear-вызовы автоматически пробросят его как bearer-заголовок. + Карточка живой доски: запрашивает `berryclub.ek.near` `get_lines` через mainnet RPC и рендерит текущую сетку 50x50 в интерфейсе документации. ## 1. Прочитайте живую доску @@ -3180,8 +3347,11 @@ curl -s "$TX_BASE_URL/v0/transactions" \ ```bash ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -sS https://rpc.mainnet.fastnear.com \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "{ \"jsonrpc\": \"2.0\", @@ -3211,7 +3381,11 @@ curl -sS https://rpc.mainnet.fastnear.com \ В этом примере используется узкое окно вокруг блока `97601515`: ```bash +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + curl -sS https://tx.main.fastnear.com/v0/account \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data '{ "account_id": "berryclub.ek.near", @@ -3230,7 +3404,11 @@ curl -sS https://tx.main.fastnear.com/v0/account \ Раскройте кандидатные хеши и оставьте только верхнеуровневые вызовы `draw`: ```bash +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + curl -sS https://tx.main.fastnear.com/v0/transactions \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data '{ "tx_hashes": [ @@ -3281,33 +3459,33 @@ for (const drawTx of drawTransactionsOldestFirst) { Используйте эту страницу только тогда, когда отправная точка — уже читаемое значение SocialDB из `api.near.social`, а следующий вопрос относится к историческому поиску записи. +Эти shell-шаги работают и с публичными endpoint-ами SocialDB и FastNear. Если `FASTNEAR_API_KEY` уже задан в окружении, FastNear-вызовы автоматически пробросят его как bearer-заголовок. + Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос звучит как «какая запись сделала это читаемое значение SocialDB истинным?». -## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` +## Канонический пример: доказать, что `root.near` установил `profile.name` в `Illia` -Используйте этот сценарий, когда читаемый факт уже звучит как «текущее `profile.name` равно `Mike Purvis`», а остаётся вопрос, какая запись сделала это поле истинным. +Используйте этот сценарий, когда читаемый факт уже звучит как «текущее `profile.name` равно `Illia`», а остаётся вопрос, какая запись сделала это поле истинным. Это единственный нюанс SocialDB, который стоит запомнить: для исторического доказательства правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. Для этого живого якоря: -- текущее `profile.name`: `Mike Purvis` -- блок записи SocialDB на уровне поля: `78675795` -- receipt ID: `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN` -- хеш исходной транзакции: `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ` -- внешний блок транзакции: `78675794` +- текущее `profile.name`: `Illia` +- блок записи SocialDB на уровне поля: `75590392` +- receipt ID: `GYvnvBxWA46UGa3aGEkqUBeT7hxhVXk2iZScJFZWU8Se` +- хеш исходной транзакции: `7HtFWv51k5Bispmh1WYPbAVkxr2X4AL6n98DhcQwVw7w` +- внешний блок транзакции: `75590391` ### Shell-сценарий 1. Прочитайте поле из NEAR Social и сохраните блок записи на уровне поля. ```bash -SOCIAL_API_BASE_URL=https://api.near.social -TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near PROFILE_FIELD=profile/name -PROFILE="$(curl -s "$SOCIAL_API_BASE_URL/get" \ +PROFILE="$(curl -s "https://api.near.social/get" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ keys: [($account_id + "/" + $profile_field)], @@ -3326,7 +3504,21 @@ PROFILE_BLOCK_HEIGHT="$(echo "$PROFILE" | jq -r --arg account_id "$ACCOUNT_ID" ' 2. Переиспользуйте этот блок уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. ```bash -BLOCK_RECEIPTS="$(curl -s "$TX_BASE_URL/v0/block" \ +ACCOUNT_ID=root.near +PROFILE_FIELD=profile/name +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +PROFILE_BLOCK_HEIGHT="$( + curl -s "https://api.near.social/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" '.[$account_id].profile.name[":block"]' +)" +BLOCK_RECEIPTS="$(curl -s "https://tx.main.fastnear.com/v0/block" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ block_id: $block_id, @@ -3355,7 +3547,37 @@ PROFILE_TX_HASH="$(echo "$BLOCK_RECEIPTS" | jq -r --arg account_id "$ACCOUNT_ID" 3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. ```bash -curl -s "$TX_BASE_URL/v0/transactions" \ +ACCOUNT_ID=root.near +PROFILE_FIELD=profile/name +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +PROFILE_BLOCK_HEIGHT="$( + curl -s "https://api.near.social/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" '.[$account_id].profile.name[":block"]' +)" +PROFILE_TX_HASH="$( + curl -s "https://tx.main.fastnear.com/v0/block" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg account_id "$ACCOUNT_ID" '{ @@ -3371,8 +3593,8 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | { method_name, profile_name: $profile.name, - description: $profile.description, - tags: ($profile.tags | keys) + image_fields: (($profile.image // {}) | keys), + linktree_keys: (($profile.linktree // {}) | keys) } ) }' @@ -3382,8 +3604,8 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Тот же мост работает и для других читаемых значений SocialDB: -- вариант для связи подписки: `mike.near -> mob.near`, блок `79574924`, tx `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- вариант для исходника виджета: `mob.near/widget/Profile`, блок `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- вариант для связи подписки: `root.near -> mob.near`, блок `79152039`, tx `DvNoqtDrruhmcq7mPpxdFacph2ZCqSzMFF5ZqMRFG78q` +- вариант для исходника виджета: `root.near/widget/Profile`, блок `76029540`, tx `ELS3DrE4Upoc91ZnBh4thVugxCUBAbaLFB4nyKsoyRNP` Ключевая идея не меняется: начните с читаемого значения и его write-block, восстановите receipt `*.near -> social.near` из блока, а затем декодируйте payload `social.near set` из исходной транзакции. @@ -33142,5 +33364,5 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/llms.txt b/static/ru/llms.txt index f973254..bc222fb 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -16,7 +16,7 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Справочник API](https://docs.fastnear.com/ru/api/reference.md): Руководство по маршрутизации между семействами FastNear REST API и их отличия от прямых методов JSON-RPC. - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth.md): Один API-ключ FastNear работает и для RPC, и для REST API — отправляйте его через заголовок Authorization Bearer или как URL-параметр. - [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv.md): Запросы только для чтения «ключ–значение» поверх FastData для сценариев с `predecessor_id`, аккаунтом, ключом и пакетным чтением. -- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Практические примеры KV FastData: scoped-записи, история ключа и переход к точному состоянию. +- [Примеры KV FastData](https://docs.fastnear.com/ru/fastdata/kv/examples.md): Практические примеры KV FastData: точные ключи, scoped-записи, история ключа и переход к точному состоянию. - [NEAR Data API](https://docs.fastnear.com/ru/neardata.md): Недавние чтения по блокам и шардам для мониторинга активности контракта, подтверждения оптимистичных наблюдений и проверки изменений на уровне шарда. - [Справочник RPC](https://docs.fastnear.com/ru/rpc.md): Прямой доступ по JSON-RPC к узлам NEAR от FastNear для запросов состояния, блоков, вызовов контрактов и отправки транзакций. - [Примеры RPC](https://docs.fastnear.com/ru/rpc/examples.md): Практические примеры RPC: проверки состояния, инспекция блоков, чтение контрактов и отправка транзакций. diff --git a/static/ru/neardata.md b/static/ru/neardata.md index ff6be0e..29a87d5 100644 --- a/static/ru/neardata.md +++ b/static/ru/neardata.md @@ -61,5 +61,5 @@ https://testnet.neardata.xyz - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-chunk.md b/static/ru/neardata/block-chunk.md index 7bdeefe..e4e55f7 100644 --- a/static/ru/neardata/block-chunk.md +++ b/static/ru/neardata/block-chunk.md @@ -911,5 +911,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-chunk/index.md b/static/ru/neardata/block-chunk/index.md index 7bdeefe..e4e55f7 100644 --- a/static/ru/neardata/block-chunk/index.md +++ b/static/ru/neardata/block-chunk/index.md @@ -911,5 +911,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-headers.md b/static/ru/neardata/block-headers.md index 263e9f9..bf2fdb0 100644 --- a/static/ru/neardata/block-headers.md +++ b/static/ru/neardata/block-headers.md @@ -256,5 +256,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-headers/index.md b/static/ru/neardata/block-headers/index.md index 263e9f9..bf2fdb0 100644 --- a/static/ru/neardata/block-headers/index.md +++ b/static/ru/neardata/block-headers/index.md @@ -256,5 +256,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-optimistic.md b/static/ru/neardata/block-optimistic.md index f9aae3a..e264df9 100644 --- a/static/ru/neardata/block-optimistic.md +++ b/static/ru/neardata/block-optimistic.md @@ -1856,5 +1856,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-optimistic/index.md b/static/ru/neardata/block-optimistic/index.md index f9aae3a..e264df9 100644 --- a/static/ru/neardata/block-optimistic/index.md +++ b/static/ru/neardata/block-optimistic/index.md @@ -1856,5 +1856,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-shard.md b/static/ru/neardata/block-shard.md index 9577cb8..35b2376 100644 --- a/static/ru/neardata/block-shard.md +++ b/static/ru/neardata/block-shard.md @@ -1635,5 +1635,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block-shard/index.md b/static/ru/neardata/block-shard/index.md index 9577cb8..35b2376 100644 --- a/static/ru/neardata/block-shard/index.md +++ b/static/ru/neardata/block-shard/index.md @@ -1635,5 +1635,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block.md b/static/ru/neardata/block.md index aac69f1..9a05b02 100644 --- a/static/ru/neardata/block.md +++ b/static/ru/neardata/block.md @@ -1856,5 +1856,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/block/index.md b/static/ru/neardata/block/index.md index aac69f1..9a05b02 100644 --- a/static/ru/neardata/block/index.md +++ b/static/ru/neardata/block/index.md @@ -1856,5 +1856,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/examples.md b/static/ru/neardata/examples.md index cb244e0..3639e91 100644 --- a/static/ru/neardata/examples.md +++ b/static/ru/neardata/examples.md @@ -4,14 +4,18 @@ NEAR Data возвращает каждый блок полностью гидратированным одним JSON-документом — header плюс per-shard chunks, receipts, результаты исполнения и state changes, — так что один `curl` уже даёт всё необходимое, чтобы отфильтровать нужный контракт без второго запроса. +Все shell-примеры ниже работают на публичных NEAR Data-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ### На каком блоке NEAR сейчас? `/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Прежде чем фильтровать по конкретному контракту, полезно увидеть, как выглядит один блок на уровне протокола: транзакции приходят с разбивкой по shard, поэтому общее число транзакций в блоке — это сумма по shards, а не одно поле верхнего уровня. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ +curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + "${AUTH_HEADER[@]}" \ | jq '{ height: .block.header.height, timestamp_nanosec: .block.header.timestamp_nanosec, @@ -27,10 +31,12 @@ curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ `/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Контракт может проявиться либо в `transactions` chunk (когда он `receiver_id`), либо в `receipts` (когда прилетает cross-shard-вызов), поэтому один проход jq по shards покрывает оба случая. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ +curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + "${AUTH_HEADER[@]}" \ | jq --arg contract "$TARGET_CONTRACT" '{ height: .block.header.height, contract: $contract, @@ -51,8 +57,9 @@ curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ Optimistic-блоки ходят по `/v0/block_opt/{height}` примерно на секунду впереди `/v0/block/{height}`. Цикл мониторинга может действовать по optimistic-сигналу и ожидать, что тот же ответ придёт на финализированный эндпоинт через один блок — если только стресс сети не расширит разрыв, и тогда финализированный fetch вернёт `null`, а вы подождёте. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi count_touches() { jq --arg contract "$1" ' @@ -63,13 +70,15 @@ count_touches() { } OPT_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + curl -s -D - -o /dev/null "${AUTH_HEADER[@]}" "https://mainnet.neardata.xyz/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" OPT_HEIGHT="${OPT_LOCATION##*/}" -echo "optimistic @ $OPT_HEIGHT: $(curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | count_touches "$TARGET_CONTRACT") touches" -FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" +echo "optimistic @ $OPT_HEIGHT: $(curl -s "https://mainnet.neardata.xyz$OPT_LOCATION" \ + "${AUTH_HEADER[@]}" | count_touches "$TARGET_CONTRACT") touches" +FINAL="$(curl -s "https://mainnet.neardata.xyz/v0/block/$OPT_HEIGHT" \ + "${AUTH_HEADER[@]}")" if [ "$(printf '%s' "$FINAL" | jq 'type')" = '"null"' ]; then echo "finalized @ $OPT_HEIGHT: not caught up yet" else @@ -84,16 +93,19 @@ fi В большинстве финализированных блоков нет мутации состояния ни для одного конкретного контракта — активность разрежена и привязана к shard. Идите назад от финализированной головы, пока состояние контракта реально не изменится, и откройте этот shard для payload мутации. Вызов уровня блока говорит, *на каком* shard это случилось; вызов уровня shard — *как*. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" +HEAD="$(curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + "${AUTH_HEADER[@]}" | jq '.block.header.height')" FOUND_HEIGHT="" FOUND_SHARD="" for OFFSET in $(seq 0 15); do H=$((HEAD - OFFSET)) - SHARD="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" \ + SHARD="$(curl -s "https://mainnet.neardata.xyz/v0/block/$H" \ + "${AUTH_HEADER[@]}" \ | jq -r --arg contract "$TARGET_CONTRACT" ' .shards[] | select([.state_changes[]? | select(.change.account_id? == $contract)] | length > 0) @@ -108,7 +120,8 @@ done if [ -z "$FOUND_HEIGHT" ]; then echo "no state mutation for $TARGET_CONTRACT in the last 16 finalized blocks" else - curl -s "$NEARDATA_BASE_URL/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + curl -s "https://mainnet.neardata.xyz/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + "${AUTH_HEADER[@]}" \ | jq --arg contract "$TARGET_CONTRACT" --argjson height "$FOUND_HEIGHT" --argjson shard_id "$FOUND_SHARD" '{ height: $height, shard_id: $shard_id, @@ -130,5 +143,5 @@ fi - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/examples/index.md b/static/ru/neardata/examples/index.md index cb244e0..3639e91 100644 --- a/static/ru/neardata/examples/index.md +++ b/static/ru/neardata/examples/index.md @@ -4,14 +4,18 @@ NEAR Data возвращает каждый блок полностью гидратированным одним JSON-документом — header плюс per-shard chunks, receipts, результаты исполнения и state changes, — так что один `curl` уже даёт всё необходимое, чтобы отфильтровать нужный контракт без второго запроса. +Все shell-примеры ниже работают на публичных NEAR Data-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ### На каком блоке NEAR сейчас? `/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Прежде чем фильтровать по конкретному контракту, полезно увидеть, как выглядит один блок на уровне протокола: транзакции приходят с разбивкой по shard, поэтому общее число транзакций в блоке — это сумма по shards, а не одно поле верхнего уровня. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ +curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + "${AUTH_HEADER[@]}" \ | jq '{ height: .block.header.height, timestamp_nanosec: .block.header.timestamp_nanosec, @@ -27,10 +31,12 @@ curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ `/v0/last_block/final` отдаёт 302-редирект на текущий финализированный блок. Контракт может проявиться либо в `transactions` chunk (когда он `receiver_id`), либо в `receipts` (когда прилетает cross-shard-вызов), поэтому один проход jq по shards покрывает оба случая. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ +curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + "${AUTH_HEADER[@]}" \ | jq --arg contract "$TARGET_CONTRACT" '{ height: .block.header.height, contract: $contract, @@ -51,8 +57,9 @@ curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" \ Optimistic-блоки ходят по `/v0/block_opt/{height}` примерно на секунду впереди `/v0/block/{height}`. Цикл мониторинга может действовать по optimistic-сигналу и ожидать, что тот же ответ придёт на финализированный эндпоинт через один блок — если только стресс сети не расширит разрыв, и тогда финализированный fetch вернёт `null`, а вы подождёте. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi count_touches() { jq --arg contract "$1" ' @@ -63,13 +70,15 @@ count_touches() { } OPT_LOCATION="$( - curl -s -D - -o /dev/null "$NEARDATA_BASE_URL/v0/last_block/optimistic" \ + curl -s -D - -o /dev/null "${AUTH_HEADER[@]}" "https://mainnet.neardata.xyz/v0/last_block/optimistic" \ | awk 'tolower($1) == "location:" {print $2}' | tr -d '\r' )" OPT_HEIGHT="${OPT_LOCATION##*/}" -echo "optimistic @ $OPT_HEIGHT: $(curl -s "$NEARDATA_BASE_URL$OPT_LOCATION" | count_touches "$TARGET_CONTRACT") touches" -FINAL="$(curl -s "$NEARDATA_BASE_URL/v0/block/$OPT_HEIGHT")" +echo "optimistic @ $OPT_HEIGHT: $(curl -s "https://mainnet.neardata.xyz$OPT_LOCATION" \ + "${AUTH_HEADER[@]}" | count_touches "$TARGET_CONTRACT") touches" +FINAL="$(curl -s "https://mainnet.neardata.xyz/v0/block/$OPT_HEIGHT" \ + "${AUTH_HEADER[@]}")" if [ "$(printf '%s' "$FINAL" | jq 'type')" = '"null"' ]; then echo "finalized @ $OPT_HEIGHT: not caught up yet" else @@ -84,16 +93,19 @@ fi В большинстве финализированных блоков нет мутации состояния ни для одного конкретного контракта — активность разрежена и привязана к shard. Идите назад от финализированной головы, пока состояние контракта реально не изменится, и откройте этот shard для payload мутации. Вызов уровня блока говорит, *на каком* shard это случилось; вызов уровня shard — *как*. ```bash -NEARDATA_BASE_URL=https://mainnet.neardata.xyz TARGET_CONTRACT=intents.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -HEAD="$(curl -sL "$NEARDATA_BASE_URL/v0/last_block/final" | jq '.block.header.height')" +HEAD="$(curl -sL "https://mainnet.neardata.xyz/v0/last_block/final" \ + "${AUTH_HEADER[@]}" | jq '.block.header.height')" FOUND_HEIGHT="" FOUND_SHARD="" for OFFSET in $(seq 0 15); do H=$((HEAD - OFFSET)) - SHARD="$(curl -s "$NEARDATA_BASE_URL/v0/block/$H" \ + SHARD="$(curl -s "https://mainnet.neardata.xyz/v0/block/$H" \ + "${AUTH_HEADER[@]}" \ | jq -r --arg contract "$TARGET_CONTRACT" ' .shards[] | select([.state_changes[]? | select(.change.account_id? == $contract)] | length > 0) @@ -108,7 +120,8 @@ done if [ -z "$FOUND_HEIGHT" ]; then echo "no state mutation for $TARGET_CONTRACT in the last 16 finalized blocks" else - curl -s "$NEARDATA_BASE_URL/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + curl -s "https://mainnet.neardata.xyz/v0/block/$FOUND_HEIGHT/shard/$FOUND_SHARD" \ + "${AUTH_HEADER[@]}" \ | jq --arg contract "$TARGET_CONTRACT" --argjson height "$FOUND_HEIGHT" --argjson shard_id "$FOUND_SHARD" '{ height: $height, shard_id: $shard_id, @@ -130,5 +143,5 @@ fi - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/first-block.md b/static/ru/neardata/first-block.md index 1c67c57..09401f7 100644 --- a/static/ru/neardata/first-block.md +++ b/static/ru/neardata/first-block.md @@ -1848,5 +1848,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/first-block/index.md b/static/ru/neardata/first-block/index.md index 1c67c57..09401f7 100644 --- a/static/ru/neardata/first-block/index.md +++ b/static/ru/neardata/first-block/index.md @@ -1848,5 +1848,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/index.md b/static/ru/neardata/index.md index ff6be0e..29a87d5 100644 --- a/static/ru/neardata/index.md +++ b/static/ru/neardata/index.md @@ -61,5 +61,5 @@ https://testnet.neardata.xyz - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/last-block-final.md b/static/ru/neardata/last-block-final.md index c28cdf6..021b228 100644 --- a/static/ru/neardata/last-block-final.md +++ b/static/ru/neardata/last-block-final.md @@ -1848,5 +1848,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/last-block-final/index.md b/static/ru/neardata/last-block-final/index.md index c28cdf6..021b228 100644 --- a/static/ru/neardata/last-block-final/index.md +++ b/static/ru/neardata/last-block-final/index.md @@ -1848,5 +1848,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/last-block-optimistic.md b/static/ru/neardata/last-block-optimistic.md index daf1b55..7f27b7d 100644 --- a/static/ru/neardata/last-block-optimistic.md +++ b/static/ru/neardata/last-block-optimistic.md @@ -1848,5 +1848,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/last-block-optimistic/index.md b/static/ru/neardata/last-block-optimistic/index.md index daf1b55..7f27b7d 100644 --- a/static/ru/neardata/last-block-optimistic/index.md +++ b/static/ru/neardata/last-block-optimistic/index.md @@ -1848,5 +1848,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/system/health.md b/static/ru/neardata/system/health.md index 84b2878..38e08dd 100644 --- a/static/ru/neardata/system/health.md +++ b/static/ru/neardata/system/health.md @@ -64,5 +64,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/neardata/system/health/index.md b/static/ru/neardata/system/health/index.md index 84b2878..38e08dd 100644 --- a/static/ru/neardata/system/health/index.md +++ b/static/ru/neardata/system/health/index.md @@ -64,5 +64,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/redocly-config.md b/static/ru/redocly-config.md index 5b71076..639f299 100644 --- a/static/ru/redocly-config.md +++ b/static/ru/redocly-config.md @@ -53,5 +53,5 @@ Bearer-токены по-прежнему используют: - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/redocly-config/index.md b/static/ru/redocly-config/index.md index 5b71076..639f299 100644 --- a/static/ru/redocly-config/index.md +++ b/static/ru/redocly-config/index.md @@ -53,5 +53,5 @@ Bearer-токены по-прежнему используют: - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc.md b/static/ru/rpc.md index 36a16ca..7e0b256 100644 --- a/static/ru/rpc.md +++ b/static/ru/rpc.md @@ -85,5 +85,5 @@ https://archival-rpc.testnet.fastnear.com - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/account/view-access-key-list.md b/static/ru/rpc/account/view-access-key-list.md index 8f9a730..4fc29a8 100644 --- a/static/ru/rpc/account/view-access-key-list.md +++ b/static/ru/rpc/account/view-access-key-list.md @@ -242,5 +242,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/account/view-access-key-list/index.md b/static/ru/rpc/account/view-access-key-list/index.md index 8f9a730..4fc29a8 100644 --- a/static/ru/rpc/account/view-access-key-list/index.md +++ b/static/ru/rpc/account/view-access-key-list/index.md @@ -242,5 +242,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/account/view-access-key.md b/static/ru/rpc/account/view-access-key.md index 95c28ff..929e0c4 100644 --- a/static/ru/rpc/account/view-access-key.md +++ b/static/ru/rpc/account/view-access-key.md @@ -260,5 +260,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/account/view-access-key/index.md b/static/ru/rpc/account/view-access-key/index.md index 95c28ff..929e0c4 100644 --- a/static/ru/rpc/account/view-access-key/index.md +++ b/static/ru/rpc/account/view-access-key/index.md @@ -260,5 +260,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/account/view-account.md b/static/ru/rpc/account/view-account.md index 5086c79..8e47621 100644 --- a/static/ru/rpc/account/view-account.md +++ b/static/ru/rpc/account/view-account.md @@ -294,5 +294,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/account/view-account/index.md b/static/ru/rpc/account/view-account/index.md index 5086c79..8e47621 100644 --- a/static/ru/rpc/account/view-account/index.md +++ b/static/ru/rpc/account/view-account/index.md @@ -294,5 +294,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/block/block-by-height.md b/static/ru/rpc/block/block-by-height.md index da8318a..2d381c3 100644 --- a/static/ru/rpc/block/block-by-height.md +++ b/static/ru/rpc/block/block-by-height.md @@ -196,5 +196,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/block/block-by-height/index.md b/static/ru/rpc/block/block-by-height/index.md index da8318a..2d381c3 100644 --- a/static/ru/rpc/block/block-by-height/index.md +++ b/static/ru/rpc/block/block-by-height/index.md @@ -196,5 +196,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/block/block-by-id.md b/static/ru/rpc/block/block-by-id.md index f88118c..587b9fc 100644 --- a/static/ru/rpc/block/block-by-id.md +++ b/static/ru/rpc/block/block-by-id.md @@ -196,5 +196,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/block/block-by-id/index.md b/static/ru/rpc/block/block-by-id/index.md index f88118c..587b9fc 100644 --- a/static/ru/rpc/block/block-by-id/index.md +++ b/static/ru/rpc/block/block-by-id/index.md @@ -196,5 +196,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/block/block-effects.md b/static/ru/rpc/block/block-effects.md index 039b7ae..9f7963d 100644 --- a/static/ru/rpc/block/block-effects.md +++ b/static/ru/rpc/block/block-effects.md @@ -230,5 +230,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/block/block-effects/index.md b/static/ru/rpc/block/block-effects/index.md index 039b7ae..9f7963d 100644 --- a/static/ru/rpc/block/block-effects/index.md +++ b/static/ru/rpc/block/block-effects/index.md @@ -230,5 +230,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/call-function.md b/static/ru/rpc/contract/call-function.md index 135c2d1..ff0e9ea 100644 --- a/static/ru/rpc/contract/call-function.md +++ b/static/ru/rpc/contract/call-function.md @@ -277,5 +277,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/call-function/index.md b/static/ru/rpc/contract/call-function/index.md index 135c2d1..ff0e9ea 100644 --- a/static/ru/rpc/contract/call-function/index.md +++ b/static/ru/rpc/contract/call-function/index.md @@ -277,5 +277,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-code.md b/static/ru/rpc/contract/view-code.md index 3c6532f..c893261 100644 --- a/static/ru/rpc/contract/view-code.md +++ b/static/ru/rpc/contract/view-code.md @@ -247,5 +247,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-code/index.md b/static/ru/rpc/contract/view-code/index.md index 3c6532f..c893261 100644 --- a/static/ru/rpc/contract/view-code/index.md +++ b/static/ru/rpc/contract/view-code/index.md @@ -247,5 +247,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-global-contract-code-by-account-id.md b/static/ru/rpc/contract/view-global-contract-code-by-account-id.md index 1142445..a5a8282 100644 --- a/static/ru/rpc/contract/view-global-contract-code-by-account-id.md +++ b/static/ru/rpc/contract/view-global-contract-code-by-account-id.md @@ -247,5 +247,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-global-contract-code-by-account-id/index.md b/static/ru/rpc/contract/view-global-contract-code-by-account-id/index.md index 1142445..a5a8282 100644 --- a/static/ru/rpc/contract/view-global-contract-code-by-account-id/index.md +++ b/static/ru/rpc/contract/view-global-contract-code-by-account-id/index.md @@ -247,5 +247,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-global-contract-code.md b/static/ru/rpc/contract/view-global-contract-code.md index e25f203..587b503 100644 --- a/static/ru/rpc/contract/view-global-contract-code.md +++ b/static/ru/rpc/contract/view-global-contract-code.md @@ -247,5 +247,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-global-contract-code/index.md b/static/ru/rpc/contract/view-global-contract-code/index.md index e25f203..587b503 100644 --- a/static/ru/rpc/contract/view-global-contract-code/index.md +++ b/static/ru/rpc/contract/view-global-contract-code/index.md @@ -247,5 +247,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-state.md b/static/ru/rpc/contract/view-state.md index 8d0625d..f23099c 100644 --- a/static/ru/rpc/contract/view-state.md +++ b/static/ru/rpc/contract/view-state.md @@ -273,5 +273,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/contract/view-state/index.md b/static/ru/rpc/contract/view-state/index.md index 8d0625d..f23099c 100644 --- a/static/ru/rpc/contract/view-state/index.md +++ b/static/ru/rpc/contract/view-state/index.md @@ -273,5 +273,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/examples.md b/static/ru/rpc/examples.md index caf180d..7d6cbcd 100644 --- a/static/ru/rpc/examples.md +++ b/static/ru/rpc/examples.md @@ -4,6 +4,8 @@ Начинайте с RPC-метода, который отвечает на вопрос. Используйте `tx`, чтобы отследить включение и финальность по хешу транзакции, и расширяйте поверхность только когда нужны дерево receipts, сырой state или трассировка на уровне shard. +Все shell-примеры ниже работают на публичных RPC-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ## Состояние аккаунта ### Показать баланс и storage аккаунта на finality @@ -11,10 +13,12 @@ `view_account` — канонический RPC-запрос для текущего состояния аккаунта. Один вызов возвращает свободный баланс, сумму, заблокированную в валидаторском стейке или lockup-контракте, использованное storage и блок, на котором было сделано чтение. `finality: "final"` гарантирует, что вы читаете стабильное состояние, а не optimistic-представление. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$RPC_URL" \ +curl -s "https://rpc.mainnet.fastnear.com" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -23,7 +27,7 @@ curl -s "$RPC_URL" \ | jq '.result | {amount, locked, storage_usage, block_height, block_hash}' ``` -Для `mike.near` это возвращает `amount` (yoctoNEAR в свободной части), `locked: "0"` (ничего в валидаторском стейке или lockup-контракте) и `storage_usage: 558441` — 558 КБ on-chain-состояния. Пара `block_height`/`block_hash` фиксирует точку чтения; чтобы прочитать несколько аккаунтов *на одном и том же* блоке, переиспользуйте возвращённый `block_hash` как `block_id` в последующих запросах. +Для `root.near` это возвращает `amount` (yoctoNEAR в свободной части), `locked: "0"` (ничего в валидаторском стейке или lockup-контракте) и `storage_usage: 28677` — примерно 28.7 КБ on-chain-состояния. Пара `block_height`/`block_hash` фиксирует точку чтения; чтобы прочитать несколько аккаунтов *на одном и том же* блоке, переиспользуйте возвращённый `block_hash` как `block_id` в последующих запросах. ## Включение транзакции и финальность @@ -32,11 +36,13 @@ curl -s "$RPC_URL" \ Есть tx hash? Опрашивайте `tx` с минимальным порогом `wait_until`, который отвечает на ваш вопрос. ```bash -RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow SIGNER_ACCOUNT_ID=mike.testnet +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$RPC_URL" \ +curl -s "https://archival-rpc.testnet.fastnear.com" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" --arg signer_id "$SIGNER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "tx", @@ -64,14 +70,15 @@ curl -s "$RPC_URL" \ Блок NEAR — это header поверх N shard chunks, а не плоский список транзакций. `block` возвращает headers chunks; сами транзакции лежат уровнем ниже, внутри `chunk`. Шортката `block → tx` нет — блок не несёт хешей транзакций, поэтому `tx` (которому нужен hash) в этой цепочке не участвует. Канонический проход — `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com EMPTY_TX_ROOT=11111111111111111111111111111111 +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -BLOCK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +BLOCK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ | jq -r '.result.sync_info.latest_block_hash')" -CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +CHUNK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg block_hash "$BLOCK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"block",params:{block_id:$block_hash} }')" \ @@ -81,7 +88,7 @@ CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ if [ -z "$CHUNK_HASH" ]; then echo "tip block had no transactions in any chunk — rerun on the next head" else - curl -s "$RPC_URL" -H 'content-type: application/json' \ + curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg chunk_hash "$CHUNK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"chunk",params:{chunk_id:$chunk_hash} }')" \ @@ -119,11 +126,13 @@ fi Любой ключ с `tx_count: 0` был создан и ни разу не использовался — самый очевидный кандидат на очистку. Следующий по порядку — ключи, заскоупленные на контракт, с которым вы больше не работаете. Фильтр ниже сужает до `social.near`, но чтобы аудитировать другой контракт, меняется только строка `RECEIVER_ID`. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near RECEIVER_ID=social.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$RPC_URL" \ +curl -s "https://rpc.mainnet.fastnear.com" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -147,7 +156,7 @@ curl -s "$RPC_URL" \ }' ``` -Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на удаление. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность одного dapp. +Для `root.near` это возвращает 235 ключей всего, включая 34 function-call-ключа на `social.near`; 21 из них были созданы и ни разу не использовались (`tx_count: 0`) и потому являются прямыми кандидатами на удаление. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность одного dapp. Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом (function-call-ключ не может авторизовать `DeleteKey`) и отправьте через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Повторный запуск того же запроса подтвердит, что ключа больше нет. @@ -160,10 +169,11 @@ View-метод вроде `get_num` всё равно заставляет уз Контракты на `near-sdk-rs` хранят верхнеуровневую `#[near_bindgen]`-структуру под ключом `STATE`. Передайте `STATE` как `prefix_base64` (`U1RBVEU=` — это base64 тех же четырёх ASCII-байт), и узел вернёт сериализованное значение. ```bash -RPC_URL=https://rpc.testnet.fastnear.com CONTRACT_ID=counter.near-examples.testnet +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +RAW_B64="$(curl -s "https://rpc.testnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"view_state",account_id:$contract,prefix_base64:"U1RBVEU=",finality:"final"} @@ -188,13 +198,14 @@ jq -n --arg raw "$RAW_B64" --argjson val "$DECODED_I8" '{raw_bytes_base64: $raw, `social.near` знает две вещи, о которых UI кошелька может только догадываться: сколько storage осталось у каждого аккаунта и разрешена ли делегированному signer запись под этим аккаунтом. Два view-вызова сворачивают вопрос готовности к одному boolean. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near # account you're writing under -SIGNER_ACCOUNT_ID=mike.near # account signing the transaction +ACCOUNT_ID=root.near # account you're writing under +SIGNER_ACCOUNT_ID=root.near # account signing the transaction +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -STORAGE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +STORAGE="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$STORAGE_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"get_account_storage",args_base64:$args,finality:"final"} @@ -205,7 +216,7 @@ if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then PERMISSION=true else PERM_ARGS_B64="$(jq -nc --arg pred "$SIGNER_ACCOUNT_ID" --arg key "$ACCOUNT_ID" '{predecessor_id:$pred,key:$key}' | base64 | tr -d '\n')" - PERMISSION="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + PERMISSION="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$PERM_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"is_write_permission_granted",args_base64:$args,finality:"final"} @@ -223,23 +234,24 @@ jq -n --argjson storage "$STORAGE" --argjson permission "$PERMISSION" \ }' ``` -Для `mike.near`, подписывающего под собой, это возвращает `storage: {used_bytes: 139803, available_bytes: 83891}`, `permission_granted: true` (владельческая запись) и `ready_to_publish: true`. Если `storage` приходит как `null` или `available_bytes: 0`, аккаунту нужен `storage_deposit` на `social.near`, прежде чем новая запись сможет закрепиться. Если signer отличается от цели, ветка permission спрашивает `is_write_permission_granted({predecessor_id, key})` — тот же on-chain-ответ, который dapp видит, прежде чем писать от имени пользователя. Полную поверхность контракта см. в [SocialDB API](https://github.com/NearSocial/social-db#api). +Для `root.near`, подписывающего под собой, это возвращает `storage: {used_bytes: 136245, available_bytes: 42484}`, `permission_granted: true` (владельческая запись) и `ready_to_publish: true`. Если `storage` приходит как `null` или `available_bytes: 0`, аккаунту нужен `storage_deposit` на `social.near`, прежде чем новая запись сможет закрепиться. Если signer отличается от цели, ветка permission спрашивает `is_write_permission_granted({predecessor_id, key})` — тот же on-chain-ответ, который dapp видит, прежде чем писать от имени пользователя. Полную поверхность контракта см. в [SocialDB API](https://github.com/NearSocial/social-db#api). ### Что `mob.near/widget/Profile` содержит прямо сейчас? SocialDB хранит BOS-виджеты как ключи `/widget/` на `social.near`. Один `keys` с типом возврата `BlockHeight` возвращает каталог плюс якоря последней записи по каждому виджету; один `get` возвращает точный исходник. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com ACCOUNT_ID=mob.near WIDGET_NAME=Profile +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi KEYS_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" '{ keys: [($account_id + "/widget/*")], options: {return_type: "BlockHeight"} }' | base64 | tr -d '\n')" -curl -s "$RPC_URL" -H 'content-type: application/json' \ +curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$KEYS_ARGS" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"keys",args_base64:$args,finality:"final"} @@ -256,7 +268,7 @@ GET_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" '{ keys: [($account_id + "/widget/" + $widget)] }' | base64 | tr -d '\n')" -curl -s "$RPC_URL" -H 'content-type: application/json' \ +curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$GET_ARGS" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"get",args_base64:$args,finality:"final"} @@ -287,5 +299,5 @@ curl -s "$RPC_URL" -H 'content-type: application/json' \ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/examples/index.md b/static/ru/rpc/examples/index.md index caf180d..7d6cbcd 100644 --- a/static/ru/rpc/examples/index.md +++ b/static/ru/rpc/examples/index.md @@ -4,6 +4,8 @@ Начинайте с RPC-метода, который отвечает на вопрос. Используйте `tx`, чтобы отследить включение и финальность по хешу транзакции, и расширяйте поверхность только когда нужны дерево receipts, сырой state или трассировка на уровне shard. +Все shell-примеры ниже работают на публичных RPC-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ## Состояние аккаунта ### Показать баланс и storage аккаунта на finality @@ -11,10 +13,12 @@ `view_account` — канонический RPC-запрос для текущего состояния аккаунта. Один вызов возвращает свободный баланс, сумму, заблокированную в валидаторском стейке или lockup-контракте, использованное storage и блок, на котором было сделано чтение. `finality: "final"` гарантирует, что вы читаете стабильное состояние, а не optimistic-представление. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$RPC_URL" \ +curl -s "https://rpc.mainnet.fastnear.com" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -23,7 +27,7 @@ curl -s "$RPC_URL" \ | jq '.result | {amount, locked, storage_usage, block_height, block_hash}' ``` -Для `mike.near` это возвращает `amount` (yoctoNEAR в свободной части), `locked: "0"` (ничего в валидаторском стейке или lockup-контракте) и `storage_usage: 558441` — 558 КБ on-chain-состояния. Пара `block_height`/`block_hash` фиксирует точку чтения; чтобы прочитать несколько аккаунтов *на одном и том же* блоке, переиспользуйте возвращённый `block_hash` как `block_id` в последующих запросах. +Для `root.near` это возвращает `amount` (yoctoNEAR в свободной части), `locked: "0"` (ничего в валидаторском стейке или lockup-контракте) и `storage_usage: 28677` — примерно 28.7 КБ on-chain-состояния. Пара `block_height`/`block_hash` фиксирует точку чтения; чтобы прочитать несколько аккаунтов *на одном и том же* блоке, переиспользуйте возвращённый `block_hash` как `block_id` в последующих запросах. ## Включение транзакции и финальность @@ -32,11 +36,13 @@ curl -s "$RPC_URL" \ Есть tx hash? Опрашивайте `tx` с минимальным порогом `wait_until`, который отвечает на ваш вопрос. ```bash -RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CVyG2xLJ6fuKCtULAxMnWTh2GL5ey2UUiTcgYT3M6Pow SIGNER_ACCOUNT_ID=mike.testnet +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$RPC_URL" \ +curl -s "https://archival-rpc.testnet.fastnear.com" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" --arg signer_id "$SIGNER_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "tx", @@ -64,14 +70,15 @@ curl -s "$RPC_URL" \ Блок NEAR — это header поверх N shard chunks, а не плоский список транзакций. `block` возвращает headers chunks; сами транзакции лежат уровнем ниже, внутри `chunk`. Шортката `block → tx` нет — блок не несёт хешей транзакций, поэтому `tx` (которому нужен hash) в этой цепочке не участвует. Канонический проход — `status` → `block` → `chunk`, пропуская пустые chunks по дороге. Большинство chunks в tip-блоке пустые — их `tx_root` равен сентинелу `11111111111111111111111111111111`, поэтому селектору нужен фильтр. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com EMPTY_TX_ROOT=11111111111111111111111111111111 +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -BLOCK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +BLOCK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":"fastnear","method":"status","params":[]}' \ | jq -r '.result.sync_info.latest_block_hash')" -CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +CHUNK_HASH="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg block_hash "$BLOCK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"block",params:{block_id:$block_hash} }')" \ @@ -81,7 +88,7 @@ CHUNK_HASH="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ if [ -z "$CHUNK_HASH" ]; then echo "tip block had no transactions in any chunk — rerun on the next head" else - curl -s "$RPC_URL" -H 'content-type: application/json' \ + curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg chunk_hash "$CHUNK_HASH" '{ jsonrpc:"2.0",id:"fastnear",method:"chunk",params:{chunk_id:$chunk_hash} }')" \ @@ -119,11 +126,13 @@ fi Любой ключ с `tx_count: 0` был создан и ни разу не использовался — самый очевидный кандидат на очистку. Следующий по порядку — ключи, заскоупленные на контракт, с которым вы больше не работаете. Фильтр ниже сужает до `social.near`, но чтобы аудитировать другой контракт, меняется только строка `RECEIVER_ID`. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near RECEIVER_ID=social.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$RPC_URL" \ +curl -s "https://rpc.mainnet.fastnear.com" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", @@ -147,7 +156,7 @@ curl -s "$RPC_URL" \ }' ``` -Для `mike.near` это возвращает десятки function-call-ключей на `social.near`. Записи с `tx_count: 0` были созданы и ни разу не использовались — прямые кандидаты на удаление. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность одного dapp. +Для `root.near` это возвращает 235 ключей всего, включая 34 function-call-ключа на `social.near`; 21 из них были созданы и ни разу не использовались (`tx_count: 0`) и потому являются прямыми кандидатами на удаление. `method_names: "ANY"` означает, что ключ может вызвать любой метод на `social.near`; сужение до списка вида `["find_grants", "insert_grant", "delete_grant"]` означает, что ключ был заскоуплен на write-поверхность одного dapp. Чтобы удалить такой ключ, подпишите action `DeleteKey` **full-access**-ключом (function-call-ключ не может авторизовать `DeleteKey`) и отправьте через [`send_tx`](https://docs.fastnear.com/ru/rpc/transaction/send-tx). Повторный запуск того же запроса подтвердит, что ключа больше нет. @@ -160,10 +169,11 @@ View-метод вроде `get_num` всё равно заставляет уз Контракты на `near-sdk-rs` хранят верхнеуровневую `#[near_bindgen]`-структуру под ключом `STATE`. Передайте `STATE` как `prefix_base64` (`U1RBVEU=` — это base64 тех же четырёх ASCII-байт), и узел вернёт сериализованное значение. ```bash -RPC_URL=https://rpc.testnet.fastnear.com CONTRACT_ID=counter.near-examples.testnet +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -RAW_B64="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +RAW_B64="$(curl -s "https://rpc.testnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg contract "$CONTRACT_ID" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"view_state",account_id:$contract,prefix_base64:"U1RBVEU=",finality:"final"} @@ -188,13 +198,14 @@ jq -n --arg raw "$RAW_B64" --argjson val "$DECODED_I8" '{raw_bytes_base64: $raw, `social.near` знает две вещи, о которых UI кошелька может только догадываться: сколько storage осталось у каждого аккаунта и разрешена ли делегированному signer запись под этим аккаунтом. Два view-вызова сворачивают вопрос готовности к одному boolean. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com -ACCOUNT_ID=mike.near # account you're writing under -SIGNER_ACCOUNT_ID=mike.near # account signing the transaction +ACCOUNT_ID=root.near # account you're writing under +SIGNER_ACCOUNT_ID=root.near # account signing the transaction +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi STORAGE_ARGS_B64="$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id:$account_id}' | base64 | tr -d '\n')" -STORAGE="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ +STORAGE="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$STORAGE_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"get_account_storage",args_base64:$args,finality:"final"} @@ -205,7 +216,7 @@ if [ "$SIGNER_ACCOUNT_ID" = "$ACCOUNT_ID" ]; then PERMISSION=true else PERM_ARGS_B64="$(jq -nc --arg pred "$SIGNER_ACCOUNT_ID" --arg key "$ACCOUNT_ID" '{predecessor_id:$pred,key:$key}' | base64 | tr -d '\n')" - PERMISSION="$(curl -s "$RPC_URL" -H 'content-type: application/json' \ + PERMISSION="$(curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$PERM_ARGS_B64" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"is_write_permission_granted",args_base64:$args,finality:"final"} @@ -223,23 +234,24 @@ jq -n --argjson storage "$STORAGE" --argjson permission "$PERMISSION" \ }' ``` -Для `mike.near`, подписывающего под собой, это возвращает `storage: {used_bytes: 139803, available_bytes: 83891}`, `permission_granted: true` (владельческая запись) и `ready_to_publish: true`. Если `storage` приходит как `null` или `available_bytes: 0`, аккаунту нужен `storage_deposit` на `social.near`, прежде чем новая запись сможет закрепиться. Если signer отличается от цели, ветка permission спрашивает `is_write_permission_granted({predecessor_id, key})` — тот же on-chain-ответ, который dapp видит, прежде чем писать от имени пользователя. Полную поверхность контракта см. в [SocialDB API](https://github.com/NearSocial/social-db#api). +Для `root.near`, подписывающего под собой, это возвращает `storage: {used_bytes: 136245, available_bytes: 42484}`, `permission_granted: true` (владельческая запись) и `ready_to_publish: true`. Если `storage` приходит как `null` или `available_bytes: 0`, аккаунту нужен `storage_deposit` на `social.near`, прежде чем новая запись сможет закрепиться. Если signer отличается от цели, ветка permission спрашивает `is_write_permission_granted({predecessor_id, key})` — тот же on-chain-ответ, который dapp видит, прежде чем писать от имени пользователя. Полную поверхность контракта см. в [SocialDB API](https://github.com/NearSocial/social-db#api). ### Что `mob.near/widget/Profile` содержит прямо сейчас? SocialDB хранит BOS-виджеты как ключи `/widget/` на `social.near`. Один `keys` с типом возврата `BlockHeight` возвращает каталог плюс якоря последней записи по каждому виджету; один `get` возвращает точный исходник. ```bash -RPC_URL=https://rpc.mainnet.fastnear.com ACCOUNT_ID=mob.near WIDGET_NAME=Profile +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi KEYS_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" '{ keys: [($account_id + "/widget/*")], options: {return_type: "BlockHeight"} }' | base64 | tr -d '\n')" -curl -s "$RPC_URL" -H 'content-type: application/json' \ +curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$KEYS_ARGS" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"keys",args_base64:$args,finality:"final"} @@ -256,7 +268,7 @@ GET_ARGS="$(jq -nc --arg account_id "$ACCOUNT_ID" --arg widget "$WIDGET_NAME" '{ keys: [($account_id + "/widget/" + $widget)] }' | base64 | tr -d '\n')" -curl -s "$RPC_URL" -H 'content-type: application/json' \ +curl -s "https://rpc.mainnet.fastnear.com" "${AUTH_HEADER[@]}" -H 'content-type: application/json' \ --data "$(jq -nc --arg args "$GET_ARGS" '{ jsonrpc:"2.0",id:"fastnear",method:"query", params:{request_type:"call_function",account_id:"social.near",method_name:"get",args_base64:$args,finality:"final"} @@ -287,5 +299,5 @@ curl -s "$RPC_URL" -H 'content-type: application/json' \ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/index.md b/static/ru/rpc/index.md index 36a16ca..7e0b256 100644 --- a/static/ru/rpc/index.md +++ b/static/ru/rpc/index.md @@ -85,5 +85,5 @@ https://archival-rpc.testnet.fastnear.com - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/changes.md b/static/ru/rpc/protocol/changes.md index 79bf155..cd399f9 100644 --- a/static/ru/rpc/protocol/changes.md +++ b/static/ru/rpc/protocol/changes.md @@ -227,5 +227,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/changes/index.md b/static/ru/rpc/protocol/changes/index.md index 79bf155..cd399f9 100644 --- a/static/ru/rpc/protocol/changes/index.md +++ b/static/ru/rpc/protocol/changes/index.md @@ -227,5 +227,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/chunk-by-block-shard.md b/static/ru/rpc/protocol/chunk-by-block-shard.md index 666b593..c8cc9b2 100644 --- a/static/ru/rpc/protocol/chunk-by-block-shard.md +++ b/static/ru/rpc/protocol/chunk-by-block-shard.md @@ -447,5 +447,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/chunk-by-block-shard/index.md b/static/ru/rpc/protocol/chunk-by-block-shard/index.md index 666b593..c8cc9b2 100644 --- a/static/ru/rpc/protocol/chunk-by-block-shard/index.md +++ b/static/ru/rpc/protocol/chunk-by-block-shard/index.md @@ -447,5 +447,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/chunk-by-hash.md b/static/ru/rpc/protocol/chunk-by-hash.md index 0a6f714..b3a5656 100644 --- a/static/ru/rpc/protocol/chunk-by-hash.md +++ b/static/ru/rpc/protocol/chunk-by-hash.md @@ -426,5 +426,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/chunk-by-hash/index.md b/static/ru/rpc/protocol/chunk-by-hash/index.md index 0a6f714..b3a5656 100644 --- a/static/ru/rpc/protocol/chunk-by-hash/index.md +++ b/static/ru/rpc/protocol/chunk-by-hash/index.md @@ -426,5 +426,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/client-config.md b/static/ru/rpc/protocol/client-config.md index 5426d54..954932b 100644 --- a/static/ru/rpc/protocol/client-config.md +++ b/static/ru/rpc/protocol/client-config.md @@ -1033,5 +1033,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/client-config/index.md b/static/ru/rpc/protocol/client-config/index.md index 5426d54..954932b 100644 --- a/static/ru/rpc/protocol/client-config/index.md +++ b/static/ru/rpc/protocol/client-config/index.md @@ -1033,5 +1033,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-congestion-level.md b/static/ru/rpc/protocol/experimental-congestion-level.md index 0fbe787..1c81283 100644 --- a/static/ru/rpc/protocol/experimental-congestion-level.md +++ b/static/ru/rpc/protocol/experimental-congestion-level.md @@ -231,5 +231,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-congestion-level/index.md b/static/ru/rpc/protocol/experimental-congestion-level/index.md index 0fbe787..1c81283 100644 --- a/static/ru/rpc/protocol/experimental-congestion-level/index.md +++ b/static/ru/rpc/protocol/experimental-congestion-level/index.md @@ -231,5 +231,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-light-client-block-proof.md b/static/ru/rpc/protocol/experimental-light-client-block-proof.md index 778c5c6..dca694c 100644 --- a/static/ru/rpc/protocol/experimental-light-client-block-proof.md +++ b/static/ru/rpc/protocol/experimental-light-client-block-proof.md @@ -262,5 +262,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-light-client-block-proof/index.md b/static/ru/rpc/protocol/experimental-light-client-block-proof/index.md index 778c5c6..dca694c 100644 --- a/static/ru/rpc/protocol/experimental-light-client-block-proof/index.md +++ b/static/ru/rpc/protocol/experimental-light-client-block-proof/index.md @@ -262,5 +262,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-light-client-proof.md b/static/ru/rpc/protocol/experimental-light-client-proof.md index 34280a7..4c255de 100644 --- a/static/ru/rpc/protocol/experimental-light-client-proof.md +++ b/static/ru/rpc/protocol/experimental-light-client-proof.md @@ -364,5 +364,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-light-client-proof/index.md b/static/ru/rpc/protocol/experimental-light-client-proof/index.md index 34280a7..4c255de 100644 --- a/static/ru/rpc/protocol/experimental-light-client-proof/index.md +++ b/static/ru/rpc/protocol/experimental-light-client-proof/index.md @@ -364,5 +364,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-protocol-config.md b/static/ru/rpc/protocol/experimental-protocol-config.md index a7bebf7..843ddae 100644 --- a/static/ru/rpc/protocol/experimental-protocol-config.md +++ b/static/ru/rpc/protocol/experimental-protocol-config.md @@ -548,5 +548,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-protocol-config/index.md b/static/ru/rpc/protocol/experimental-protocol-config/index.md index a7bebf7..843ddae 100644 --- a/static/ru/rpc/protocol/experimental-protocol-config/index.md +++ b/static/ru/rpc/protocol/experimental-protocol-config/index.md @@ -548,5 +548,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-split-storage-info.md b/static/ru/rpc/protocol/experimental-split-storage-info.md index ec72637..316d9f5 100644 --- a/static/ru/rpc/protocol/experimental-split-storage-info.md +++ b/static/ru/rpc/protocol/experimental-split-storage-info.md @@ -217,5 +217,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/experimental-split-storage-info/index.md b/static/ru/rpc/protocol/experimental-split-storage-info/index.md index ec72637..316d9f5 100644 --- a/static/ru/rpc/protocol/experimental-split-storage-info/index.md +++ b/static/ru/rpc/protocol/experimental-split-storage-info/index.md @@ -217,5 +217,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/gas-price-by-block.md b/static/ru/rpc/protocol/gas-price-by-block.md index a406fb9..4e52f8d 100644 --- a/static/ru/rpc/protocol/gas-price-by-block.md +++ b/static/ru/rpc/protocol/gas-price-by-block.md @@ -218,5 +218,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/gas-price-by-block/index.md b/static/ru/rpc/protocol/gas-price-by-block/index.md index a406fb9..4e52f8d 100644 --- a/static/ru/rpc/protocol/gas-price-by-block/index.md +++ b/static/ru/rpc/protocol/gas-price-by-block/index.md @@ -218,5 +218,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/gas-price.md b/static/ru/rpc/protocol/gas-price.md index fc7e998..756ac4f 100644 --- a/static/ru/rpc/protocol/gas-price.md +++ b/static/ru/rpc/protocol/gas-price.md @@ -200,5 +200,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/gas-price/index.md b/static/ru/rpc/protocol/gas-price/index.md index fc7e998..756ac4f 100644 --- a/static/ru/rpc/protocol/gas-price/index.md +++ b/static/ru/rpc/protocol/gas-price/index.md @@ -200,5 +200,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/genesis-config.md b/static/ru/rpc/protocol/genesis-config.md index 63abf19..a65474c 100644 --- a/static/ru/rpc/protocol/genesis-config.md +++ b/static/ru/rpc/protocol/genesis-config.md @@ -601,5 +601,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/genesis-config/index.md b/static/ru/rpc/protocol/genesis-config/index.md index 63abf19..a65474c 100644 --- a/static/ru/rpc/protocol/genesis-config/index.md +++ b/static/ru/rpc/protocol/genesis-config/index.md @@ -601,5 +601,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/health.md b/static/ru/rpc/protocol/health.md index 07e9805..4d137b6 100644 --- a/static/ru/rpc/protocol/health.md +++ b/static/ru/rpc/protocol/health.md @@ -180,5 +180,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/health/index.md b/static/ru/rpc/protocol/health/index.md index 07e9805..4d137b6 100644 --- a/static/ru/rpc/protocol/health/index.md +++ b/static/ru/rpc/protocol/health/index.md @@ -180,5 +180,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/latest-block.md b/static/ru/rpc/protocol/latest-block.md index 9376f6a..ab28723 100644 --- a/static/ru/rpc/protocol/latest-block.md +++ b/static/ru/rpc/protocol/latest-block.md @@ -201,5 +201,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/latest-block/index.md b/static/ru/rpc/protocol/latest-block/index.md index 9376f6a..ab28723 100644 --- a/static/ru/rpc/protocol/latest-block/index.md +++ b/static/ru/rpc/protocol/latest-block/index.md @@ -201,5 +201,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/light-client-proof.md b/static/ru/rpc/protocol/light-client-proof.md index 19d9c06..5d9e849 100644 --- a/static/ru/rpc/protocol/light-client-proof.md +++ b/static/ru/rpc/protocol/light-client-proof.md @@ -364,5 +364,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/light-client-proof/index.md b/static/ru/rpc/protocol/light-client-proof/index.md index 19d9c06..5d9e849 100644 --- a/static/ru/rpc/protocol/light-client-proof/index.md +++ b/static/ru/rpc/protocol/light-client-proof/index.md @@ -364,5 +364,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/maintenance-windows.md b/static/ru/rpc/protocol/maintenance-windows.md index aad205d..eac058a 100644 --- a/static/ru/rpc/protocol/maintenance-windows.md +++ b/static/ru/rpc/protocol/maintenance-windows.md @@ -221,5 +221,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/maintenance-windows/index.md b/static/ru/rpc/protocol/maintenance-windows/index.md index aad205d..eac058a 100644 --- a/static/ru/rpc/protocol/maintenance-windows/index.md +++ b/static/ru/rpc/protocol/maintenance-windows/index.md @@ -221,5 +221,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/metrics.md b/static/ru/rpc/protocol/metrics.md index e93c0ad..f96f4fd 100644 --- a/static/ru/rpc/protocol/metrics.md +++ b/static/ru/rpc/protocol/metrics.md @@ -48,5 +48,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/metrics/index.md b/static/ru/rpc/protocol/metrics/index.md index e93c0ad..f96f4fd 100644 --- a/static/ru/rpc/protocol/metrics/index.md +++ b/static/ru/rpc/protocol/metrics/index.md @@ -48,5 +48,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/network-info.md b/static/ru/rpc/protocol/network-info.md index e882265..a67a2dc 100644 --- a/static/ru/rpc/protocol/network-info.md +++ b/static/ru/rpc/protocol/network-info.md @@ -242,5 +242,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/network-info/index.md b/static/ru/rpc/protocol/network-info/index.md index e882265..a67a2dc 100644 --- a/static/ru/rpc/protocol/network-info/index.md +++ b/static/ru/rpc/protocol/network-info/index.md @@ -242,5 +242,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/next-light-client-block.md b/static/ru/rpc/protocol/next-light-client-block.md index 477df32..3db9d79 100644 --- a/static/ru/rpc/protocol/next-light-client-block.md +++ b/static/ru/rpc/protocol/next-light-client-block.md @@ -339,5 +339,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/next-light-client-block/index.md b/static/ru/rpc/protocol/next-light-client-block/index.md index 477df32..3db9d79 100644 --- a/static/ru/rpc/protocol/next-light-client-block/index.md +++ b/static/ru/rpc/protocol/next-light-client-block/index.md @@ -339,5 +339,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/status.md b/static/ru/rpc/protocol/status.md index 1df337b..5c3ec62 100644 --- a/static/ru/rpc/protocol/status.md +++ b/static/ru/rpc/protocol/status.md @@ -499,5 +499,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/protocol/status/index.md b/static/ru/rpc/protocol/status/index.md index 1df337b..5c3ec62 100644 --- a/static/ru/rpc/protocol/status/index.md +++ b/static/ru/rpc/protocol/status/index.md @@ -499,5 +499,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/broadcast-tx-async.md b/static/ru/rpc/transaction/broadcast-tx-async.md index c861162..538b209 100644 --- a/static/ru/rpc/transaction/broadcast-tx-async.md +++ b/static/ru/rpc/transaction/broadcast-tx-async.md @@ -215,5 +215,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/broadcast-tx-async/index.md b/static/ru/rpc/transaction/broadcast-tx-async/index.md index c861162..538b209 100644 --- a/static/ru/rpc/transaction/broadcast-tx-async/index.md +++ b/static/ru/rpc/transaction/broadcast-tx-async/index.md @@ -215,5 +215,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/broadcast-tx-commit.md b/static/ru/rpc/transaction/broadcast-tx-commit.md index 6d0b207..64bf9cc 100644 --- a/static/ru/rpc/transaction/broadcast-tx-commit.md +++ b/static/ru/rpc/transaction/broadcast-tx-commit.md @@ -281,5 +281,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/broadcast-tx-commit/index.md b/static/ru/rpc/transaction/broadcast-tx-commit/index.md index 6d0b207..64bf9cc 100644 --- a/static/ru/rpc/transaction/broadcast-tx-commit/index.md +++ b/static/ru/rpc/transaction/broadcast-tx-commit/index.md @@ -281,5 +281,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/experimental-receipt.md b/static/ru/rpc/transaction/experimental-receipt.md index 6a66204..532666b 100644 --- a/static/ru/rpc/transaction/experimental-receipt.md +++ b/static/ru/rpc/transaction/experimental-receipt.md @@ -246,5 +246,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/experimental-receipt/index.md b/static/ru/rpc/transaction/experimental-receipt/index.md index 6a66204..532666b 100644 --- a/static/ru/rpc/transaction/experimental-receipt/index.md +++ b/static/ru/rpc/transaction/experimental-receipt/index.md @@ -246,5 +246,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/experimental-tx-status.md b/static/ru/rpc/transaction/experimental-tx-status.md index 17b68b3..a763990 100644 --- a/static/ru/rpc/transaction/experimental-tx-status.md +++ b/static/ru/rpc/transaction/experimental-tx-status.md @@ -300,5 +300,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/experimental-tx-status/index.md b/static/ru/rpc/transaction/experimental-tx-status/index.md index 17b68b3..a763990 100644 --- a/static/ru/rpc/transaction/experimental-tx-status/index.md +++ b/static/ru/rpc/transaction/experimental-tx-status/index.md @@ -300,5 +300,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/send-tx.md b/static/ru/rpc/transaction/send-tx.md index 1536d21..ff314e1 100644 --- a/static/ru/rpc/transaction/send-tx.md +++ b/static/ru/rpc/transaction/send-tx.md @@ -283,5 +283,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/send-tx/index.md b/static/ru/rpc/transaction/send-tx/index.md index 1536d21..ff314e1 100644 --- a/static/ru/rpc/transaction/send-tx/index.md +++ b/static/ru/rpc/transaction/send-tx/index.md @@ -283,5 +283,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/tx-status.md b/static/ru/rpc/transaction/tx-status.md index b970384..556bbb6 100644 --- a/static/ru/rpc/transaction/tx-status.md +++ b/static/ru/rpc/transaction/tx-status.md @@ -298,5 +298,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/transaction/tx-status/index.md b/static/ru/rpc/transaction/tx-status/index.md index b970384..556bbb6 100644 --- a/static/ru/rpc/transaction/tx-status/index.md +++ b/static/ru/rpc/transaction/tx-status/index.md @@ -298,5 +298,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/validators/experimental-validators-ordered.md b/static/ru/rpc/validators/experimental-validators-ordered.md index 753b3d9..dbb0e1d 100644 --- a/static/ru/rpc/validators/experimental-validators-ordered.md +++ b/static/ru/rpc/validators/experimental-validators-ordered.md @@ -209,5 +209,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/validators/experimental-validators-ordered/index.md b/static/ru/rpc/validators/experimental-validators-ordered/index.md index 753b3d9..dbb0e1d 100644 --- a/static/ru/rpc/validators/experimental-validators-ordered/index.md +++ b/static/ru/rpc/validators/experimental-validators-ordered/index.md @@ -209,5 +209,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/validators/validators-by-epoch.md b/static/ru/rpc/validators/validators-by-epoch.md index f66841e..307708f 100644 --- a/static/ru/rpc/validators/validators-by-epoch.md +++ b/static/ru/rpc/validators/validators-by-epoch.md @@ -297,5 +297,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/validators/validators-by-epoch/index.md b/static/ru/rpc/validators/validators-by-epoch/index.md index f66841e..307708f 100644 --- a/static/ru/rpc/validators/validators-by-epoch/index.md +++ b/static/ru/rpc/validators/validators-by-epoch/index.md @@ -297,5 +297,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/validators/validators-current.md b/static/ru/rpc/validators/validators-current.md index 2ef9752..dbfb5d9 100644 --- a/static/ru/rpc/validators/validators-current.md +++ b/static/ru/rpc/validators/validators-current.md @@ -288,5 +288,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpc/validators/validators-current/index.md b/static/ru/rpc/validators/validators-current/index.md index 2ef9752..dbfb5d9 100644 --- a/static/ru/rpc/validators/validators-current/index.md +++ b/static/ru/rpc/validators/validators-current/index.md @@ -288,5 +288,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/account/view_access_key.md b/static/ru/rpcs/account/view_access_key.md index b04408b..2b49490 100644 --- a/static/ru/rpcs/account/view_access_key.md +++ b/static/ru/rpcs/account/view_access_key.md @@ -259,5 +259,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/account/view_access_key/index.md b/static/ru/rpcs/account/view_access_key/index.md index b04408b..2b49490 100644 --- a/static/ru/rpcs/account/view_access_key/index.md +++ b/static/ru/rpcs/account/view_access_key/index.md @@ -259,5 +259,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/account/view_access_key_list.md b/static/ru/rpcs/account/view_access_key_list.md index 48df69d..1557ba7 100644 --- a/static/ru/rpcs/account/view_access_key_list.md +++ b/static/ru/rpcs/account/view_access_key_list.md @@ -241,5 +241,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/account/view_access_key_list/index.md b/static/ru/rpcs/account/view_access_key_list/index.md index 48df69d..1557ba7 100644 --- a/static/ru/rpcs/account/view_access_key_list/index.md +++ b/static/ru/rpcs/account/view_access_key_list/index.md @@ -241,5 +241,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/account/view_account.md b/static/ru/rpcs/account/view_account.md index 71daf77..8c904c0 100644 --- a/static/ru/rpcs/account/view_account.md +++ b/static/ru/rpcs/account/view_account.md @@ -293,5 +293,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/account/view_account/index.md b/static/ru/rpcs/account/view_account/index.md index 71daf77..8c904c0 100644 --- a/static/ru/rpcs/account/view_account/index.md +++ b/static/ru/rpcs/account/view_account/index.md @@ -293,5 +293,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/block/block_by_height.md b/static/ru/rpcs/block/block_by_height.md index 288d440..a84711b 100644 --- a/static/ru/rpcs/block/block_by_height.md +++ b/static/ru/rpcs/block/block_by_height.md @@ -195,5 +195,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/block/block_by_height/index.md b/static/ru/rpcs/block/block_by_height/index.md index 288d440..a84711b 100644 --- a/static/ru/rpcs/block/block_by_height/index.md +++ b/static/ru/rpcs/block/block_by_height/index.md @@ -195,5 +195,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/block/block_by_id.md b/static/ru/rpcs/block/block_by_id.md index 9b84d3b..0c9a325 100644 --- a/static/ru/rpcs/block/block_by_id.md +++ b/static/ru/rpcs/block/block_by_id.md @@ -195,5 +195,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/block/block_by_id/index.md b/static/ru/rpcs/block/block_by_id/index.md index 9b84d3b..0c9a325 100644 --- a/static/ru/rpcs/block/block_by_id/index.md +++ b/static/ru/rpcs/block/block_by_id/index.md @@ -195,5 +195,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/block/block_effects.md b/static/ru/rpcs/block/block_effects.md index 2c64769..4523c67 100644 --- a/static/ru/rpcs/block/block_effects.md +++ b/static/ru/rpcs/block/block_effects.md @@ -229,5 +229,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/block/block_effects/index.md b/static/ru/rpcs/block/block_effects/index.md index 2c64769..4523c67 100644 --- a/static/ru/rpcs/block/block_effects/index.md +++ b/static/ru/rpcs/block/block_effects/index.md @@ -229,5 +229,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/call.md b/static/ru/rpcs/contract/call.md index efbc072..999959e 100644 --- a/static/ru/rpcs/contract/call.md +++ b/static/ru/rpcs/contract/call.md @@ -276,5 +276,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/call/index.md b/static/ru/rpcs/contract/call/index.md index efbc072..999959e 100644 --- a/static/ru/rpcs/contract/call/index.md +++ b/static/ru/rpcs/contract/call/index.md @@ -276,5 +276,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_code.md b/static/ru/rpcs/contract/view_code.md index 20a52a8..9ef3277 100644 --- a/static/ru/rpcs/contract/view_code.md +++ b/static/ru/rpcs/contract/view_code.md @@ -246,5 +246,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_code/index.md b/static/ru/rpcs/contract/view_code/index.md index 20a52a8..9ef3277 100644 --- a/static/ru/rpcs/contract/view_code/index.md +++ b/static/ru/rpcs/contract/view_code/index.md @@ -246,5 +246,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_global_contract_code.md b/static/ru/rpcs/contract/view_global_contract_code.md index 03ff28b..036080e 100644 --- a/static/ru/rpcs/contract/view_global_contract_code.md +++ b/static/ru/rpcs/contract/view_global_contract_code.md @@ -246,5 +246,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_global_contract_code/index.md b/static/ru/rpcs/contract/view_global_contract_code/index.md index 03ff28b..036080e 100644 --- a/static/ru/rpcs/contract/view_global_contract_code/index.md +++ b/static/ru/rpcs/contract/view_global_contract_code/index.md @@ -246,5 +246,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_global_contract_code_by_account_id.md b/static/ru/rpcs/contract/view_global_contract_code_by_account_id.md index 433bd3a..e3b7ce3 100644 --- a/static/ru/rpcs/contract/view_global_contract_code_by_account_id.md +++ b/static/ru/rpcs/contract/view_global_contract_code_by_account_id.md @@ -246,5 +246,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_global_contract_code_by_account_id/index.md b/static/ru/rpcs/contract/view_global_contract_code_by_account_id/index.md index 433bd3a..e3b7ce3 100644 --- a/static/ru/rpcs/contract/view_global_contract_code_by_account_id/index.md +++ b/static/ru/rpcs/contract/view_global_contract_code_by_account_id/index.md @@ -246,5 +246,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_state.md b/static/ru/rpcs/contract/view_state.md index fe5fb6b..e2fbf29 100644 --- a/static/ru/rpcs/contract/view_state.md +++ b/static/ru/rpcs/contract/view_state.md @@ -272,5 +272,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/contract/view_state/index.md b/static/ru/rpcs/contract/view_state/index.md index fe5fb6b..e2fbf29 100644 --- a/static/ru/rpcs/contract/view_state/index.md +++ b/static/ru/rpcs/contract/view_state/index.md @@ -272,5 +272,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level.md b/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level.md index 5927ae3..d609f19 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level.md @@ -230,5 +230,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level/index.md b/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level/index.md index 5927ae3..d609f19 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level/index.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_congestion_level/index.md @@ -230,5 +230,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof.md b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof.md index e9207c6..7d6d1d6 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof.md @@ -261,5 +261,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof/index.md b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof/index.md index e9207c6..7d6d1d6 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof/index.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_block_proof/index.md @@ -261,5 +261,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof.md b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof.md index bce7bcc..8e89e6c 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof.md @@ -363,5 +363,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof/index.md b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof/index.md index bce7bcc..8e89e6c 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof/index.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_light_client_proof/index.md @@ -363,5 +363,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config.md b/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config.md index 1e6b574..f94c243 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config.md @@ -547,5 +547,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config/index.md b/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config/index.md index 1e6b574..f94c243 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config/index.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_protocol_config/index.md @@ -547,5 +547,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info.md b/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info.md index ce0faa0..d09ef9e 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info.md @@ -216,5 +216,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info/index.md b/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info/index.md index ce0faa0..d09ef9e 100644 --- a/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info/index.md +++ b/static/ru/rpcs/protocol/EXPERIMENTAL_split_storage_info/index.md @@ -216,5 +216,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/changes.md b/static/ru/rpcs/protocol/changes.md index f7a5ac2..bd8714e 100644 --- a/static/ru/rpcs/protocol/changes.md +++ b/static/ru/rpcs/protocol/changes.md @@ -226,5 +226,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/changes/index.md b/static/ru/rpcs/protocol/changes/index.md index f7a5ac2..bd8714e 100644 --- a/static/ru/rpcs/protocol/changes/index.md +++ b/static/ru/rpcs/protocol/changes/index.md @@ -226,5 +226,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/chunk_by_block_shard.md b/static/ru/rpcs/protocol/chunk_by_block_shard.md index 36a28c2..21c03f5 100644 --- a/static/ru/rpcs/protocol/chunk_by_block_shard.md +++ b/static/ru/rpcs/protocol/chunk_by_block_shard.md @@ -446,5 +446,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/chunk_by_block_shard/index.md b/static/ru/rpcs/protocol/chunk_by_block_shard/index.md index 36a28c2..21c03f5 100644 --- a/static/ru/rpcs/protocol/chunk_by_block_shard/index.md +++ b/static/ru/rpcs/protocol/chunk_by_block_shard/index.md @@ -446,5 +446,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/chunk_by_hash.md b/static/ru/rpcs/protocol/chunk_by_hash.md index 33fb043..43dd6a1 100644 --- a/static/ru/rpcs/protocol/chunk_by_hash.md +++ b/static/ru/rpcs/protocol/chunk_by_hash.md @@ -425,5 +425,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/chunk_by_hash/index.md b/static/ru/rpcs/protocol/chunk_by_hash/index.md index 33fb043..43dd6a1 100644 --- a/static/ru/rpcs/protocol/chunk_by_hash/index.md +++ b/static/ru/rpcs/protocol/chunk_by_hash/index.md @@ -425,5 +425,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/client_config.md b/static/ru/rpcs/protocol/client_config.md index f4001f0..4e8c672 100644 --- a/static/ru/rpcs/protocol/client_config.md +++ b/static/ru/rpcs/protocol/client_config.md @@ -1032,5 +1032,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/client_config/index.md b/static/ru/rpcs/protocol/client_config/index.md index f4001f0..4e8c672 100644 --- a/static/ru/rpcs/protocol/client_config/index.md +++ b/static/ru/rpcs/protocol/client_config/index.md @@ -1032,5 +1032,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/gas_price.md b/static/ru/rpcs/protocol/gas_price.md index d34353a..24da275 100644 --- a/static/ru/rpcs/protocol/gas_price.md +++ b/static/ru/rpcs/protocol/gas_price.md @@ -199,5 +199,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/gas_price/index.md b/static/ru/rpcs/protocol/gas_price/index.md index d34353a..24da275 100644 --- a/static/ru/rpcs/protocol/gas_price/index.md +++ b/static/ru/rpcs/protocol/gas_price/index.md @@ -199,5 +199,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/gas_price_by_block.md b/static/ru/rpcs/protocol/gas_price_by_block.md index 2de8aad..6660fe8 100644 --- a/static/ru/rpcs/protocol/gas_price_by_block.md +++ b/static/ru/rpcs/protocol/gas_price_by_block.md @@ -217,5 +217,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/gas_price_by_block/index.md b/static/ru/rpcs/protocol/gas_price_by_block/index.md index 2de8aad..6660fe8 100644 --- a/static/ru/rpcs/protocol/gas_price_by_block/index.md +++ b/static/ru/rpcs/protocol/gas_price_by_block/index.md @@ -217,5 +217,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/genesis_config.md b/static/ru/rpcs/protocol/genesis_config.md index 979a8f7..99b7913 100644 --- a/static/ru/rpcs/protocol/genesis_config.md +++ b/static/ru/rpcs/protocol/genesis_config.md @@ -600,5 +600,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/genesis_config/index.md b/static/ru/rpcs/protocol/genesis_config/index.md index 979a8f7..99b7913 100644 --- a/static/ru/rpcs/protocol/genesis_config/index.md +++ b/static/ru/rpcs/protocol/genesis_config/index.md @@ -600,5 +600,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/health.md b/static/ru/rpcs/protocol/health.md index 8dd242a..c56987d 100644 --- a/static/ru/rpcs/protocol/health.md +++ b/static/ru/rpcs/protocol/health.md @@ -179,5 +179,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/health/index.md b/static/ru/rpcs/protocol/health/index.md index 8dd242a..c56987d 100644 --- a/static/ru/rpcs/protocol/health/index.md +++ b/static/ru/rpcs/protocol/health/index.md @@ -179,5 +179,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/latest_block.md b/static/ru/rpcs/protocol/latest_block.md index 20437fd..666f63b 100644 --- a/static/ru/rpcs/protocol/latest_block.md +++ b/static/ru/rpcs/protocol/latest_block.md @@ -200,5 +200,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/latest_block/index.md b/static/ru/rpcs/protocol/latest_block/index.md index 20437fd..666f63b 100644 --- a/static/ru/rpcs/protocol/latest_block/index.md +++ b/static/ru/rpcs/protocol/latest_block/index.md @@ -200,5 +200,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/light_client_proof.md b/static/ru/rpcs/protocol/light_client_proof.md index 7cacb43..36ab61d 100644 --- a/static/ru/rpcs/protocol/light_client_proof.md +++ b/static/ru/rpcs/protocol/light_client_proof.md @@ -363,5 +363,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/light_client_proof/index.md b/static/ru/rpcs/protocol/light_client_proof/index.md index 7cacb43..36ab61d 100644 --- a/static/ru/rpcs/protocol/light_client_proof/index.md +++ b/static/ru/rpcs/protocol/light_client_proof/index.md @@ -363,5 +363,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/maintenance_windows.md b/static/ru/rpcs/protocol/maintenance_windows.md index 04e2d57..ecb329a 100644 --- a/static/ru/rpcs/protocol/maintenance_windows.md +++ b/static/ru/rpcs/protocol/maintenance_windows.md @@ -220,5 +220,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/maintenance_windows/index.md b/static/ru/rpcs/protocol/maintenance_windows/index.md index 04e2d57..ecb329a 100644 --- a/static/ru/rpcs/protocol/maintenance_windows/index.md +++ b/static/ru/rpcs/protocol/maintenance_windows/index.md @@ -220,5 +220,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/metrics.md b/static/ru/rpcs/protocol/metrics.md index 07d5820..1bc7a15 100644 --- a/static/ru/rpcs/protocol/metrics.md +++ b/static/ru/rpcs/protocol/metrics.md @@ -47,5 +47,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/metrics/index.md b/static/ru/rpcs/protocol/metrics/index.md index 07d5820..1bc7a15 100644 --- a/static/ru/rpcs/protocol/metrics/index.md +++ b/static/ru/rpcs/protocol/metrics/index.md @@ -47,5 +47,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/network_info.md b/static/ru/rpcs/protocol/network_info.md index eeae82b..050b3f0 100644 --- a/static/ru/rpcs/protocol/network_info.md +++ b/static/ru/rpcs/protocol/network_info.md @@ -241,5 +241,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/network_info/index.md b/static/ru/rpcs/protocol/network_info/index.md index eeae82b..050b3f0 100644 --- a/static/ru/rpcs/protocol/network_info/index.md +++ b/static/ru/rpcs/protocol/network_info/index.md @@ -241,5 +241,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/next_light_client_block.md b/static/ru/rpcs/protocol/next_light_client_block.md index 16757de..33569a0 100644 --- a/static/ru/rpcs/protocol/next_light_client_block.md +++ b/static/ru/rpcs/protocol/next_light_client_block.md @@ -338,5 +338,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/next_light_client_block/index.md b/static/ru/rpcs/protocol/next_light_client_block/index.md index 16757de..33569a0 100644 --- a/static/ru/rpcs/protocol/next_light_client_block/index.md +++ b/static/ru/rpcs/protocol/next_light_client_block/index.md @@ -338,5 +338,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/status.md b/static/ru/rpcs/protocol/status.md index 705379e..cb3fb90 100644 --- a/static/ru/rpcs/protocol/status.md +++ b/static/ru/rpcs/protocol/status.md @@ -498,5 +498,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/protocol/status/index.md b/static/ru/rpcs/protocol/status/index.md index 705379e..cb3fb90 100644 --- a/static/ru/rpcs/protocol/status/index.md +++ b/static/ru/rpcs/protocol/status/index.md @@ -498,5 +498,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/EXPERIMENTAL_receipt.md b/static/ru/rpcs/transaction/EXPERIMENTAL_receipt.md index 9068d0b..45aa8a2 100644 --- a/static/ru/rpcs/transaction/EXPERIMENTAL_receipt.md +++ b/static/ru/rpcs/transaction/EXPERIMENTAL_receipt.md @@ -245,5 +245,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/EXPERIMENTAL_receipt/index.md b/static/ru/rpcs/transaction/EXPERIMENTAL_receipt/index.md index 9068d0b..45aa8a2 100644 --- a/static/ru/rpcs/transaction/EXPERIMENTAL_receipt/index.md +++ b/static/ru/rpcs/transaction/EXPERIMENTAL_receipt/index.md @@ -245,5 +245,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status.md b/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status.md index 7ecb843..18611b9 100644 --- a/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status.md +++ b/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status.md @@ -299,5 +299,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status/index.md b/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status/index.md index 7ecb843..18611b9 100644 --- a/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status/index.md +++ b/static/ru/rpcs/transaction/EXPERIMENTAL_tx_status/index.md @@ -299,5 +299,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/broadcast_tx_async.md b/static/ru/rpcs/transaction/broadcast_tx_async.md index 9754fe9..dc53455 100644 --- a/static/ru/rpcs/transaction/broadcast_tx_async.md +++ b/static/ru/rpcs/transaction/broadcast_tx_async.md @@ -214,5 +214,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/broadcast_tx_async/index.md b/static/ru/rpcs/transaction/broadcast_tx_async/index.md index 9754fe9..dc53455 100644 --- a/static/ru/rpcs/transaction/broadcast_tx_async/index.md +++ b/static/ru/rpcs/transaction/broadcast_tx_async/index.md @@ -214,5 +214,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/broadcast_tx_commit.md b/static/ru/rpcs/transaction/broadcast_tx_commit.md index 7678eb8..6dcd64b 100644 --- a/static/ru/rpcs/transaction/broadcast_tx_commit.md +++ b/static/ru/rpcs/transaction/broadcast_tx_commit.md @@ -280,5 +280,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/broadcast_tx_commit/index.md b/static/ru/rpcs/transaction/broadcast_tx_commit/index.md index 7678eb8..6dcd64b 100644 --- a/static/ru/rpcs/transaction/broadcast_tx_commit/index.md +++ b/static/ru/rpcs/transaction/broadcast_tx_commit/index.md @@ -280,5 +280,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/send_tx.md b/static/ru/rpcs/transaction/send_tx.md index 2443e8e..9d0deb0 100644 --- a/static/ru/rpcs/transaction/send_tx.md +++ b/static/ru/rpcs/transaction/send_tx.md @@ -282,5 +282,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/send_tx/index.md b/static/ru/rpcs/transaction/send_tx/index.md index 2443e8e..9d0deb0 100644 --- a/static/ru/rpcs/transaction/send_tx/index.md +++ b/static/ru/rpcs/transaction/send_tx/index.md @@ -282,5 +282,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/tx_status.md b/static/ru/rpcs/transaction/tx_status.md index 475f730..a11a25c 100644 --- a/static/ru/rpcs/transaction/tx_status.md +++ b/static/ru/rpcs/transaction/tx_status.md @@ -297,5 +297,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/transaction/tx_status/index.md b/static/ru/rpcs/transaction/tx_status/index.md index 475f730..a11a25c 100644 --- a/static/ru/rpcs/transaction/tx_status/index.md +++ b/static/ru/rpcs/transaction/tx_status/index.md @@ -297,5 +297,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered.md b/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered.md index d089c7c..a8a1ab9 100644 --- a/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered.md +++ b/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered.md @@ -208,5 +208,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered/index.md b/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered/index.md index d089c7c..a8a1ab9 100644 --- a/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered/index.md +++ b/static/ru/rpcs/validators/EXPERIMENTAL_validators_ordered/index.md @@ -208,5 +208,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/validators/validators_by_epoch.md b/static/ru/rpcs/validators/validators_by_epoch.md index 886c8c7..8086de3 100644 --- a/static/ru/rpcs/validators/validators_by_epoch.md +++ b/static/ru/rpcs/validators/validators_by_epoch.md @@ -296,5 +296,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/validators/validators_by_epoch/index.md b/static/ru/rpcs/validators/validators_by_epoch/index.md index 886c8c7..8086de3 100644 --- a/static/ru/rpcs/validators/validators_by_epoch/index.md +++ b/static/ru/rpcs/validators/validators_by_epoch/index.md @@ -296,5 +296,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/validators/validators_current.md b/static/ru/rpcs/validators/validators_current.md index 6742344..7a6bfb8 100644 --- a/static/ru/rpcs/validators/validators_current.md +++ b/static/ru/rpcs/validators/validators_current.md @@ -287,5 +287,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/rpcs/validators/validators_current/index.md b/static/ru/rpcs/validators/validators_current/index.md index 6742344..7a6bfb8 100644 --- a/static/ru/rpcs/validators/validators_current/index.md +++ b/static/ru/rpcs/validators/validators_current/index.md @@ -287,5 +287,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots.md b/static/ru/snapshots.md index 91e722e..037bee2 100644 --- a/static/ru/snapshots.md +++ b/static/ru/snapshots.md @@ -60,5 +60,5 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/examples.md b/static/ru/snapshots/examples.md index e651706..83f91fa 100644 --- a/static/ru/snapshots/examples.md +++ b/static/ru/snapshots/examples.md @@ -61,5 +61,5 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/examples/index.md b/static/ru/snapshots/examples/index.md index e651706..83f91fa 100644 --- a/static/ru/snapshots/examples/index.md +++ b/static/ru/snapshots/examples/index.md @@ -61,5 +61,5 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/index.md b/static/ru/snapshots/index.md index 91e722e..037bee2 100644 --- a/static/ru/snapshots/index.md +++ b/static/ru/snapshots/index.md @@ -60,5 +60,5 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/mainnet.md b/static/ru/snapshots/mainnet.md index 24fe8c5..00af631 100644 --- a/static/ru/snapshots/mainnet.md +++ b/static/ru/snapshots/mainnet.md @@ -133,5 +133,5 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/mainnet/index.md b/static/ru/snapshots/mainnet/index.md index 24fe8c5..00af631 100644 --- a/static/ru/snapshots/mainnet/index.md +++ b/static/ru/snapshots/mainnet/index.md @@ -133,5 +133,5 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/testnet.md b/static/ru/snapshots/testnet.md index 068dbe6..eaf4788 100644 --- a/static/ru/snapshots/testnet.md +++ b/static/ru/snapshots/testnet.md @@ -82,5 +82,5 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/snapshots/testnet/index.md b/static/ru/snapshots/testnet/index.md index 068dbe6..eaf4788 100644 --- a/static/ru/snapshots/testnet/index.md +++ b/static/ru/snapshots/testnet/index.md @@ -82,5 +82,5 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/transfers.md b/static/ru/transfers.md index 4c2cbf1..831666b 100644 --- a/static/ru/transfers.md +++ b/static/ru/transfers.md @@ -76,5 +76,5 @@ https://transfers.main.fastnear.com - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/transfers/examples.md b/static/ru/transfers/examples.md index a819880..0655a42 100644 --- a/static/ru/transfers/examples.md +++ b/static/ru/transfers/examples.md @@ -1,17 +1,47 @@ **Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) -## Пример +## Примеры + +Эти shell-примеры работают и на публичных Transfers и Transactions endpoint-ах. Если `FASTNEAR_API_KEY` уже задан в окружении, сниппеты автоматически пробросят его как bearer-заголовок. + +### Какая у этого аккаунта свежая активность по переводам? + +`/v0/transfers` всего с `account_id` и `desc: true` возвращает самые свежие переводы, касающиеся этого аккаунта, по всем типам активов, в обоих направлениях сразу. В каждой строке уже есть `human_amount`, `asset_id` и `transaction_id`, так что этот поток заодно служит быстрым сканом активности до того, как вы достанете фильтры. + +```bash +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + +curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id: $account_id, desc: true, limit: 5}')" \ + | jq '{ + recent: [.transfers[] | { + block_height, + asset_id, + human_amount, + other_account_id, + transfer_type, + tx: .transaction_id + }] + }' +``` + +Для `root.near` последние строки смешивают активы `FtTransfer` и `MtTransfer`. `asset_id` использует URI по NEP-стандартам (`native:near`, `nep141:...`, `nep245:...`), так что одно поле уже подсказывает, к какому стандарту тянуться дальше. Положительный `human_amount` означает, что аккаунт получил; отрицательный — что отправил. `other_account_id: null` — норма для multi-token-форм, где контрагент сидит внутри границы контракта, а не как отдельный аккаунт верхнего уровня. ### Отфильтровать и листать ленту переводов одного аккаунта `/v0/transfers` возвращает отфильтрованную ленту плюс `resume_token`, который вы переиспользуете *без изменения фильтров*, чтобы продолжать листать. В каждой строке уже есть `human_amount`, `usd_amount`, `transaction_id` и `receipt_id` — большинство audit-вопросов закрываются без второго запроса. ```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -FEED="$(curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ +FEED="$(curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ account_id: $account_id, @@ -33,15 +63,30 @@ echo "$FEED" | jq '{ Когда одной строке нужна точка исполнения, возьмите её `receipt_id` и сразу обратитесь к `/v0/receipt`: ```bash +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +FEED="$(curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + direction: "receiver", + asset_id: "native:near", + min_amount: "1000000000000000000000000", + desc: true, + limit: 10 + }')")" RECEIPT_ID="$(echo "$FEED" | jq -r '.transfers[0].receipt_id')" -curl -s "$TX_BASE_URL/v0/receipt" \ +curl -s "https://tx.main.fastnear.com/v0/receipt" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ | jq '.receipt | {receipt_id, transaction_hash, receiver_id, predecessor_id, tx_block_height, is_success}' ``` -Это тот же переход, что описан в [Превратить один неказистый receipt ID из логов в человекочитаемую историю](https://docs.fastnear.com/ru/tx/examples#%D0%BF%D1%80%D0%B5%D0%B2%D1%80%D0%B0%D1%82%D0%B8%D1%82%D1%8C-%D0%BE%D0%B4%D0%B8%D0%BD-%D0%BD%D0%B5%D0%BA%D0%B0%D0%B7%D0%B8%D1%81%D1%82%D1%8B%D0%B9-receipt-id-%D0%B8%D0%B7-%D0%BB%D0%BE%D0%B3%D0%BE%D0%B2-%D0%B2-%D1%87%D0%B5%D0%BB%D0%BE%D0%B2%D0%B5%D0%BA%D0%BE%D1%87%D0%B8%D1%82%D0%B0%D0%B5%D0%BC%D1%83%D1%8E-%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D1%8E) — один запрос возвращает и квитанцию, и её родительскую транзакцию целиком. +Это тот же переход, что описан в [Превратить один receipt ID в читаемую историю транзакции](https://docs.fastnear.com/ru/tx/examples#receipt-id-to-readable-story) — один запрос возвращает и квитанцию, и её родительскую транзакцию целиком. ## Частые ошибки @@ -61,5 +106,5 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/transfers/examples/index.md b/static/ru/transfers/examples/index.md index a819880..0655a42 100644 --- a/static/ru/transfers/examples/index.md +++ b/static/ru/transfers/examples/index.md @@ -1,17 +1,47 @@ **Источник:** [https://docs.fastnear.com/ru/transfers/examples](https://docs.fastnear.com/ru/transfers/examples) -## Пример +## Примеры + +Эти shell-примеры работают и на публичных Transfers и Transactions endpoint-ах. Если `FASTNEAR_API_KEY` уже задан в окружении, сниппеты автоматически пробросят его как bearer-заголовок. + +### Какая у этого аккаунта свежая активность по переводам? + +`/v0/transfers` всего с `account_id` и `desc: true` возвращает самые свежие переводы, касающиеся этого аккаунта, по всем типам активов, в обоих направлениях сразу. В каждой строке уже есть `human_amount`, `asset_id` и `transaction_id`, так что этот поток заодно служит быстрым сканом активности до того, как вы достанете фильтры. + +```bash +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + +curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{account_id: $account_id, desc: true, limit: 5}')" \ + | jq '{ + recent: [.transfers[] | { + block_height, + asset_id, + human_amount, + other_account_id, + transfer_type, + tx: .transaction_id + }] + }' +``` + +Для `root.near` последние строки смешивают активы `FtTransfer` и `MtTransfer`. `asset_id` использует URI по NEP-стандартам (`native:near`, `nep141:...`, `nep245:...`), так что одно поле уже подсказывает, к какому стандарту тянуться дальше. Положительный `human_amount` означает, что аккаунт получил; отрицательный — что отправил. `other_account_id: null` — норма для multi-token-форм, где контрагент сидит внутри границы контракта, а не как отдельный аккаунт верхнего уровня. ### Отфильтровать и листать ленту переводов одного аккаунта `/v0/transfers` возвращает отфильтрованную ленту плюс `resume_token`, который вы переиспользуете *без изменения фильтров*, чтобы продолжать листать. В каждой строке уже есть `human_amount`, `usd_amount`, `transaction_id` и `receipt_id` — большинство audit-вопросов закрываются без второго запроса. ```bash -TRANSFERS_BASE_URL=https://transfers.main.fastnear.com -TX_BASE_URL=https://tx.main.fastnear.com ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -FEED="$(curl -s "$TRANSFERS_BASE_URL/v0/transfers" \ +FEED="$(curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ account_id: $account_id, @@ -33,15 +63,30 @@ echo "$FEED" | jq '{ Когда одной строке нужна точка исполнения, возьмите её `receipt_id` и сразу обратитесь к `/v0/receipt`: ```bash +ACCOUNT_ID=root.near +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +FEED="$(curl -s "https://transfers.main.fastnear.com/v0/transfers" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" '{ + account_id: $account_id, + direction: "receiver", + asset_id: "native:near", + min_amount: "1000000000000000000000000", + desc: true, + limit: 10 + }')")" RECEIPT_ID="$(echo "$FEED" | jq -r '.transfers[0].receipt_id')" -curl -s "$TX_BASE_URL/v0/receipt" \ +curl -s "https://tx.main.fastnear.com/v0/receipt" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ | jq '.receipt | {receipt_id, transaction_hash, receiver_id, predecessor_id, tx_block_height, is_success}' ``` -Это тот же переход, что описан в [Превратить один неказистый receipt ID из логов в человекочитаемую историю](https://docs.fastnear.com/ru/tx/examples#%D0%BF%D1%80%D0%B5%D0%B2%D1%80%D0%B0%D1%82%D0%B8%D1%82%D1%8C-%D0%BE%D0%B4%D0%B8%D0%BD-%D0%BD%D0%B5%D0%BA%D0%B0%D0%B7%D0%B8%D1%81%D1%82%D1%8B%D0%B9-receipt-id-%D0%B8%D0%B7-%D0%BB%D0%BE%D0%B3%D0%BE%D0%B2-%D0%B2-%D1%87%D0%B5%D0%BB%D0%BE%D0%B2%D0%B5%D0%BA%D0%BE%D1%87%D0%B8%D1%82%D0%B0%D0%B5%D0%BC%D1%83%D1%8E-%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D1%8E) — один запрос возвращает и квитанцию, и её родительскую транзакцию целиком. +Это тот же переход, что описан в [Превратить один receipt ID в читаемую историю транзакции](https://docs.fastnear.com/ru/tx/examples#receipt-id-to-readable-story) — один запрос возвращает и квитанцию, и её родительскую транзакцию целиком. ## Частые ошибки @@ -61,5 +106,5 @@ curl -s "$TX_BASE_URL/v0/receipt" \ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/transfers/index.md b/static/ru/transfers/index.md index 4c2cbf1..831666b 100644 --- a/static/ru/transfers/index.md +++ b/static/ru/transfers/index.md @@ -76,5 +76,5 @@ https://transfers.main.fastnear.com - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/transfers/query.md b/static/ru/transfers/query.md index 6b93007..c4c19c1 100644 --- a/static/ru/transfers/query.md +++ b/static/ru/transfers/query.md @@ -388,5 +388,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/transfers/query/index.md b/static/ru/transfers/query/index.md index 6b93007..c4c19c1 100644 --- a/static/ru/transfers/query/index.md +++ b/static/ru/transfers/query/index.md @@ -388,5 +388,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx.md b/static/ru/tx.md index 985bb94..565c487 100644 --- a/static/ru/tx.md +++ b/static/ru/tx.md @@ -62,5 +62,5 @@ https://tx.test.fastnear.com - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/account.md b/static/ru/tx/account.md index 4d00a72..c299b01 100644 --- a/static/ru/tx/account.md +++ b/static/ru/tx/account.md @@ -419,5 +419,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/account/index.md b/static/ru/tx/account/index.md index 4d00a72..c299b01 100644 --- a/static/ru/tx/account/index.md +++ b/static/ru/tx/account/index.md @@ -419,5 +419,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/block.md b/static/ru/tx/block.md index 9d86fc3..bcb016c 100644 --- a/static/ru/tx/block.md +++ b/static/ru/tx/block.md @@ -594,5 +594,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/block/index.md b/static/ru/tx/block/index.md index 9d86fc3..bcb016c 100644 --- a/static/ru/tx/block/index.md +++ b/static/ru/tx/block/index.md @@ -594,5 +594,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/blocks.md b/static/ru/tx/blocks.md index c53a163..6bdc239 100644 --- a/static/ru/tx/blocks.md +++ b/static/ru/tx/blocks.md @@ -273,5 +273,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/blocks/index.md b/static/ru/tx/blocks/index.md index c53a163..6bdc239 100644 --- a/static/ru/tx/blocks/index.md +++ b/static/ru/tx/blocks/index.md @@ -273,5 +273,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/examples.md b/static/ru/tx/examples.md index 2f1e464..6d7cfc9 100644 --- a/static/ru/tx/examples.md +++ b/static/ru/tx/examples.md @@ -2,15 +2,19 @@ ## Начните здесь +Все shell-примеры ниже работают на публичных Transactions API-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ### У меня один хеш транзакции. Что произошло? Вставьте хеш в `POST /v0/transactions` — один ответ обычно содержит всю историю. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp +TX_HASH=7ZKnhzt2MqMNmsk13dV8GAjGu3Db8aHzSBHeNeu9MJCq +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ @@ -24,18 +28,20 @@ curl -s "$TX_BASE_URL/v0/transactions" \ }' ``` -Для зафиксированного хеша `mike.near` отправил один `Transfer` на `global-counter.mike.near` в блоке `194263342`, с передачей в receipt `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq`. Если `receipt_count > 1` или следующий вопрос касается поведения на уровне receipt, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие) или [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt). +Для зафиксированного хеша `root.near` отправил один `Transfer` на `escrow.ai.near` в блоке `188976785`, с передачей в receipt `B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1`. Если `receipt_count > 1` или следующий вопрос касается поведения на уровне receipt, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие) или [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt). ### Какой receipt испустил этот лог или событие? Выведите список всех receipt транзакции с логами и флагом, содержат ли их логи ваш фрагмент. Совпадение доказывается, а не угадывается: у зафиксированной транзакции один receipt логирует `Transfer`, другой — `Refund`, и только сторона `Refund` переключается в `true`. ```bash -TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL LOG_FRAGMENT=Refund +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg fragment "$LOG_FRAGMENT" ' @@ -55,15 +61,17 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Фрагмент `Refund` атрибутируется receipt `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` на `wrap.near`, метод `ft_resolve_transfer`. Логи receipt живут на receipts, а не на транзакции, поэтому одного прохода достаточно — более глубокая async-трассировка не нужна. -### Превратить один неказистый receipt ID из логов в человекочитаемую историю +### Превратить один receipt ID в читаемую историю транзакции {#receipt-id-to-readable-story} `POST /v0/receipt` возвращает запись receipt **и** его полную родительскую транзакцию в одном ответе, поэтому единственного запроса хватает на всю историю — дополнительный `/v0/transactions` не нужен. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID=5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq +RECEIPT_ID=B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1 +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/receipt" \ +curl -s "https://tx.main.fastnear.com/v0/receipt" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ | jq '{ @@ -85,7 +93,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ }' ``` -Для зафиксированного receipt это возвращает `Action`-receipt от `mike.near` к `global-counter.mike.near`, который успешно выполнился в блоке `194263343`, через один блок после попадания родительской транзакции `AdgNifPY…`, — один `Transfer` (5 NEAR, в сыром `.transaction.transaction.actions` видимо как `5000000000000000000000000` yocto). Если интересным якорем становится родительская транзакция, хеш у вас уже есть — переиспользуйте его в [У меня один хеш транзакции. Что произошло?](#у-меня-один-хеш-транзакции-что-произошло). +Для зафиксированного receipt это возвращает `Action`-receipt от `root.near` к `escrow.ai.near`, который успешно выполнился в блоке `188976786`, через один блок после попадания родительской транзакции `7ZKnhzt2…`, — один `Transfer` (3.5 NEAR, в сыром `.transaction.transaction.actions` видимо как `3500000000000000000000000` yocto). Если интересным якорем становится родительская транзакция, хеш у вас уже есть — переиспользуйте его в [У меня один хеш транзакции. Что произошло?](#у-меня-один-хеш-транзакции-что-произошло). ## Сбои и async @@ -94,12 +102,13 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Один batch отправил `CreateAccount → Transfer → AddKey → FunctionCall`, и финальный вызов попал в отсутствующий метод. Индексированная запись транзакции уже несёт упорядоченный batch *и* точный сбой на уровне receipt, поэтому одного запроса хватает, чтобы ответить «что пытались и что сломалось»; проверка через `view_account` затем доказывает, что предыдущие actions откатились. ```bash -TX_BASE_URL=https://tx.test.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.test.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ @@ -121,7 +130,12 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Теперь докажите откат предыдущих actions: спросите аккаунт, который batch *пытался* создать: ```bash -curl -s "$RPC_URL" \ +NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + +curl -s "https://rpc.testnet.fastnear.com" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "query", @@ -137,12 +151,14 @@ curl -s "$RPC_URL" \ Внешний `execution_outcome.outcome.status` рапортует `SuccessReceiptId`, как только сработал handoff первого receipt, — и ничего не говорит о том, успешны ли дочерние receipts и отработал ли callback на исходном контракте. Один pipeline над `/v0/transactions` отвечает сразу на все три вопроса. ```bash -TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL ORIGIN_CONTRACT_ID=wrap.near CALLBACK_METHOD=ft_resolve_transfer +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ @@ -180,11 +196,13 @@ curl -s "$TX_BASE_URL/v0/transactions" \ [OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь подписывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, затем `worker.outlayer.near` присылает результат через `submit_execution_output_and_resolve`. Обе половины несут один и тот же `request_id` — передайте оба tx-хеша в один запрос `/v0/transactions` и извлеките это поле с каждой стороны, чтобы подтвердить пару. ```bash -TX_BASE_URL=https://tx.main.fastnear.com REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg a "$REQUEST_TX" --arg b "$WORKER_TX" '{tx_hashes: [$a, $b]}')" \ | jq '[ @@ -231,5 +249,5 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/examples/berry-club.md b/static/ru/tx/examples/berry-club.md index 6511907..15427bf 100644 --- a/static/ru/tx/examples/berry-club.md +++ b/static/ru/tx/examples/berry-club.md @@ -10,6 +10,8 @@ Переходите к Transactions API только тогда, когда вопрос становится историческим: «как Berry Club выглядел в одной более ранней эпохе и какие `draw`-вызовы сделали доску именно такой?» +Эти shell-примеры работают и с публичными RPC и Transactions endpoint-ами. Если `FASTNEAR_API_KEY` уже задан в окружении, FastNear-вызовы автоматически пробросят его как bearer-заголовок. + Карточка живой доски: запрашивает `berryclub.ek.near` `get_lines` через mainnet RPC и рендерит текущую сетку 50x50 в интерфейсе документации. ## 1. Прочитайте живую доску @@ -18,8 +20,11 @@ ```bash ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -sS https://rpc.mainnet.fastnear.com \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "{ \"jsonrpc\": \"2.0\", @@ -49,7 +54,11 @@ curl -sS https://rpc.mainnet.fastnear.com \ В этом примере используется узкое окно вокруг блока `97601515`: ```bash +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + curl -sS https://tx.main.fastnear.com/v0/account \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data '{ "account_id": "berryclub.ek.near", @@ -68,7 +77,11 @@ curl -sS https://tx.main.fastnear.com/v0/account \ Раскройте кандидатные хеши и оставьте только верхнеуровневые вызовы `draw`: ```bash +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + curl -sS https://tx.main.fastnear.com/v0/transactions \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data '{ "tx_hashes": [ @@ -110,5 +123,5 @@ for (const drawTx of drawTransactionsOldestFirst) { - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/examples/berry-club/index.md b/static/ru/tx/examples/berry-club/index.md index 6511907..15427bf 100644 --- a/static/ru/tx/examples/berry-club/index.md +++ b/static/ru/tx/examples/berry-club/index.md @@ -10,6 +10,8 @@ Переходите к Transactions API только тогда, когда вопрос становится историческим: «как Berry Club выглядел в одной более ранней эпохе и какие `draw`-вызовы сделали доску именно такой?» +Эти shell-примеры работают и с публичными RPC и Transactions endpoint-ами. Если `FASTNEAR_API_KEY` уже задан в окружении, FastNear-вызовы автоматически пробросят его как bearer-заголовок. + Карточка живой доски: запрашивает `berryclub.ek.near` `get_lines` через mainnet RPC и рендерит текущую сетку 50x50 в интерфейсе документации. ## 1. Прочитайте живую доску @@ -18,8 +20,11 @@ ```bash ARGS_BASE64="$(jq -nc '{lines: [range(0;50)]}' | base64 | tr -d '\n')" +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi curl -sS https://rpc.mainnet.fastnear.com \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "{ \"jsonrpc\": \"2.0\", @@ -49,7 +54,11 @@ curl -sS https://rpc.mainnet.fastnear.com \ В этом примере используется узкое окно вокруг блока `97601515`: ```bash +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + curl -sS https://tx.main.fastnear.com/v0/account \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data '{ "account_id": "berryclub.ek.near", @@ -68,7 +77,11 @@ curl -sS https://tx.main.fastnear.com/v0/account \ Раскройте кандидатные хеши и оставьте только верхнеуровневые вызовы `draw`: ```bash +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + curl -sS https://tx.main.fastnear.com/v0/transactions \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data '{ "tx_hashes": [ @@ -110,5 +123,5 @@ for (const drawTx of drawTransactionsOldestFirst) { - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/examples/index.md b/static/ru/tx/examples/index.md index 2f1e464..6d7cfc9 100644 --- a/static/ru/tx/examples/index.md +++ b/static/ru/tx/examples/index.md @@ -2,15 +2,19 @@ ## Начните здесь +Все shell-примеры ниже работают на публичных Transactions API-хостах как есть. Если в shell задан `FASTNEAR_API_KEY`, они автоматически добавляют bearer header; если переменная не задана, они переходят на публичный неаутентифицированный путь. + ### У меня один хеш транзакции. Что произошло? Вставьте хеш в `POST /v0/transactions` — один ответ обычно содержит всю историю. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -TX_HASH=AdgNifPYpoDNS5ckfBZm36Ai6LuL5bTstuKsVdGjKwGp +TX_HASH=7ZKnhzt2MqMNmsk13dV8GAjGu3Db8aHzSBHeNeu9MJCq +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ @@ -24,18 +28,20 @@ curl -s "$TX_BASE_URL/v0/transactions" \ }' ``` -Для зафиксированного хеша `mike.near` отправил один `Transfer` на `global-counter.mike.near` в блоке `194263342`, с передачей в receipt `5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq`. Если `receipt_count > 1` или следующий вопрос касается поведения на уровне receipt, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие) или [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt). +Для зафиксированного хеша `root.near` отправил один `Transfer` на `escrow.ai.near` в блоке `188976785`, с передачей в receipt `B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1`. Если `receipt_count > 1` или следующий вопрос касается поведения на уровне receipt, переходите к [Какой receipt испустил этот лог или событие?](#какой-receipt-испустил-этот-лог-или-событие) или [`POST /v0/receipt`](https://docs.fastnear.com/ru/tx/receipt). ### Какой receipt испустил этот лог или событие? Выведите список всех receipt транзакции с логами и флагом, содержат ли их логи ваш фрагмент. Совпадение доказывается, а не угадывается: у зафиксированной транзакции один receipt логирует `Transfer`, другой — `Refund`, и только сторона `Refund` переключается в `true`. ```bash -TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL LOG_FRAGMENT=Refund +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg fragment "$LOG_FRAGMENT" ' @@ -55,15 +61,17 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Фрагмент `Refund` атрибутируется receipt `9sLHQpaGz3NnMNMn8zGrDUSyktR1q6ts2otr9mHkfD1w` на `wrap.near`, метод `ft_resolve_transfer`. Логи receipt живут на receipts, а не на транзакции, поэтому одного прохода достаточно — более глубокая async-трассировка не нужна. -### Превратить один неказистый receipt ID из логов в человекочитаемую историю +### Превратить один receipt ID в читаемую историю транзакции {#receipt-id-to-readable-story} `POST /v0/receipt` возвращает запись receipt **и** его полную родительскую транзакцию в одном ответе, поэтому единственного запроса хватает на всю историю — дополнительный `/v0/transactions` не нужен. ```bash -TX_BASE_URL=https://tx.main.fastnear.com -RECEIPT_ID=5GhZcpfKWhrpaZo5Am74QfEUFQnZBz48G7hfoLPVDXcq +RECEIPT_ID=B8QzHQZ6VnUVy8zaVXCEkWuSs7MPb34yoHYixZV3Zdj1 +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/receipt" \ +curl -s "https://tx.main.fastnear.com/v0/receipt" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg receipt_id "$RECEIPT_ID" '{receipt_id: $receipt_id}')" \ | jq '{ @@ -85,7 +93,7 @@ curl -s "$TX_BASE_URL/v0/receipt" \ }' ``` -Для зафиксированного receipt это возвращает `Action`-receipt от `mike.near` к `global-counter.mike.near`, который успешно выполнился в блоке `194263343`, через один блок после попадания родительской транзакции `AdgNifPY…`, — один `Transfer` (5 NEAR, в сыром `.transaction.transaction.actions` видимо как `5000000000000000000000000` yocto). Если интересным якорем становится родительская транзакция, хеш у вас уже есть — переиспользуйте его в [У меня один хеш транзакции. Что произошло?](#у-меня-один-хеш-транзакции-что-произошло). +Для зафиксированного receipt это возвращает `Action`-receipt от `root.near` к `escrow.ai.near`, который успешно выполнился в блоке `188976786`, через один блок после попадания родительской транзакции `7ZKnhzt2…`, — один `Transfer` (3.5 NEAR, в сыром `.transaction.transaction.actions` видимо как `3500000000000000000000000` yocto). Если интересным якорем становится родительская транзакция, хеш у вас уже есть — переиспользуйте его в [У меня один хеш транзакции. Что произошло?](#у-меня-один-хеш-транзакции-что-произошло). ## Сбои и async @@ -94,12 +102,13 @@ curl -s "$TX_BASE_URL/v0/receipt" \ Один batch отправил `CreateAccount → Transfer → AddKey → FunctionCall`, и финальный вызов попал в отсутствующий метод. Индексированная запись транзакции уже несёт упорядоченный batch *и* точный сбой на уровне receipt, поэтому одного запроса хватает, чтобы ответить «что пытались и что сломалось»; проверка через `view_account` затем доказывает, что предыдущие actions откатились. ```bash -TX_BASE_URL=https://tx.test.fastnear.com -RPC_URL=https://rpc.testnet.fastnear.com TX_HASH=CrhH3xLzbNwNMGgZkgptXorwh8YmqxRGuA6Mc11MkU6M NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.test.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq '{ @@ -121,7 +130,12 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Теперь докажите откат предыдущих actions: спросите аккаунт, который batch *пытался* создать: ```bash -curl -s "$RPC_URL" \ +NEW_ACCOUNT_ID=rollback-mo4vmkig.temp.mike.testnet +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi + +curl -s "https://rpc.testnet.fastnear.com" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$NEW_ACCOUNT_ID" '{ jsonrpc: "2.0", id: "fastnear", method: "query", @@ -137,12 +151,14 @@ curl -s "$RPC_URL" \ Внешний `execution_outcome.outcome.status` рапортует `SuccessReceiptId`, как только сработал handoff первого receipt, — и ничего не говорит о том, успешны ли дочерние receipts и отработал ли callback на исходном контракте. Один pipeline над `/v0/transactions` отвечает сразу на все три вопроса. ```bash -TX_BASE_URL=https://tx.main.fastnear.com TX_HASH=2KhhB1uDScGCFQfVchep7DiZTGTxMcgfUYHNzwf5e6uL ORIGIN_CONTRACT_ID=wrap.near CALLBACK_METHOD=ft_resolve_transfer +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg origin "$ORIGIN_CONTRACT_ID" --arg callback "$CALLBACK_METHOD" '{ @@ -180,11 +196,13 @@ curl -s "$TX_BASE_URL/v0/transactions" \ [OutLayer](https://outlayer.fastnear.com) разделяет один логический вызов на две транзакции: пользователь подписывает `request_execution` на `outlayer.near`, worker в Intel TDX запускает нужный WASM off-chain, затем `worker.outlayer.near` присылает результат через `submit_execution_output_and_resolve`. Обе половины несут один и тот же `request_id` — передайте оба tx-хеша в один запрос `/v0/transactions` и извлеките это поле с каждой стороны, чтобы подтвердить пару. ```bash -TX_BASE_URL=https://tx.main.fastnear.com REQUEST_TX=BZDQAxEdpQ9wUGXmXTa2APwFLDTTqTy5ucrBPsfgZeyz WORKER_TX=3NYD4Mkn5cwkuVkGP9PPoiJ9PB5Vr7v6r8CwSswtHVA3 +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi -curl -s "$TX_BASE_URL/v0/transactions" \ +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg a "$REQUEST_TX" --arg b "$WORKER_TX" '{tx_hashes: [$a, $b]}')" \ | jq '[ @@ -231,5 +249,5 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/index.md b/static/ru/tx/index.md index 985bb94..565c487 100644 --- a/static/ru/tx/index.md +++ b/static/ru/tx/index.md @@ -62,5 +62,5 @@ https://tx.test.fastnear.com - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/receipt.md b/static/ru/tx/receipt.md index 0ff738d..53ef0d2 100644 --- a/static/ru/tx/receipt.md +++ b/static/ru/tx/receipt.md @@ -240,5 +240,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/receipt/index.md b/static/ru/tx/receipt/index.md index 0ff738d..53ef0d2 100644 --- a/static/ru/tx/receipt/index.md +++ b/static/ru/tx/receipt/index.md @@ -240,5 +240,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/socialdb-proofs.md b/static/ru/tx/socialdb-proofs.md index a03410c..1c32166 100644 --- a/static/ru/tx/socialdb-proofs.md +++ b/static/ru/tx/socialdb-proofs.md @@ -4,33 +4,33 @@ Используйте эту страницу только тогда, когда отправная точка — уже читаемое значение SocialDB из `api.near.social`, а следующий вопрос относится к историческому поиску записи. +Эти shell-шаги работают и с публичными endpoint-ами SocialDB и FastNear. Если `FASTNEAR_API_KEY` уже задан в окружении, FastNear-вызовы автоматически пробросят его как bearer-заголовок. + Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос звучит как «какая запись сделала это читаемое значение SocialDB истинным?». -## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` +## Канонический пример: доказать, что `root.near` установил `profile.name` в `Illia` -Используйте этот сценарий, когда читаемый факт уже звучит как «текущее `profile.name` равно `Mike Purvis`», а остаётся вопрос, какая запись сделала это поле истинным. +Используйте этот сценарий, когда читаемый факт уже звучит как «текущее `profile.name` равно `Illia`», а остаётся вопрос, какая запись сделала это поле истинным. Это единственный нюанс SocialDB, который стоит запомнить: для исторического доказательства правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. Для этого живого якоря: -- текущее `profile.name`: `Mike Purvis` -- блок записи SocialDB на уровне поля: `78675795` -- receipt ID: `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN` -- хеш исходной транзакции: `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ` -- внешний блок транзакции: `78675794` +- текущее `profile.name`: `Illia` +- блок записи SocialDB на уровне поля: `75590392` +- receipt ID: `GYvnvBxWA46UGa3aGEkqUBeT7hxhVXk2iZScJFZWU8Se` +- хеш исходной транзакции: `7HtFWv51k5Bispmh1WYPbAVkxr2X4AL6n98DhcQwVw7w` +- внешний блок транзакции: `75590391` ### Shell-сценарий 1. Прочитайте поле из NEAR Social и сохраните блок записи на уровне поля. ```bash -SOCIAL_API_BASE_URL=https://api.near.social -TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near PROFILE_FIELD=profile/name -PROFILE="$(curl -s "$SOCIAL_API_BASE_URL/get" \ +PROFILE="$(curl -s "https://api.near.social/get" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ keys: [($account_id + "/" + $profile_field)], @@ -49,7 +49,21 @@ PROFILE_BLOCK_HEIGHT="$(echo "$PROFILE" | jq -r --arg account_id "$ACCOUNT_ID" ' 2. Переиспользуйте этот блок уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. ```bash -BLOCK_RECEIPTS="$(curl -s "$TX_BASE_URL/v0/block" \ +ACCOUNT_ID=root.near +PROFILE_FIELD=profile/name +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +PROFILE_BLOCK_HEIGHT="$( + curl -s "https://api.near.social/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" '.[$account_id].profile.name[":block"]' +)" +BLOCK_RECEIPTS="$(curl -s "https://tx.main.fastnear.com/v0/block" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ block_id: $block_id, @@ -78,7 +92,37 @@ PROFILE_TX_HASH="$(echo "$BLOCK_RECEIPTS" | jq -r --arg account_id "$ACCOUNT_ID" 3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. ```bash -curl -s "$TX_BASE_URL/v0/transactions" \ +ACCOUNT_ID=root.near +PROFILE_FIELD=profile/name +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +PROFILE_BLOCK_HEIGHT="$( + curl -s "https://api.near.social/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" '.[$account_id].profile.name[":block"]' +)" +PROFILE_TX_HASH="$( + curl -s "https://tx.main.fastnear.com/v0/block" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg account_id "$ACCOUNT_ID" '{ @@ -94,8 +138,8 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | { method_name, profile_name: $profile.name, - description: $profile.description, - tags: ($profile.tags | keys) + image_fields: (($profile.image // {}) | keys), + linktree_keys: (($profile.linktree // {}) | keys) } ) }' @@ -105,8 +149,8 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Тот же мост работает и для других читаемых значений SocialDB: -- вариант для связи подписки: `mike.near -> mob.near`, блок `79574924`, tx `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- вариант для исходника виджета: `mob.near/widget/Profile`, блок `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- вариант для связи подписки: `root.near -> mob.near`, блок `79152039`, tx `DvNoqtDrruhmcq7mPpxdFacph2ZCqSzMFF5ZqMRFG78q` +- вариант для исходника виджета: `root.near/widget/Profile`, блок `76029540`, tx `ELS3DrE4Upoc91ZnBh4thVugxCUBAbaLFB4nyKsoyRNP` Ключевая идея не меняется: начните с читаемого значения и его write-block, восстановите receipt `*.near -> social.near` из блока, а затем декодируйте payload `social.near set` из исходной транзакции. --- @@ -114,5 +158,5 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/socialdb-proofs/index.md b/static/ru/tx/socialdb-proofs/index.md index a03410c..1c32166 100644 --- a/static/ru/tx/socialdb-proofs/index.md +++ b/static/ru/tx/socialdb-proofs/index.md @@ -4,33 +4,33 @@ Используйте эту страницу только тогда, когда отправная точка — уже читаемое значение SocialDB из `api.near.social`, а следующий вопрос относится к историческому поиску записи. +Эти shell-шаги работают и с публичными endpoint-ами SocialDB и FastNear. Если `FASTNEAR_API_KEY` уже задан в окружении, FastNear-вызовы автоматически пробросят его как bearer-заголовок. + Для FastNear-first-задач сначала откройте [Transactions Examples](https://docs.fastnear.com/ru/tx/examples). Сюда переходите только тогда, когда вопрос звучит как «какая запись сделала это читаемое значение SocialDB истинным?». -## Канонический пример: доказать, что `mike.near` установил `profile.name` в `Mike Purvis` +## Канонический пример: доказать, что `root.near` установил `profile.name` в `Illia` -Используйте этот сценарий, когда читаемый факт уже звучит как «текущее `profile.name` равно `Mike Purvis`», а остаётся вопрос, какая запись сделала это поле истинным. +Используйте этот сценарий, когда читаемый факт уже звучит как «текущее `profile.name` равно `Illia`», а остаётся вопрос, какая запись сделала это поле истинным. Это единственный нюанс SocialDB, который стоит запомнить: для исторического доказательства правильным мостом обычно служит `:block` на уровне поля, а не `:block` родительского объекта. Для этого живого якоря: -- текущее `profile.name`: `Mike Purvis` -- блок записи SocialDB на уровне поля: `78675795` -- receipt ID: `2gbAmEEdcCNARuCorquXStftqvWFmPG2GSaMJXFw5qiN` -- хеш исходной транзакции: `6zMb9L6rLNufZGUgCmeHTh5LvFsn3R92dPxuubH6MRsZ` -- внешний блок транзакции: `78675794` +- текущее `profile.name`: `Illia` +- блок записи SocialDB на уровне поля: `75590392` +- receipt ID: `GYvnvBxWA46UGa3aGEkqUBeT7hxhVXk2iZScJFZWU8Se` +- хеш исходной транзакции: `7HtFWv51k5Bispmh1WYPbAVkxr2X4AL6n98DhcQwVw7w` +- внешний блок транзакции: `75590391` ### Shell-сценарий 1. Прочитайте поле из NEAR Social и сохраните блок записи на уровне поля. ```bash -SOCIAL_API_BASE_URL=https://api.near.social -TX_BASE_URL=https://tx.main.fastnear.com -ACCOUNT_ID=mike.near +ACCOUNT_ID=root.near PROFILE_FIELD=profile/name -PROFILE="$(curl -s "$SOCIAL_API_BASE_URL/get" \ +PROFILE="$(curl -s "https://api.near.social/get" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ keys: [($account_id + "/" + $profile_field)], @@ -49,7 +49,21 @@ PROFILE_BLOCK_HEIGHT="$(echo "$PROFILE" | jq -r --arg account_id "$ACCOUNT_ID" ' 2. Переиспользуйте этот блок уровня поля в FastNear block receipts и восстановите receipt вместе с tx hash. ```bash -BLOCK_RECEIPTS="$(curl -s "$TX_BASE_URL/v0/block" \ +ACCOUNT_ID=root.near +PROFILE_FIELD=profile/name +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +PROFILE_BLOCK_HEIGHT="$( + curl -s "https://api.near.social/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" '.[$account_id].profile.name[":block"]' +)" +BLOCK_RECEIPTS="$(curl -s "https://tx.main.fastnear.com/v0/block" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ block_id: $block_id, @@ -78,7 +92,37 @@ PROFILE_TX_HASH="$(echo "$BLOCK_RECEIPTS" | jq -r --arg account_id "$ACCOUNT_ID" 3. Переиспользуйте этот tx hash в `POST /v0/transactions` и декодируйте payload записи SocialDB. ```bash -curl -s "$TX_BASE_URL/v0/transactions" \ +ACCOUNT_ID=root.near +PROFILE_FIELD=profile/name +AUTH_HEADER=() +if [ -n "${FASTNEAR_API_KEY:-}" ]; then AUTH_HEADER=(-H "Authorization: Bearer $FASTNEAR_API_KEY"); fi +PROFILE_BLOCK_HEIGHT="$( + curl -s "https://api.near.social/get" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --arg account_id "$ACCOUNT_ID" --arg profile_field "$PROFILE_FIELD" '{ + keys: [($account_id + "/" + $profile_field)], + options: {with_block_height: true} + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" '.[$account_id].profile.name[":block"]' +)" +PROFILE_TX_HASH="$( + curl -s "https://tx.main.fastnear.com/v0/block" \ + "${AUTH_HEADER[@]}" \ + -H 'content-type: application/json' \ + --data "$(jq -nc --argjson block_id "$PROFILE_BLOCK_HEIGHT" '{ + block_id: $block_id, + with_transactions: false, + with_receipts: true + }')" \ + | jq -r --arg account_id "$ACCOUNT_ID" ' + first( + .block_receipts[] + | select(.predecessor_id == $account_id and .receiver_id == "social.near") + | .transaction_hash + )' +)" +curl -s "https://tx.main.fastnear.com/v0/transactions" \ + "${AUTH_HEADER[@]}" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg tx_hash "$PROFILE_TX_HASH" '{tx_hashes: [$tx_hash]}')" \ | jq --arg account_id "$ACCOUNT_ID" '{ @@ -94,8 +138,8 @@ curl -s "$TX_BASE_URL/v0/transactions" \ | { method_name, profile_name: $profile.name, - description: $profile.description, - tags: ($profile.tags | keys) + image_fields: (($profile.image // {}) | keys), + linktree_keys: (($profile.linktree // {}) | keys) } ) }' @@ -105,8 +149,8 @@ curl -s "$TX_BASE_URL/v0/transactions" \ Тот же мост работает и для других читаемых значений SocialDB: -- вариант для связи подписки: `mike.near -> mob.near`, блок `79574924`, tx `FLLmTvFx9vCof79scy2uUviF5WwYmevkz9TZ8azPGVQb` -- вариант для исходника виджета: `mob.near/widget/Profile`, блок `86494825`, tx `9QDupdK2ewMxfSvMmdGEkdBcVnoL4TexmXY2FnMRxfia` +- вариант для связи подписки: `root.near -> mob.near`, блок `79152039`, tx `DvNoqtDrruhmcq7mPpxdFacph2ZCqSzMFF5ZqMRFG78q` +- вариант для исходника виджета: `root.near/widget/Profile`, блок `76029540`, tx `ELS3DrE4Upoc91ZnBh4thVugxCUBAbaLFB4nyKsoyRNP` Ключевая идея не меняется: начните с читаемого значения и его write-block, восстановите receipt `*.near -> social.near` из блока, а затем декодируйте payload `social.near set` из исходной транзакции. --- @@ -114,5 +158,5 @@ curl -s "$TX_BASE_URL/v0/transactions" \ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/transactions.md b/static/ru/tx/transactions.md index 5de5459..efb5c58 100644 --- a/static/ru/tx/transactions.md +++ b/static/ru/tx/transactions.md @@ -103,5 +103,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com). diff --git a/static/ru/tx/transactions/index.md b/static/ru/tx/transactions/index.md index 5de5459..efb5c58 100644 --- a/static/ru/tx/transactions/index.md +++ b/static/ru/tx/transactions/index.md @@ -103,5 +103,5 @@ - FastNear обрабатывает более 10 млрд запросов в месяц. - FastNear управляет более чем 100 нодами по всему миру. -- FastNear предлагает щедрые кредиты и бесплатный пробный период. -- Быстро получите пробный аккаунт на [dashboard.fastnear.com](https://dashboard.fastnear.com). +- Один API-ключ FastNear работает и для RPC, и для индексированных API. +- Получите API-ключ на [dashboard.fastnear.com](https://dashboard.fastnear.com).