原文作者:PaperMoon团队

一、引言(Introduction)

基于 Polkadot SDK(Substrate) 构建的区块链,其所有运行时数据都存储在链上的“数据库”(Key-Value Storage)中,外部应用可以直接进行查询(Query)。

所谓“链上状态(On-Chain State)”包括但不限于:
    •    账户余额(Account Balance)
    •    资产信息(Asset Metadata)
    •    治理提案(Governance Proposals)
    •    运行时模块(Pallet)管理的任意数据

也就是说,链 ≠ 只负责转账,它本质上是一台可被外部程序实时读取的数据库。

开发者可以通过 SDK 直接读取这些存储,而无需运行区块浏览器。

二、可用于查询链上状态的 SDK

Polkadot 生态目前支持多种语言的链上查询 SDK:

SDK

语言

特点

Polkadot API (PAPI)

TypeScript

现代、强类型

Polkadot.js API

JavaScript

功能全面(维护模式)

Dedot

TypeScript

轻量、高性能

Python Substrate Interface

Python

数据分析常用

Subxt

Rust

编译期类型安全(推荐后端开发)

本文将重点讲解:使用 Subxt(Rust)查询 Polkadot Hub 的账户余额与 USDT 资产信息。

三、Subxt 是什么?

Subxt 是一个 Rust 库,它通过读取区块链的 runtime metadata 自动生成类型安全接口,从而在编译阶段保证调用正确性。

简单理解:

  • Polkadot.js 是“动态 API”
  • Subxt 是“静态强类型 API”

这意味着:
    •    如果你查询了不存在的 Storage
    •    或参数类型错误

Rust 会直接编译失败,而不是运行时报错。这对于后端服务(Indexer、钱包、交易系统)非常重要。

四、开发环境准备(Prerequisites)

需要安装:
    •    最新稳定版 Rust Toolchain
    •    Cargo 包管理器

五、创建项目

cargo new subxt-query-example && cd subxt-query-example

六、安装 Subxt CLI

cargo install subxt-cli@0.44.2

七、下载 Polkadot Hub Metadata

Subxt 的核心机制依赖链的 Metadata(运行时元数据)。

执行:

subxt metadata --url INSERT_WS_ENDPOINT -o asset_hub_metadata.scale
INSERT_WS_ENDPOINT

请将上文替换为真实 WebSocket RPC:

wss://polkadot-asset-hub-rpc.polkadot.io

Metadata 是链运行时的“ABI”,描述了所有 Pallet、Storage、Call、Event 结构。

八、配置 Cargo.toml

[package]
name = "subxt-query-example"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "query_balance"
path = "src/bin/query_balance.rs"

[[bin]]
name = "query_asset"
path = "src/bin/query_asset.rs"

[dependencies]
subxt = "0.44.0"
tokio = { version = "1", features = ["rt", "macros"] }

九、查询账户余额(System.Account)

我们将读取 System Pallet 的 Account Storage。在 Substrate 中,账户余额并不是 ERC-20 合约,而是 Runtime 原生存储。

创建文件:

src/bin/query_balance.rs

代码示例

use std::str::FromStr;
use subxt::utils::AccountId32;
use subxt::{OnlineClient, PolkadotConfig};

#[subxt::subxt(runtime_metadata_path = "asset_hub_metadata.scale")]
pub mod asset_hub {}

const ASSET_HUB_RPC: &str = "INSERT_WS_ENDPOINT";
const ADDRESS: &str = "INSERT_ADDRESS";

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {

    let api = OnlineClient::<PolkadotConfig>::from_url(ASSET_HUB_RPC).await?;

    println!("Connected to Polkadot Hub");
    println!("Querying balance for: {}\n", ADDRESS);

    let account = AccountId32::from_str(ADDRESS)?;

    let storage_query = asset_hub::storage().system().account(account);

    let account_info = api
        .storage()
        .at_latest()
        .await?
        .fetch(&storage_query)
        .await?;

    match account_info {
        Some(info) => {
            println!("Nonce: {}", info.nonce);
            println!("Free Balance: {}", info.data.free);
            println!("Reserved: {}", info.data.reserved);
            println!("Frozen: {}", info.data.frozen);
        }
        None => {
            println!("Account not found");
        }
    }

    Ok(())
}

运行

cargo run --bin query_balance

重要说明

如果返回:

Account not found

并不代表地址不存在,而是:该账户从未发生过交易 → 链上没有 storage entry → fetch() 返回 None

十、查询资产信息(Assets Pallet)

接下来查询 USDT(Asset ID = 1984)

Polkadot Hub 的资产并不是 ERC-20,而是 Runtime 的 Assets Pallet 管理。

创建:

src/bin/query_asset.rs

示例代码

use std::str::FromStr;
use subxt::utils::AccountId32;
use subxt::{OnlineClient, PolkadotConfig};

#[subxt::subxt(runtime_metadata_path = "asset_hub_metadata.scale")]
pub mod asset_hub {}

const ASSET_HUB_RPC: &str = "INSERT_WS_ENDPOINT";
const USDT_ASSET_ID: u32 = 1984;
const ADDRESS: &str = "INSERT_ADDRESS";

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {

    let api = OnlineClient::<PolkadotConfig>::from_url(ASSET_HUB_RPC).await?;

    println!("Connected to Polkadot Hub");

    let metadata_query = asset_hub::storage().assets().metadata(USDT_ASSET_ID);
    let metadata = api.storage().at_latest().await?.fetch(&metadata_query).await?;

    if let Some(meta) = metadata {
        println!("Name: {}", String::from_utf8_lossy(&meta.name.0));
        println!("Symbol: {}", String::from_utf8_lossy(&meta.symbol.0));
        println!("Decimals: {}", meta.decimals);
    }

    let asset_query = asset_hub::storage().assets().asset(USDT_ASSET_ID);
    let asset_details = api.storage().at_latest().await?.fetch(&asset_query).await?;

    if let Some(details) = asset_details {
        println!("Owner: {}", details.owner);
        println!("Supply: {}", details.supply);
        println!("Accounts: {}", details.accounts);
        println!("Min Balance: {}", details.min_balance);
    }

    let account = AccountId32::from_str(ADDRESS)?;
    let account_query = asset_hub::storage().assets().account(USDT_ASSET_ID, account);

    let asset_account = api.storage().at_latest().await?.fetch(&account_query).await?;

    match asset_account {
        Some(account) => println!("Balance: {}", account.balance),
        None => println!("No asset balance found for this account"),
    }

    Ok(())
}

运行

cargo run --bin query_asset

示例输出:

Name: Tether USD
Symbol: USDT
Decimals: 6
Supply: 77998622834581
Accounts: 13545
Min Balance: 10000
No asset balance found for this account

十一、核心知识补充

1)Polkadot 的资产不是智能合约

很多开发者第一次会困惑:

为什么不用调用合约 ABI?

因为:Polkadot Hub 上的资产是 Runtime Native Asset,而非 ERC-20。

它的结构更像:系统模块存储(Storage),而不是用户部署的智能合约(Contract)。

优点:
    •    Gas 更低
    •    无需合约执行
    •    钱包读取更快
    •    更安全(无重入攻击)

2)为什么要用 Metadata

Substrate 不是固定 ABI 链:

以太坊:ABI 固定 → 节点无需解释

Substrate:Runtime 可升级 → 接口会变化 → 必须动态生成接口

Subxt 正是解决这个问题。

阅读原文:https://docs.polkadot.com/chain-interactions/query-data/query-sdks/#__tabbed_1_5

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐