Skip to content

Commit 8ca0f9f

Browse files
authored
SS58 updates (#39)
1 parent c521085 commit 8ca0f9f

10 files changed

Lines changed: 157 additions & 35 deletions

File tree

examples/wallet_ops.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl QuantusApp {
7676
let keypair = wallet_data.keypair;
7777

7878
// Parse recipient address
79-
let to_account_id = AccountId32::from_ss58check(to_address)
79+
let (to_account_id, _) = AccountId32::from_ss58check_with_version(to_address)
8080
.map_err(|e| QuantusError::Generic(format!("Invalid recipient address: {e}")))?;
8181

8282
// Perform the transfer

src/cli/block.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -769,8 +769,9 @@ async fn get_account_nonce_at_block(
769769
block_hash: subxt::utils::H256,
770770
) -> crate::error::Result<u32> {
771771
// Parse the SS58 address to AccountId32 (sp-core)
772-
let account_id_sp = sp_core::crypto::AccountId32::from_ss58check(account_address)
773-
.map_err(|e| QuantusError::NetworkError(format!("Invalid SS58 address: {e:?}")))?;
772+
let (account_id_sp, _) =
773+
sp_core::crypto::AccountId32::from_ss58check_with_version(account_address)
774+
.map_err(|e| QuantusError::NetworkError(format!("Invalid SS58 address: {e:?}")))?;
774775

775776
// Convert to subxt_core AccountId32 for storage query
776777
let account_bytes: [u8; 32] = *account_id_sp.as_ref();

src/cli/common.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use sp_core::crypto::{AccountId32, Ss58Codec};
77
/// If it's already an SS58 address, return it as is
88
pub fn resolve_address(address_or_wallet_name: &str) -> Result<String> {
99
// First, try to parse as SS58 address
10-
if AccountId32::from_ss58check(address_or_wallet_name).is_ok() {
10+
if AccountId32::from_ss58check_with_version(address_or_wallet_name).is_ok() {
1111
// It's a valid SS58 address, return as is
1212
return Ok(address_or_wallet_name.to_string());
1313
}
@@ -36,10 +36,10 @@ pub async fn get_fresh_nonce_with_client(
3636
quantus_client: &crate::chain::client::QuantusClient,
3737
from_keypair: &crate::wallet::QuantumKeyPair,
3838
) -> Result<u64> {
39-
let from_account_id = AccountId32::from_ss58check(&from_keypair.to_account_id_ss58check())
40-
.map_err(|e| {
41-
crate::error::QuantusError::NetworkError(format!("Invalid from address: {e:?}"))
42-
})?;
39+
let (from_account_id, _version) =
40+
AccountId32::from_ss58check_with_version(&from_keypair.to_account_id_ss58check()).map_err(
41+
|e| crate::error::QuantusError::NetworkError(format!("Invalid from address: {e:?}")),
42+
)?;
4343

4444
// Get nonce from the latest block (best block)
4545
let latest_nonce = quantus_client
@@ -83,10 +83,10 @@ pub async fn get_incremented_nonce_with_client(
8383
from_keypair: &crate::wallet::QuantumKeyPair,
8484
base_nonce: u64,
8585
) -> Result<u64> {
86-
let from_account_id = AccountId32::from_ss58check(&from_keypair.to_account_id_ss58check())
87-
.map_err(|e| {
88-
crate::error::QuantusError::NetworkError(format!("Invalid from address: {e:?}"))
89-
})?;
86+
let (from_account_id, _version) =
87+
AccountId32::from_ss58check_with_version(&from_keypair.to_account_id_ss58check()).map_err(
88+
|e| crate::error::QuantusError::NetworkError(format!("Invalid from address: {e:?}")),
89+
)?;
9090

9191
// Get current nonce from the latest block
9292
let current_nonce = quantus_client

src/cli/generic_call.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ async fn submit_balance_transfer(
132132
})?;
133133

134134
// Convert to AccountId32
135-
let to_account_id = AccountId32::from_ss58check(to_address)
135+
let (to_account_id, _) = AccountId32::from_ss58check_with_version(to_address)
136136
.map_err(|e| QuantusError::Generic(format!("Invalid to_address: {e:?}")))?;
137137

138138
// Convert to subxt_core AccountId32
@@ -209,7 +209,7 @@ async fn submit_tech_collective_add_member(
209209
QuantusError::Generic("Argument must be a string (member_address)".to_string())
210210
})?;
211211

212-
let member_account_id = AccountId32::from_ss58check(member_address)
212+
let (member_account_id, _) = AccountId32::from_ss58check_with_version(member_address)
213213
.map_err(|e| QuantusError::Generic(format!("Invalid member_address: {e:?}")))?;
214214

215215
// Convert to subxt_core AccountId32
@@ -243,7 +243,7 @@ async fn submit_tech_collective_remove_member(
243243
QuantusError::Generic("Argument must be a string (member_address)".to_string())
244244
})?;
245245

246-
let member_account_id = AccountId32::from_ss58check(member_address)
246+
let (member_account_id, _) = AccountId32::from_ss58check_with_version(member_address)
247247
.map_err(|e| QuantusError::Generic(format!("Invalid member_address: {e:?}")))?;
248248

249249
// Convert to subxt_core AccountId32
@@ -303,7 +303,7 @@ async fn submit_reversible_transfer(
303303
QuantusError::Generic("Second argument must be a number (amount)".to_string())
304304
})?;
305305

306-
let to_account_id = AccountId32::from_ss58check(to_address)
306+
let (to_account_id, _) = AccountId32::from_ss58check_with_version(to_address)
307307
.map_err(|e| QuantusError::Generic(format!("Invalid to_address: {e:?}")))?;
308308

309309
// Convert to subxt_core AccountId32

src/cli/reversible.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,10 @@ pub async fn schedule_transfer(
121121
log_verbose!(" Amount: {}", amount);
122122

123123
// Parse the destination address
124-
let to_account_id_sp = SpAccountId32::from_ss58check(to_address).map_err(|e| {
125-
crate::error::QuantusError::NetworkError(format!("Invalid destination address: {e:?}"))
126-
})?;
124+
let (to_account_id_sp, _version) = SpAccountId32::from_ss58check_with_version(to_address)
125+
.map_err(|e| {
126+
crate::error::QuantusError::NetworkError(format!("Invalid destination address: {e:?}"))
127+
})?;
127128

128129
// Convert to subxt_core AccountId32
129130
let to_account_id_bytes: [u8; 32] = *to_account_id_sp.as_ref();

src/cli/send.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ pub async fn get_balance(quantus_client: &QuantusClient, account_address: &str)
1414
log_verbose!("💰 Querying balance for account: {}", account_address.bright_green());
1515

1616
// Decode the SS58 address into `AccountId32` (sp-core) first …
17-
let account_id_sp = SpAccountId32::from_ss58check(account_address).map_err(|e| {
18-
crate::error::QuantusError::Generic(format!(
19-
"Invalid account address '{account_address}': {e:?}"
20-
))
21-
})?;
17+
let (account_id_sp, _) =
18+
SpAccountId32::from_ss58check_with_version(account_address).map_err(|e| {
19+
crate::error::QuantusError::Generic(format!(
20+
"Invalid account address '{account_address}': {e:?}"
21+
))
22+
})?;
2223

2324
// … then convert into the `subxt` representation expected by the generated API.
2425
let bytes: [u8; 32] = *account_id_sp.as_ref();
@@ -178,9 +179,10 @@ pub async fn transfer_with_nonce(
178179
log_verbose!(" Resolved to: {}", resolved_address.bright_green());
179180

180181
// Parse the destination address
181-
let to_account_id_sp = SpAccountId32::from_ss58check(&resolved_address).map_err(|e| {
182-
crate::error::QuantusError::NetworkError(format!("Invalid destination address: {e:?}"))
183-
})?;
182+
let (to_account_id_sp, _) = SpAccountId32::from_ss58check_with_version(&resolved_address)
183+
.map_err(|e| {
184+
crate::error::QuantusError::NetworkError(format!("Invalid destination address: {e:?}"))
185+
})?;
184186

185187
// Convert to subxt_core AccountId32
186188
let to_account_id_bytes: [u8; 32] = *to_account_id_sp.as_ref();

src/cli/tech_collective.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ pub async fn add_member(
114114
log_verbose!(" Member: {}", who_address.bright_cyan());
115115

116116
// Parse the member address
117-
let member_account_sp = AccountId32::from_ss58check(who_address)
117+
let (member_account_sp, _) = AccountId32::from_ss58check_with_version(who_address)
118118
.map_err(|e| QuantusError::Generic(format!("Invalid member address: {e:?}")))?;
119119

120120
// Convert to subxt_core AccountId32
@@ -152,7 +152,7 @@ pub async fn remove_member(
152152
log_verbose!(" Member: {}", who_address.bright_cyan());
153153

154154
// Parse the member address
155-
let member_account_sp = AccountId32::from_ss58check(who_address)
155+
let (member_account_sp, _) = AccountId32::from_ss58check_with_version(who_address)
156156
.map_err(|e| QuantusError::Generic(format!("Invalid member address: {e:?}")))?;
157157

158158
// Convert to subxt_core AccountId32
@@ -215,7 +215,7 @@ pub async fn is_member(
215215
log_verbose!(" Address: {}", address.bright_cyan());
216216

217217
// Parse the address
218-
let account_sp = AccountId32::from_ss58check(address)
218+
let (account_sp, _) = AccountId32::from_ss58check_with_version(address)
219219
.map_err(|e| QuantusError::Generic(format!("Invalid address: {e:?}")))?;
220220

221221
// Convert to subxt_core AccountId32

src/cli/wallet.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,21 @@ pub enum WalletCommands {
6565
password: Option<String>,
6666
},
6767

68+
/// Create wallet from 32-byte seed
69+
FromSeed {
70+
/// Wallet name
71+
#[arg(short, long)]
72+
name: String,
73+
74+
/// 32-byte seed in hex format (64 hex characters)
75+
#[arg(short, long)]
76+
seed: String,
77+
78+
/// Password to encrypt the wallet (optional, will prompt if not provided)
79+
#[arg(short, long)]
80+
password: Option<String>,
81+
},
82+
6883
/// List all wallets
6984
List,
7085

@@ -103,7 +118,7 @@ pub async fn get_account_nonce(
103118
log_verbose!("#️⃣ Querying nonce for account: {}", account_address.bright_green());
104119

105120
// Parse the SS58 address to AccountId32 (sp-core)
106-
let account_id_sp = AccountId32::from_ss58check(account_address)
121+
let (account_id_sp, _) = AccountId32::from_ss58check_with_version(account_address)
107122
.map_err(|e| QuantusError::NetworkError(format!("Invalid SS58 address: {e:?}")))?;
108123

109124
log_verbose!("🔍 SP Account ID: {:?}", account_id_sp);
@@ -326,6 +341,38 @@ pub async fn handle_wallet_command(
326341
Ok(())
327342
},
328343

344+
WalletCommands::FromSeed { name, seed, password } => {
345+
log_print!("🌱 Creating wallet from seed...");
346+
347+
let wallet_manager = WalletManager::new()?;
348+
349+
// Get password from user if not provided
350+
let final_password =
351+
crate::wallet::password::get_wallet_password(&name, password, None)?;
352+
353+
match wallet_manager
354+
.create_wallet_from_seed(&name, &seed, Some(&final_password))
355+
.await
356+
{
357+
Ok(wallet_info) => {
358+
log_success!("Wallet name: {}", name.bright_green());
359+
log_success!("Address: {}", wallet_info.address.bright_cyan());
360+
log_success!("Key type: {}", wallet_info.key_type.bright_yellow());
361+
log_success!(
362+
"Created: {}",
363+
wallet_info.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string().dimmed()
364+
);
365+
log_success!("✅ Wallet created from seed successfully!");
366+
},
367+
Err(e) => {
368+
log_error!("{}", format!("❌ Failed to create wallet from seed: {e}").red());
369+
return Err(e);
370+
},
371+
}
372+
373+
Ok(())
374+
},
375+
329376
WalletCommands::List => {
330377
log_print!("📋 Listing all wallets...");
331378

src/wallet/keystore.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ use crate::error::{Result, WalletError};
88
use qp_rusty_crystals_dilithium::ml_dsa_87::{Keypair, PublicKey, SecretKey};
99
use serde::{Deserialize, Serialize};
1010
use sp_core::{
11-
crypto::{AccountId32, Ss58Codec},
11+
crypto::{AccountId32, Ss58AddressFormat, Ss58Codec},
1212
ByteArray,
1313
};
14-
1514
// Quantum-safe encryption imports
1615
use aes_gcm::{
1716
aead::{Aead, AeadCore, KeyInit, OsRng as AesOsRng},
@@ -80,7 +79,7 @@ impl QuantumKeyPair {
8079

8180
pub fn to_account_id_ss58check(&self) -> String {
8281
let account = self.to_account_id_32();
83-
account.to_ss58check()
82+
account.to_ss58check_with_version(Ss58AddressFormat::custom(189))
8483
}
8584

8685
/// Convert to subxt Signer for use
@@ -96,7 +95,7 @@ impl QuantumKeyPair {
9695
// from_ss58check returns a Result, we unwrap it to panic on invalid input.
9796
// We then convert the AccountId32 struct to a Vec<u8> to be compatible with Polkadart's
9897
// typedef.
99-
AsRef::<[u8]>::as_ref(&AccountId32::from_ss58check(s).unwrap()).to_vec()
98+
AsRef::<[u8]>::as_ref(&AccountId32::from_ss58check_with_version(s).unwrap().0).to_vec()
10099
}
101100
}
102101

src/wallet/mod.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,78 @@ impl WalletManager {
215215
})
216216
}
217217

218+
/// Create wallet from 32-byte seed
219+
pub async fn create_wallet_from_seed(
220+
&self,
221+
name: &str,
222+
seed_hex: &str,
223+
password: Option<&str>,
224+
) -> Result<WalletInfo> {
225+
// Check if wallet already exists
226+
let keystore = Keystore::new(&self.wallets_dir);
227+
if keystore.load_wallet(name)?.is_some() {
228+
return Err(WalletError::AlreadyExists.into());
229+
}
230+
231+
// Validate seed hex format (should be 64 hex characters for 32 bytes)
232+
if seed_hex.len() != 64 {
233+
return Err(WalletError::InvalidMnemonic.into()); // Reusing error type
234+
}
235+
236+
// Convert hex to bytes
237+
let seed_bytes = hex::decode(seed_hex).map_err(|_| WalletError::InvalidMnemonic)?;
238+
if seed_bytes.len() != 32 {
239+
return Err(WalletError::InvalidMnemonic.into());
240+
}
241+
242+
// Create DilithiumPair from seed
243+
let seed_array: [u8; 32] =
244+
seed_bytes.try_into().map_err(|_| WalletError::InvalidMnemonic)?;
245+
246+
println!("Debug: seed_array length: {}", seed_array.len());
247+
println!("Debug: seed_hex: {}", seed_hex);
248+
println!("Debug: calling DilithiumPair::from_seed");
249+
250+
let dilithium_pair = qp_dilithium_crypto::types::DilithiumPair::from_seed(&seed_array)
251+
.map_err(|e| {
252+
println!("Debug: DilithiumPair::from_seed failed with error: {:?}", e);
253+
WalletError::InvalidMnemonic
254+
})?;
255+
256+
println!("Debug: DilithiumPair created successfully");
257+
258+
// Convert to QuantumKeyPair
259+
let quantum_keypair = QuantumKeyPair::from_resonance_pair(&dilithium_pair);
260+
261+
// Create wallet data
262+
let mut metadata = std::collections::HashMap::new();
263+
metadata.insert("version".to_string(), "1.0.0".to_string());
264+
metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
265+
metadata.insert("from_seed".to_string(), "true".to_string());
266+
267+
// Generate address from public key
268+
let address = quantum_keypair.to_account_id_ss58check();
269+
270+
let wallet_data = WalletData {
271+
name: name.to_string(),
272+
keypair: quantum_keypair,
273+
mnemonic: None, // No mnemonic for seed-based wallets
274+
metadata,
275+
};
276+
277+
// Encrypt and save the wallet
278+
let password = password.unwrap_or(""); // Use empty password if none provided
279+
let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
280+
keystore.save_wallet(&encrypted_wallet)?;
281+
282+
Ok(WalletInfo {
283+
name: name.to_string(),
284+
address,
285+
created_at: encrypted_wallet.created_at,
286+
key_type: "Dilithium ML-DSA-87".to_string(),
287+
})
288+
}
289+
218290
/// Get wallet by name with password for decryption
219291
pub fn get_wallet(&self, name: &str, password: Option<&str>) -> Result<Option<WalletInfo>> {
220292
let keystore = Keystore::new(&self.wallets_dir);

0 commit comments

Comments
 (0)