The Apex SDK Substrate adapter supports two modes of operation:
This guide explains how to generate and use typed metadata for enhanced type safety and performance.
Install the subxt CLI tool:
cargo install subxt-cli
Verify installation:
subxt --version
cd apex-sdk-substrate
./scripts/generate_metadata.sh westend
This will:
src/metadata/westend.rsPolkadot:
./scripts/generate_metadata.sh polkadot
Kusama:
./scripts/generate_metadata.sh kusama
Custom endpoint:
./scripts/generate_metadata.sh wss://your-custom-node:9944
Add to Cargo.toml:
[features]
default = ["typed"]
typed = []
Before (Dynamic API):
use subxt::dynamic;
let transfer_tx = dynamic::tx(
"Balances",
"transfer_keep_alive",
vec![
dynamic::Value::from_bytes(&dest_bytes),
dynamic::Value::u128(amount),
],
);
After (Typed API):
use crate::metadata::westend::{self, runtime_types};
let transfer_tx = westend::tx()
.balances()
.transfer_keep_alive(
runtime_types::sp_runtime::MultiAddress::Id(dest_account),
amount,
);
subxt metadata \
--url wss://westend-rpc.polkadot.io \
--format json \
> westend_metadata.scale
subxt codegen \
--file westend_metadata.scale \
> src/metadata/westend.rs
Create or update src/metadata/mod.rs:
#[cfg(feature = "typed")]
pub mod westend;
#[cfg(feature = "typed")]
pub mod polkadot;
#[cfg(feature = "typed")]
pub mod kusama;
Dynamic API:
let storage_query = subxt::dynamic::storage(
"System",
"Account",
vec![subxt::dynamic::Value::from_bytes(&account_id)],
);
let result = client.storage().at_latest().await?
.fetch(&storage_query).await?;
Typed API:
use crate::metadata::westend;
let account_query = westend::storage()
.system()
.account(&account_id);
let result = client.storage().at_latest().await?
.fetch(&account_query).await?;
// Direct access to typed fields
let free_balance = result.data.free;
let nonce = result.nonce;
use crate::metadata::westend::{self, runtime_types};
// Type-safe transaction building
let tx = westend::tx().balances().transfer_keep_alive(
runtime_types::sp_runtime::MultiAddress::Id(dest),
1_000_000_000_000, // Amount in Planck
);
// Sign and submit
let hash = client
.tx()
.sign_and_submit_then_watch_default(&tx, &signer)
.await?
.wait_for_finalized_success()
.await?;
use crate::metadata::westend;
// Access runtime constants with type safety
let existential_deposit = client
.constants()
.at(&westend::constants().balances().existential_deposit())?;
println!("Existential deposit: {} Planck", existential_deposit);
You can support both dynamic and typed APIs using conditional compilation:
#[cfg(feature = "typed")]
use crate::metadata::westend;
pub async fn transfer(
&self,
from: &Wallet,
to: &str,
amount: u128,
) -> Result<String> {
#[cfg(feature = "typed")]
{
// Use typed API for compile-time safety
use westend::runtime_types;
let dest = runtime_types::sp_runtime::MultiAddress::Id(to_account);
let tx = westend::tx().balances().transfer_keep_alive(dest, amount);
self.submit_typed_extrinsic(&tx, from).await
}
#[cfg(not(feature = "typed"))]
{
// Fallback to dynamic API
let tx = subxt::dynamic::tx("Balances", "transfer_keep_alive", vec![...]);
self.submit_extrinsic(&tx, from).await
}
}
Don’t commit generated metadata files to version control. They are large and chain-specific.
Add to .gitignore:
# Generated metadata
src/metadata/*.rs
src/metadata/*.scale
!src/metadata/mod.rs
Generate metadata during CI builds:
# .github/workflows/ci.yml
- name: Generate Westend Metadata
run: |
cargo install subxt-cli
cd apex-sdk-substrate
./scripts/generate_metadata.sh westend
- name: Build with Typed API
run: cargo build --features typed
Monitor runtime upgrades and regenerate metadata:
Generate metadata for all supported chains:
#!/bin/bash
# generate_all.sh
./scripts/generate_metadata.sh westend
./scripts/generate_metadata.sh polkadot
./scripts/generate_metadata.sh kusama
echo "All metadata generated successfully"
Test with both APIs to ensure compatibility:
# Test dynamic API (default)
cargo test
# Test typed API
cargo test --features typed
Cause: Network issues or endpoint unavailable
Solution:
# Try a different endpoint
./scripts/generate_metadata.sh wss://westend.api.onfinality.io/public-ws
# Or use a local node
./scripts/generate_metadata.sh ws://localhost:9944
Cause: Runtime metadata incompatibility
Solution:
subxt to the latest version: cargo update subxtCause: Generated types don’t match runtime version
Solution:
client.runtime_version().spec_version| Operation | Dynamic API | Typed API | Improvement |
|---|---|---|---|
| Transaction build | ~100μs | ~10μs | 10x faster |
| Storage query | ~80μs | ~15μs | 5x faster |
| Compile time | baseline | +10-30s | N/A |
| Binary size | baseline | +1-5MB | N/A |
| Type safety | Runtime | Compile-time | ∞ better |
For issues or questions: