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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 16 additions & 40 deletions src/chain/extrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2692,9 +2692,9 @@ impl Client {
self.sign_submit(&tx, pair).await
}

// ──────── Registry ────────
// ──────── Identity ────────

/// Set on-chain identity (Registry pallet).
/// Set on-chain identity (`SubtensorModule::set_identity`, keyed by the signer's coldkey).
pub async fn set_registry_identity(
&self,
pair: &sr25519::Pair,
Expand All @@ -2705,52 +2705,28 @@ impl Client {
image: &str,
) -> Result<String> {
use subxt::dynamic::Value;
let data_value = |s: &str| -> Value {
if s.is_empty() {
Value::unnamed_variant("None", [])
} else {
let bytes = s.as_bytes();
let len = bytes.len().min(128);
Value::unnamed_variant(format!("Raw{}", len), [Value::from_bytes(&bytes[..len])])
}
};
let additional_entries: Vec<Value> = [("github", github), ("description", description)]
.into_iter()
.filter(|(_, value)| !value.trim().is_empty())
.map(|(key, value)| Value::unnamed_composite([data_value(key), data_value(value)]))
.collect();
let info = Value::named_composite([
("additional", Value::unnamed_composite(additional_entries)),
("display", data_value(name)),
("legal", Value::unnamed_variant("None", [])),
("web", data_value(url)),
("riot", Value::unnamed_variant("None", [])),
("email", Value::unnamed_variant("None", [])),
("pgp_fingerprint", Value::unnamed_variant("None", [])),
("image", data_value(image)),
("twitter", Value::unnamed_variant("None", [])),
]);
let identified = AccountId::from(pair.public().0);
let bytes = |s: &str| Value::from_bytes(s.as_bytes());
// Fields are plain byte vectors: name, url, github_repo, image, discord, description, additional.
self.submit_raw_call(
pair,
"Registry",
"SubtensorModule",
"set_identity",
vec![Value::from_bytes(identified.0), info],
vec![
bytes(name),
bytes(url),
bytes(github),
bytes(image),
bytes(""),
bytes(description),
bytes(""),
],
)
.await
}

/// Clear on-chain identity (Registry pallet).
/// Clear on-chain identity by writing an empty identity
pub async fn clear_registry_identity(&self, pair: &sr25519::Pair) -> Result<String> {
use subxt::dynamic::Value;
let identified = AccountId::from(pair.public().0);
self.submit_raw_call(
pair,
"Registry",
"clear_identity",
vec![Value::from_bytes(identified.0)],
)
.await
self.set_registry_identity(pair, "", "", "", "", "").await
}

// ──────── Utility: batch variants ────────
Expand Down
19 changes: 3 additions & 16 deletions src/chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1307,8 +1307,6 @@ fn format_dispatch_error(e: subxt::Error) -> anyhow::Error {
|| msg.contains("NotEnoughBalanceToPaySwapColdKey")
{
"Insufficient free balance to pay the hotkey or coldkey swap fee. Fund the signing account and retry."
} else if msg.contains("Registry::NotRegistered") {
"No on-chain identity is registered for this account (pallet Registry). Use `agcli network identity set` or pass the correct SS58."
} else if msg.contains("HotKeyNotRegisteredInNetwork") {
"This hotkey is not registered on any subnet yet. Pick a target netuid and register with `agcli subnet register-neuron --netuid <N>`."
} else if msg.contains("HotKeyNotRegisteredInSubNet") || msg.contains("NotRegistered") {
Expand Down Expand Up @@ -1490,17 +1488,6 @@ fn format_dispatch_error(e: subxt::Error) -> anyhow::Error {
"Pool reserves are too low for this swap or liquidity action — reduce size or wait for liquidity."
} else if msg.contains("UserLiquidityDisabled") {
"Subnet owner disabled user add/remove liquidity on this subnet."
} else if msg.contains("TooManyFieldsInIdentityInfo") {
"On-chain identity has too many additional fields — trim optional fields to the runtime maximum."
} else if msg.contains("Registry::CannotRegister") {
"Registry pallet: identity registration failed requirements (deposit, permissions, or eligibility)."
} else if msg.contains("CannotRegister") {
"Registry identity registration failed requirements (deposit, permissions, or eligibility)."
} else if msg.contains("NotRegistered")
&& !msg.contains("HotKey")
&& !msg.contains("ColdkeySwapAnnouncement")
{
"No on-chain identity registered for this account — register first or use the correct SS58."
} else if msg.contains("DepositTooLow")
|| msg.contains("CapTooLow")
|| msg.contains("MinimumContributionTooLow")
Expand Down Expand Up @@ -1924,13 +1911,13 @@ mod tests {
}

#[test]
fn format_dispatch_error_registry_not_registered_identity_hint() {
let err = subxt::Error::Other("Pallet error: Registry::NotRegistered".to_string());
fn format_dispatch_error_invalid_identity_hint() {
let err = subxt::Error::Other("Pallet error: SubtensorModule::InvalidIdentity".to_string());
let result = format_dispatch_error(err);
let msg = format!("{:#}", result);
assert!(
msg.contains("Hint:") && msg.to_lowercase().contains("identity"),
"expected Registry identity hint, got: {}",
"expected identity validation hint, got: {}",
msg
);
assert!(
Expand Down
55 changes: 16 additions & 39 deletions src/chain/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,13 +377,13 @@ impl Client {

// ──────── Identity Queries ────────

/// Get on-chain identity for an account (from Registry pallet).
/// Get on-chain identity for an account (SubtensorModule `IdentitiesV2`, keyed by coldkey).
pub async fn get_identity(&self, ss58: &str) -> Result<Option<ChainIdentity>> {
let account_id = Self::ss58_to_account_id(ss58)?;
let inner = &self.inner;
let short = crate::utils::short_ss58(ss58);
let result = retry_on_transient("get_identity", RPC_RETRIES, || async {
let addr = api::storage().registry().identity_of(&account_id);
let addr = api::storage().subtensor_module().identities_v2(&account_id);
let r = inner
.storage()
.at_latest()
Expand All @@ -394,7 +394,7 @@ impl Client {
Ok(r)
})
.await?;
Ok(result.map(|reg| chain_identity_from_registration(reg.info)))
Ok(result.map(chain_identity_from_v2))
}

/// Get subnet identity (from SubtensorModule SubnetIdentitiesV3).
Expand Down Expand Up @@ -1015,15 +1015,15 @@ impl Client {
block_hash: subxt::utils::H256,
) -> Result<Option<ChainIdentity>> {
let account_id = Self::ss58_to_account_id(ss58)?;
let addr = api::storage().registry().identity_of(&account_id);
let addr = api::storage().subtensor_module().identities_v2(&account_id);
let result = self
.inner
.storage()
.at(block_hash)
.fetch(&addr)
.await
.map_err(|e| Self::annotate_at_block_error(e.into(), None))?;
Ok(result.map(|reg| chain_identity_from_registration(reg.info)))
Ok(result.map(chain_identity_from_v2))
}

/// Get all subnets at a specific block hash (via runtime API at block).
Expand Down Expand Up @@ -1737,24 +1737,19 @@ impl Client {
}
}

/// Convert a Registry pallet `IdentityInfo` into our `ChainIdentity` struct.
fn chain_identity_from_registration(
info: api::runtime_types::pallet_registry::types::IdentityInfo,
/// Convert SubtensorModule `ChainIdentityV2` (plain UTF-8 byte fields) into our `ChainIdentity`.
fn chain_identity_from_v2(
info: api::runtime_types::pallet_subtensor::pallet::ChainIdentityV2,
) -> ChainIdentity {
let s = |b: &[u8]| String::from_utf8_lossy(b).into_owned();
ChainIdentity {
name: decode_identity_data(&info.display),
url: decode_identity_data(&info.web),
github: String::new(), // Registry pallet doesn't have github field
image: decode_identity_data(&info.image),
discord: decode_identity_data(&info.riot),
description: String::new(),
additional: info
.additional
.0
.iter()
.map(|(k, v)| format!("{}={}", decode_identity_data(k), decode_identity_data(v)))
.collect::<Vec<_>>()
.join(", "),
name: s(&info.name),
url: s(&info.url),
github: s(&info.github_repo),
image: s(&info.image),
discord: s(&info.discord),
description: s(&info.description),
additional: s(&info.additional),
}
}

Expand Down Expand Up @@ -2362,24 +2357,6 @@ fn json_to_ss58_account(value: &serde_json::Value) -> Option<String> {
}
}

fn decode_identity_data(data: &api::runtime_types::pallet_registry::types::Data) -> String {
use api::runtime_types::pallet_registry::types::Data;
macro_rules! raw_to_string {
($($variant:ident),+) => {
match data {
Data::None => String::new(),
$(Data::$variant(b) => String::from_utf8_lossy(b).into_owned(),)+
_ => format!("<hash:{:?}>", data),
}
}
}
raw_to_string!(
Raw0, Raw1, Raw2, Raw3, Raw4, Raw5, Raw6, Raw7, Raw8, Raw9, Raw10, Raw11, Raw12, Raw13,
Raw14, Raw15, Raw16, Raw17, Raw18, Raw19, Raw20, Raw21, Raw22, Raw23, Raw24, Raw25, Raw26,
Raw27, Raw28, Raw29, Raw30, Raw31, Raw32
)
}

/// Crowdloan contributors map name for this runtime (`Contributions` vs legacy `Contributors`).
fn crowdloan_contributors_storage_item(metadata: &subxt::Metadata) -> Result<&'static str> {
const PRIMARY: &str = "Contributions";
Expand Down
16 changes: 6 additions & 10 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ const SURFACED_DISPATCH_PALLETS: &[&str] = &[
"swap",
"drand",
"utility",
"registry",
"shield",
];

Expand Down Expand Up @@ -312,11 +311,8 @@ pub fn classify(err: &anyhow::Error) -> i32 {
|| msg.contains("swap::insufficientbalance")
|| msg.contains("swap::subtokendisabled")
|| msg.contains("swap::insufficientliquidity")
// Registry identity pallet
|| msg.contains("registry::notregistered")
|| msg.contains("registry::cannotregister")
|| msg.contains("cannotregister")
|| msg.contains("toomanyfieldsinidentityinfo")
// Identity (SubtensorModule::set_identity)
|| msg.contains("invalididentity")
// Crowdloan pallet
|| msg.contains("deposittoolow")
|| msg.contains("captoolow")
Expand Down Expand Up @@ -436,8 +432,8 @@ pub fn hint(code: i32, msg: &str) -> Option<&'static str> {
Some("Tip: Subtensor liquidity constraint (stake/swap path) — try a smaller amount or looser slippage; not always the same as DEX pool depth")
} else if lower.contains("swap::subtokendisabled") {
Some("Tip: This subnet’s subtoken mode is not enabled for the on-chain AMM — check subnet token/subtoken config before swaps")
} else if lower.contains("registry::notregistered") {
Some("Tip: Set on-chain identity first (`agcli network identity set`) or use an SS58 that already has one")
} else if lower.contains("invalididentity") {
Some("Tip: Identity field too long or malformed — shorten name/url/description and retry `agcli network identity set`")
} else if lower.contains("insufficient") {
Some("Tip: Check your balance with `agcli balance`. Transaction fees require a small reserve")
} else if lower.contains("delegatetxratelimitexceeded") {
Expand Down Expand Up @@ -1378,8 +1374,8 @@ mod tests {
}

#[test]
fn hint_chain_registry_not_registered() {
let h = hint(exit_code::CHAIN, "Registry::NotRegistered");
fn hint_chain_invalid_identity() {
let h = hint(exit_code::CHAIN, "SubtensorModule::InvalidIdentity");
assert!(h.is_some_and(|s| s.contains("identity")));
}

Expand Down
2 changes: 1 addition & 1 deletion src/types/chain_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ pub struct SubnetHyperparameters {
pub weights_version: u64,
pub weights_rate_limit: u64,
pub adjustment_interval: u16,
pub activity_cutoff: u16,
pub activity_cutoff: u64,
pub registration_allowed: bool,
pub target_regs_per_interval: u16,
pub min_burn: Balance,
Expand Down
48 changes: 23 additions & 25 deletions tests/extrinsic_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,36 +50,34 @@ fn proxy_announce_differs_from_raw_account_bytes() {
}

#[test]
fn registry_set_identity_includes_two_top_level_fields() {
let identified = [9u8; 32];
let info = Value::named_composite([
(
"display",
Value::unnamed_variant("Raw4", [Value::from_bytes(b"test")]),
),
("legal", Value::unnamed_variant("None", [])),
("web", Value::unnamed_variant("None", [])),
("riot", Value::unnamed_variant("None", [])),
("email", Value::unnamed_variant("None", [])),
("pgp_fingerprint", Value::unnamed_variant("None", [])),
("image", Value::unnamed_variant("None", [])),
("twitter", Value::unnamed_variant("None", [])),
("additional", Value::unnamed_composite([])),
]);
let with_info = encode_dynamic(
"Registry",
"set_identity",
vec![Value::from_bytes(identified), info],
);
fn set_identity_encodes_seven_flat_byte_fields() {
// SubtensorModule::set_identity takes seven plain Vec<u8> fields keyed by the
// signer's coldkey — name, url, github_repo, image, discord, description, additional.
let bytes = |s: &[u8]| Value::from_bytes(s);
let full = vec![
bytes(b"name"),
bytes(b"url"),
bytes(b"github"),
bytes(b"image"),
bytes(b""),
bytes(b"desc"),
bytes(b""),
];
let with_info = encode_dynamic("SubtensorModule", "set_identity", full);
assert!(!with_info.is_empty());
assert!(
encode_dynamic_panics(
"Registry",
"SubtensorModule",
"set_identity",
vec![Value::from_bytes(identified)],
vec![bytes(b"name"), bytes(b"url")],
),
"set_identity must include identity info as second arg"
"set_identity must encode all seven identity fields"
);
// The standalone Registry pallet was removed from the runtime.
assert!(
encode_dynamic_panics("Registry", "set_identity", Vec::<Value>::new()),
"Registry pallet should no longer exist in metadata"
);
assert!(!with_info.is_empty());
}

#[test]
Expand Down