cli tool fixes
This commit is contained in:
parent
4fad309fc2
commit
12916c0b11
|
|
@ -847,7 +847,7 @@ Expected reply:
|
|||
]
|
||||
```
|
||||
|
||||
### lookup_contract_by_hash
|
||||
### lookup_loan_by_hash
|
||||
|
||||
Looks up a loan contract by contract hash.
|
||||
|
||||
|
|
@ -860,16 +860,20 @@ Requires:
|
|||
Usage:
|
||||
|
||||
```text
|
||||
lookup_contract_by_hash <contract_hash>
|
||||
lookup_loan_by_hash <loan_hash>
|
||||
```
|
||||
|
||||
Expected reply:
|
||||
|
||||
```text
|
||||
<contract data or peer error>
|
||||
```json
|
||||
{
|
||||
"contract": "<loan hash>",
|
||||
"status": "active",
|
||||
"payments": []
|
||||
}
|
||||
```
|
||||
|
||||
### lookup_contract_by_address
|
||||
### lookup_loan_by_address
|
||||
|
||||
Looks up loan contracts by wallet address.
|
||||
|
||||
|
|
@ -882,13 +886,17 @@ Requires:
|
|||
Usage:
|
||||
|
||||
```text
|
||||
lookup_contract_by_address <wallet_address>
|
||||
lookup_loan_by_address <wallet_address>
|
||||
```
|
||||
|
||||
Expected reply:
|
||||
|
||||
```text
|
||||
<contract data or peer error>
|
||||
```json
|
||||
{
|
||||
"address": "<wallet address>",
|
||||
"contracts": 1,
|
||||
"loans": []
|
||||
}
|
||||
```
|
||||
|
||||
### lookup_remote_balance
|
||||
|
|
|
|||
|
|
@ -139,5 +139,5 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
println!("transaction: {output_str}");
|
||||
println!("{output_str}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,5 +115,5 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
println!("transaction: {output_str}");
|
||||
println!("{output_str}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,5 +123,5 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
println!("transaction: {output_str}");
|
||||
println!("{output_str}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,5 +137,5 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
println!("transaction: {output_str}");
|
||||
println!("{output_str}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,5 +256,5 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
println!("transaction: {output_str}");
|
||||
println!("{output_str}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,5 +174,5 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
println!("transaction: {output_str}");
|
||||
println!("{output_str}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,5 +156,5 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
println!("transaction: {output_str}");
|
||||
println!("{output_str}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,5 +237,5 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
println!("transaction: {output_str}");
|
||||
println!("{output_str}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,5 +143,5 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
println!("transaction: {output_str}");
|
||||
println!("{output_str}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -246,5 +246,5 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
println!("transaction: {output_str}");
|
||||
println!("{output_str}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,5 +169,5 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
println!("transaction: {output_str}");
|
||||
println!("{output_str}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::records::unpack_block::load_by_binary_data::load_block_from_binary;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
|
|
@ -72,7 +73,11 @@ async fn main() {
|
|||
println!("{trimmed}");
|
||||
connected = true;
|
||||
} else if !response.is_empty() {
|
||||
println!("{}", hex::encode(response));
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "hex": hex::encode(response) }))
|
||||
.unwrap()
|
||||
);
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::records::unpack_block::load_by_binary_data::load_block_from_binary;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
|
|
@ -76,7 +77,11 @@ async fn main() {
|
|||
println!("{trimmed}");
|
||||
connected = true;
|
||||
} else if !response.is_empty() {
|
||||
println!("{}", hex::encode(response));
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "hex": hex::encode(response) }))
|
||||
.unwrap()
|
||||
);
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path
|
|||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::encode;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
||||
|
|
@ -79,7 +80,10 @@ async fn main() {
|
|||
println!("{trimmed}");
|
||||
connected = true;
|
||||
} else if !response.is_empty() {
|
||||
println!("{}", encode(response));
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "hex": encode(response) })).unwrap()
|
||||
);
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path
|
|||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::encode;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
|
||||
|
|
@ -64,7 +65,10 @@ async fn main() {
|
|||
println!("{trimmed}");
|
||||
connected = true;
|
||||
} else if !response.is_empty() {
|
||||
println!("{}", encode(response));
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "hex": encode(response) })).unwrap()
|
||||
);
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
|
||||
|
|
@ -45,7 +46,10 @@ async fn main() {
|
|||
// The difficulty reply carries 4 bytes of header plus an 8-byte u64 difficulty.
|
||||
if response.len() == 12 {
|
||||
let difficulty = u64::from_le_bytes(response[4..12].try_into().unwrap());
|
||||
println!("{difficulty}");
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "difficulty": difficulty })).unwrap()
|
||||
);
|
||||
connected = true;
|
||||
} else {
|
||||
let response_text = String::from_utf8_lossy(&response);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
|
||||
|
|
@ -53,7 +54,10 @@ async fn main() {
|
|||
Ok(response) => {
|
||||
if response.len() == 4 {
|
||||
let height = u32::from_le_bytes(response[..4].try_into().unwrap());
|
||||
println!("{height}");
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "height": height })).unwrap()
|
||||
);
|
||||
connected = true;
|
||||
} else {
|
||||
let response_text = String::from_utf8_lossy(&response);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
|
||||
|
|
@ -58,15 +59,21 @@ async fn main() {
|
|||
match result {
|
||||
Ok(response) => {
|
||||
if response.is_empty() {
|
||||
println!("0");
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "largest_tx_fee": "0.00000000" }))
|
||||
.unwrap()
|
||||
);
|
||||
connected = true;
|
||||
} else if response.len() == 8 {
|
||||
let fee = u64::from_le_bytes(response[0..8].try_into().unwrap());
|
||||
if fee == 0 {
|
||||
println!("0");
|
||||
} else {
|
||||
println!("{}", format_balance(fee));
|
||||
}
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(
|
||||
&json!({ "largest_tx_fee": format_balance(fee) })
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
connected = true;
|
||||
} else {
|
||||
let response_text = String::from_utf8_lossy(&response);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::encode;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
use blockchain::standalone_tools::loan_lookup_json::parse_loan_lookup_by_address;
|
||||
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Command 37 asks a peer for contract records tied to a wallet address.
|
||||
let hashmap_key = generate_uid();
|
||||
let rpc_command = 37;
|
||||
|
||||
// The address can be a long, short, or vanity address; request encoding normalizes it later.
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() != 2 {
|
||||
println!("Usage: ./lookup_loan_by_address <wallet_address>");
|
||||
return;
|
||||
}
|
||||
|
||||
let wallet_address = match args[1].parse::<String>() {
|
||||
Ok(address) => address,
|
||||
Err(_) => {
|
||||
println!("Please enter a wallet address");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
"Wallet key cannot be empty. Please try again.",
|
||||
)
|
||||
.await;
|
||||
let wallet = match Wallet::try_obtain_wallet(encryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let wallet_address = match resolve_wallet_address_input(&wallet_address, &wallet).await {
|
||||
Ok(address) => address,
|
||||
Err(err) => {
|
||||
eprintln!("wallet address is not valid: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Try each configured peer until one returns a response.
|
||||
let connections = get_connections().await;
|
||||
let mut connected = false;
|
||||
|
||||
for conn in connections {
|
||||
if connected {
|
||||
break;
|
||||
}
|
||||
|
||||
let socket_address = conn.parse().expect("Failed to parse the socket address");
|
||||
let result = handshake::connect_and_handshake(
|
||||
socket_address,
|
||||
wallet_address.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletParts {
|
||||
public_key: wallet.saved.public_key.clone(),
|
||||
private_key: wallet.saved.private_key.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(response) => {
|
||||
// Loan lookups return a key/value text summary; print successful data as JSON.
|
||||
let response_text = String::from_utf8_lossy(&response);
|
||||
let trimmed = response_text.trim();
|
||||
if !trimmed.is_empty() {
|
||||
if trimmed.starts_with("error:") {
|
||||
println!("{trimmed}");
|
||||
} else {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&parse_loan_lookup_by_address(trimmed))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
connected = true;
|
||||
} else if !response.is_empty() {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "hex": encode(response) })).unwrap()
|
||||
);
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !connected {
|
||||
eprintln!("failed to connect");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::encode;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
use blockchain::standalone_tools::loan_lookup_json::parse_loan_summary;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Command 33 asks a peer for one loan contract by its 32-byte hash.
|
||||
let hashmap_key = generate_uid();
|
||||
let rpc_command = 33;
|
||||
|
||||
// The hash is passed as text here and encoded into binary request bytes later.
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() != 2 {
|
||||
println!("Usage: ./lookup_loan_by_hash <loan_hash>");
|
||||
return;
|
||||
}
|
||||
|
||||
let contract_hash = match args[1].parse::<String>() {
|
||||
Ok(hash) => hash,
|
||||
Err(_) => {
|
||||
println!("Please enter a contract hash");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
"Wallet key cannot be empty. Please try again.",
|
||||
)
|
||||
.await;
|
||||
|
||||
// Try each configured peer until one returns a response.
|
||||
let connections = get_connections().await;
|
||||
let mut connected = false;
|
||||
|
||||
for conn in connections {
|
||||
if connected {
|
||||
break;
|
||||
}
|
||||
|
||||
let socket_address = conn.parse().expect("Failed to parse the socket address");
|
||||
let result = handshake::connect_and_handshake(
|
||||
socket_address,
|
||||
contract_hash.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(response) => {
|
||||
// Loan lookups return a key/value text summary; print successful data as JSON.
|
||||
let response_text = String::from_utf8_lossy(&response);
|
||||
let trimmed = response_text.trim();
|
||||
if !trimmed.is_empty() {
|
||||
if trimmed.starts_with("error:") {
|
||||
println!("{trimmed}");
|
||||
} else {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&parse_loan_summary(trimmed)).unwrap()
|
||||
);
|
||||
}
|
||||
connected = true;
|
||||
} else if !response.is_empty() {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "hex": encode(response) })).unwrap()
|
||||
);
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !connected {
|
||||
eprintln!("failed to connect");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
use blockchain::env;
|
||||
use blockchain::fs;
|
||||
use blockchain::json;
|
||||
use blockchain::tilde;
|
||||
use rustyline::completion::FilenameCompleter;
|
||||
use rustyline::error::ReadlineError;
|
||||
|
|
@ -85,5 +86,11 @@ async fn main() {
|
|||
buffer.copy_from_slice(&bytes[..8]);
|
||||
let value = u64::from_le_bytes(buffer);
|
||||
|
||||
println!("{}", format_balance(value));
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"balance": format_balance(value)
|
||||
}))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use blockchain::common::types::{
|
|||
ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
||||
};
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::rpc::command_maps;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
|
|
@ -154,12 +155,23 @@ async fn main() {
|
|||
match result {
|
||||
Ok(response) => {
|
||||
if response.is_empty() {
|
||||
println!("[]");
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "transactions": [] })).unwrap()
|
||||
);
|
||||
connected = true;
|
||||
} else if let Some(transactions) = decode_mempool_transactions(&response).await {
|
||||
let mut output = Vec::new();
|
||||
for transaction in transactions {
|
||||
println!("{transaction}");
|
||||
match serde_json::from_str::<serde_json::Value>(&transaction) {
|
||||
Ok(value) => output.push(value),
|
||||
Err(_) => output.push(json!(transaction)),
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "transactions": output })).unwrap()
|
||||
);
|
||||
connected = true;
|
||||
} else {
|
||||
// Text errors are printed directly; unknown binary is shown as hex for inspection.
|
||||
|
|
@ -168,7 +180,11 @@ async fn main() {
|
|||
if !trimmed.is_empty() {
|
||||
println!("{trimmed}");
|
||||
} else {
|
||||
println!("{}", hex::encode(response));
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "hex": hex::encode(response) }))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
connected = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use blockchain::common::types::{
|
|||
ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
||||
};
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
use blockchain::to_string_pretty;
|
||||
|
|
@ -132,7 +133,11 @@ async fn main() {
|
|||
if !trimmed.is_empty() {
|
||||
println!("{trimmed}");
|
||||
} else {
|
||||
println!("{}", hex::encode(response));
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "hex": hex::encode(response) }))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
connected = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
|
||||
|
|
@ -49,7 +50,10 @@ async fn main() {
|
|||
Ok(response) if response.len() >= 4 => {
|
||||
// The mempool count command returns a 4-byte little-endian integer.
|
||||
let count = u32::from_le_bytes(response[0..4].try_into().unwrap());
|
||||
println!("{count}");
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "mempool_tx_count": count })).unwrap()
|
||||
);
|
||||
connected = true;
|
||||
}
|
||||
Ok(response) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
use blockchain::{TimeZone, Utc};
|
||||
|
|
@ -54,8 +55,14 @@ async fn main() {
|
|||
.single()
|
||||
.map(|datetime| datetime.format("%H:%M:%S UTC").to_string())
|
||||
.unwrap_or_else(|| "invalid UTC timestamp".to_string());
|
||||
println!("timestamp: {timestamp}");
|
||||
println!("time: {time}");
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"timestamp": timestamp,
|
||||
"time": time
|
||||
}))
|
||||
.unwrap()
|
||||
);
|
||||
connected = true;
|
||||
}
|
||||
Ok(response) => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path
|
|||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::from_str;
|
||||
use blockchain::json;
|
||||
use blockchain::read_to_string;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
|
|
@ -157,23 +158,31 @@ async fn main() {
|
|||
Ok(response) => {
|
||||
if response.is_empty() {
|
||||
connected = true;
|
||||
println!("[]");
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "balances": [] })).unwrap()
|
||||
);
|
||||
break;
|
||||
}
|
||||
// Balance rows are 15 bytes asset name, 4 bytes NFT series, and 8 bytes balance.
|
||||
if response.len() % 27 == 0 {
|
||||
let mut balances = Vec::new();
|
||||
for entry in response.chunks_exact(27) {
|
||||
let name_bytes = &entry[..15];
|
||||
let nft_series = u32::from_le_bytes(entry[15..19].try_into().unwrap());
|
||||
let balance = u64::from_le_bytes(entry[19..27].try_into().unwrap());
|
||||
let asset_name = String::from_utf8_lossy(name_bytes).trim_end().to_string();
|
||||
let formatted_balance = format_balance(balance);
|
||||
if nft_series > 0 {
|
||||
println!("{asset_name}:{nft_series} = {formatted_balance}");
|
||||
} else {
|
||||
println!("{asset_name} = {formatted_balance}");
|
||||
}
|
||||
balances.push(json!({
|
||||
"asset": asset_name,
|
||||
"nft_series": nft_series,
|
||||
"balance": formatted_balance
|
||||
}));
|
||||
}
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "balances": balances })).unwrap()
|
||||
);
|
||||
connected = true;
|
||||
} else {
|
||||
let response_text = String::from_utf8_lossy(&response);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
use blockchain::to_string_pretty;
|
||||
|
|
@ -79,7 +80,11 @@ async fn main() {
|
|||
println!("{trimmed}");
|
||||
connected = true;
|
||||
} else if !response.is_empty() {
|
||||
println!("{}", hex::encode(response));
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "hex": hex::encode(response) }))
|
||||
.unwrap()
|
||||
);
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use blockchain::common::types::{
|
|||
MARKETING_TYPE, REWARDS_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
||||
};
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
use blockchain::to_string_pretty;
|
||||
|
|
@ -89,7 +90,11 @@ async fn main() {
|
|||
println!("{trimmed}");
|
||||
connected = true;
|
||||
} else if !response.is_empty() {
|
||||
println!("{}", hex::encode(response));
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({ "hex": hex::encode(response) }))
|
||||
.unwrap()
|
||||
);
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -163,5 +168,10 @@ async fn decode_transaction_to_json(response: &[u8]) -> Option<String> {
|
|||
_ => return None,
|
||||
};
|
||||
|
||||
Some(format!("block: {block_number}\n{json}"))
|
||||
let transaction = serde_json::from_str::<serde_json::Value>(&json).ok()?;
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"block": block_number,
|
||||
"transaction": transaction
|
||||
}))
|
||||
.ok()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use blockchain::common::binary_conversions::hex_to_u64;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
|
||||
use blockchain::common::skein::{skein_128_hash_bytes, skein_256_hash_data};
|
||||
use blockchain::common::skein::skein_128_hash_bytes;
|
||||
use blockchain::encode;
|
||||
use blockchain::env;
|
||||
use blockchain::records::unpack_block::unpack_header::load_block_header;
|
||||
use blockchain::records::wallet_registry::resolve_pubkey_from_short_address;
|
||||
use blockchain::standalone_tools::wallet_registry_lookup::lookup_pubkey_from_live_registry;
|
||||
use blockchain::torrent::structs::Torrent;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::{AsyncReadExt, File};
|
||||
|
|
@ -15,8 +16,8 @@ use std::process;
|
|||
async fn main() {
|
||||
// Validate that a local block file, block header, and torrent metadata agree.
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() != 2 {
|
||||
eprintln!("Usage: {} <block_number>", args[0]);
|
||||
if !(2..=3).contains(&args.len()) {
|
||||
eprintln!("Usage: {} <block_number> [miner_public_key_hex]", args[0]);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
|
|
@ -55,12 +56,25 @@ async fn main() {
|
|||
let header = load_block_header(block_number).await.unwrap();
|
||||
let block_hash = header.hash().await;
|
||||
let block_difficulty = hex_to_u64(&block_hash).await.unwrap();
|
||||
let miner_pubkey = resolve_pubkey_from_short_address(
|
||||
&blockchain::startup::initialize_startup::open_chain_state().await,
|
||||
&header.unmined_block.miner,
|
||||
let miner_pubkey = if let Some(public_key_hex) = args.get(2) {
|
||||
Wallet::normalize_saved_public_key_bytes(public_key_hex).unwrap_or_else(|| {
|
||||
eprintln!("Error: supplied miner public key is invalid.");
|
||||
process::exit(1);
|
||||
})
|
||||
} else {
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
"Wallet key cannot be empty. Please try again.",
|
||||
)
|
||||
.unwrap_or(None)
|
||||
.unwrap_or_default();
|
||||
.await;
|
||||
lookup_pubkey_from_live_registry(&header.unmined_block.miner, &wallet_path, &encryption_key)
|
||||
.await
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Error: {err}");
|
||||
process::exit(1);
|
||||
})
|
||||
};
|
||||
let miner_pubkey_hex = encode(&miner_pubkey);
|
||||
|
||||
// Load the raw block bytes for file-size, info-hash, and piece-hash checks.
|
||||
|
|
@ -73,9 +87,9 @@ async fn main() {
|
|||
|
||||
let info_hash_computed = skein_128_hash_bytes(&block_data);
|
||||
|
||||
// Rebuild the unmined-block hash used by the miner proof signature.
|
||||
let unmined_json = serde_json::to_string(&header.unmined_block).unwrap();
|
||||
let unmined_hash = skein_256_hash_data(&unmined_json);
|
||||
// Rebuild the unmined-block hash using the same canonical path as mining
|
||||
// and live block verification.
|
||||
let unmined_hash = header.unmined_block.hash().await;
|
||||
let signature_ok =
|
||||
Wallet::verify_transaction_with_public_key(&unmined_hash, &header.proof, &miner_pubkey_hex)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,105 +1,25 @@
|
|||
use blockchain::common::cli_prompts::prompt_visible;
|
||||
use blockchain::env;
|
||||
use blockchain::from_str;
|
||||
use blockchain::read_to_string;
|
||||
use blockchain::tilde;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::Value;
|
||||
use rustyline::completion::FilenameCompleter;
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::{history::DefaultHistory, CompletionType, Config, Editor};
|
||||
use rustyline_derive::Completer;
|
||||
use rustyline_derive::Helper as RustyHelper;
|
||||
use rustyline_derive::Highlighter as RustyHighlighter;
|
||||
use rustyline_derive::Hinter as RustyHinter;
|
||||
use rustyline_derive::Validator as RustyValidator;
|
||||
|
||||
async fn read_address_file(path: &str) -> Result<String, String> {
|
||||
// Allow ~ in paths and then extract either a raw address or an address from wallet JSON.
|
||||
let expanded_path = tilde(path).to_string();
|
||||
read_to_string(&expanded_path)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to read {expanded_path}: {e}"))
|
||||
.and_then(|contents| extract_address(&contents))
|
||||
}
|
||||
|
||||
fn extract_address(contents: &str) -> Result<String, String> {
|
||||
// Address files may be a plain address string or a saved wallet JSON object.
|
||||
let trimmed = contents.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Err("Address file is empty".to_string());
|
||||
}
|
||||
|
||||
if trimmed.starts_with('{') {
|
||||
// Wallet JSON address validation uses the canonical short address.
|
||||
let value: Value =
|
||||
from_str(trimmed).map_err(|e| format!("Failed to parse wallet JSON: {e}"))?;
|
||||
let address = value
|
||||
.get("short_address")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| "Wallet JSON does not contain a usable address field".to_string())?;
|
||||
return Ok(address.trim().to_string());
|
||||
}
|
||||
|
||||
Ok(trimmed.to_string())
|
||||
}
|
||||
|
||||
#[derive(RustyHelper, Completer, RustyHinter, RustyHighlighter, RustyValidator)]
|
||||
struct PathHelper {
|
||||
#[rustyline(Completer)]
|
||||
completer: FilenameCompleter,
|
||||
}
|
||||
|
||||
fn prompt_for_path(prompt: &str) -> Result<String, String> {
|
||||
// Rustyline gives the interactive prompt filesystem completion.
|
||||
let config = Config::builder()
|
||||
.completion_type(CompletionType::List)
|
||||
.build();
|
||||
let mut editor =
|
||||
Editor::<PathHelper, DefaultHistory>::with_config(config).map_err(|e| e.to_string())?;
|
||||
editor.set_helper(Some(PathHelper {
|
||||
completer: FilenameCompleter::new(),
|
||||
}));
|
||||
|
||||
match editor.readline(prompt) {
|
||||
Ok(line) => Ok(line.trim().to_string()),
|
||||
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
|
||||
Err("Input cancelled".to_string())
|
||||
}
|
||||
Err(err) => Err(format!("Failed to read address file path: {err}")),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Accept the address file path as an arg or prompt interactively.
|
||||
// Accept a directly pasted address as an arg or prompt interactively.
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() > 1 && args.len() != 2 {
|
||||
println!("Usage: ./verify_address <address_file>");
|
||||
println!("Usage: ./verify_address <wallet_address>");
|
||||
return;
|
||||
}
|
||||
|
||||
let address_path = if args.len() == 2 {
|
||||
args[1].clone()
|
||||
let address = if args.len() == 2 {
|
||||
args[1].trim().to_string()
|
||||
} else {
|
||||
match prompt_for_path("Please enter the path to the file containing the wallet address: ") {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
prompt_visible("Please enter the wallet address: ")
|
||||
.await
|
||||
.trim()
|
||||
.to_string()
|
||||
};
|
||||
|
||||
let address = match read_address_file(&address_path).await {
|
||||
Ok(address) => address,
|
||||
Err(err) => {
|
||||
println!("{err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let message_hash = Wallet::short_address_validation(address.trim());
|
||||
|
||||
println!("{message_hash}");
|
||||
println!("{}", Wallet::short_address_validation(&address));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,97 +1,17 @@
|
|||
use blockchain::common::cli_prompts::prompt_visible;
|
||||
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
|
||||
use blockchain::common::skein::skein_256_hash_data;
|
||||
use blockchain::env;
|
||||
use blockchain::from_str;
|
||||
use blockchain::read_to_string;
|
||||
use blockchain::records::wallet_registry::resolve_pubkey_from_short_address;
|
||||
use blockchain::sled;
|
||||
use blockchain::tilde;
|
||||
use blockchain::standalone_tools::wallet_registry_lookup::lookup_pubkey_from_live_registry;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::Value;
|
||||
use rustyline::completion::FilenameCompleter;
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::{history::DefaultHistory, CompletionType, Config, Editor};
|
||||
use rustyline_derive::Completer;
|
||||
use rustyline_derive::Helper as RustyHelper;
|
||||
use rustyline_derive::Highlighter as RustyHighlighter;
|
||||
use rustyline_derive::Hinter as RustyHinter;
|
||||
use rustyline_derive::Validator as RustyValidator;
|
||||
|
||||
async fn read_input_file(path: &str) -> Result<String, String> {
|
||||
// Signature input files are plain text and are trimmed before verification.
|
||||
let expanded_path = tilde(path).to_string();
|
||||
read_to_string(&expanded_path)
|
||||
.await
|
||||
.map(|s| s.trim().to_string())
|
||||
.map_err(|e| format!("Failed to read {expanded_path}: {e}"))
|
||||
}
|
||||
|
||||
async fn read_address_file(path: &str) -> Result<String, String> {
|
||||
// Allow ~ in paths and then extract either a raw address or an address from wallet JSON.
|
||||
let expanded_path = tilde(path).to_string();
|
||||
read_to_string(&expanded_path)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to read {expanded_path}: {e}"))
|
||||
.and_then(|contents| extract_address(&contents))
|
||||
}
|
||||
|
||||
fn extract_address(contents: &str) -> Result<String, String> {
|
||||
// Address files may be a plain address string or a saved wallet JSON object.
|
||||
let trimmed = contents.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Err("Address file is empty".to_string());
|
||||
}
|
||||
|
||||
if trimmed.starts_with('{') {
|
||||
// Signature verification uses the canonical short address.
|
||||
let value: Value =
|
||||
from_str(trimmed).map_err(|e| format!("Failed to parse wallet JSON: {e}"))?;
|
||||
let address = value
|
||||
.get("short_address")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| "Wallet JSON does not contain a usable address field".to_string())?;
|
||||
return Ok(address.trim().to_string());
|
||||
}
|
||||
|
||||
Ok(trimmed.to_string())
|
||||
}
|
||||
|
||||
#[derive(RustyHelper, Completer, RustyHinter, RustyHighlighter, RustyValidator)]
|
||||
struct PathHelper {
|
||||
#[rustyline(Completer)]
|
||||
completer: FilenameCompleter,
|
||||
}
|
||||
|
||||
fn prompt_for_path(prompt: &str) -> Result<String, String> {
|
||||
// Rustyline gives the interactive prompt filesystem completion.
|
||||
let config = Config::builder()
|
||||
.completion_type(CompletionType::List)
|
||||
.build();
|
||||
let mut editor =
|
||||
Editor::<PathHelper, DefaultHistory>::with_config(config).map_err(|e| e.to_string())?;
|
||||
editor.set_helper(Some(PathHelper {
|
||||
completer: FilenameCompleter::new(),
|
||||
}));
|
||||
|
||||
match editor.readline(prompt) {
|
||||
Ok(line) => Ok(line.trim().to_string()),
|
||||
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
|
||||
Err("Input cancelled".to_string())
|
||||
}
|
||||
Err(err) => Err(format!("Failed to read file path: {err}")),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Accept all verification inputs as args or prompt interactively.
|
||||
// Accept direct pasted values. Verification needs a live peer registry
|
||||
// lookup because the local sled database may already be locked by a node.
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let address_path: String;
|
||||
let signature_path: String;
|
||||
|
||||
if args.len() > 1 && args.len() != 4 {
|
||||
println!("Usage: ./verify_message \"<message to verify>\" <address_file> <signature_file>");
|
||||
println!("Usage: ./verify_message \"<message to verify>\" <wallet_address> <signature>");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -101,74 +21,46 @@ async fn main() {
|
|||
prompt_visible("Please enter the message to verify: ").await
|
||||
};
|
||||
|
||||
if args.len() == 4 {
|
||||
address_path = args[2].clone();
|
||||
signature_path = args[3].clone();
|
||||
let address = if args.len() == 4 {
|
||||
args[2].trim().to_string()
|
||||
} else {
|
||||
address_path = match prompt_for_path(
|
||||
"Please enter the path to the file containing the wallet address: ",
|
||||
) {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
signature_path =
|
||||
match prompt_for_path("Please enter the path to the file containing the signature: ") {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let address = match read_address_file(&address_path).await {
|
||||
Ok(address) => address,
|
||||
Err(err) => {
|
||||
println!("{err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let signature = match read_input_file(&signature_path).await {
|
||||
Ok(signature) => signature,
|
||||
Err(err) => {
|
||||
println!("{err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Hash the message exactly as sign_message does before verifying the wallet signature.
|
||||
let message_hash = skein_256_hash_data(message.as_str());
|
||||
let (
|
||||
_network_name,
|
||||
_padded_base_coin,
|
||||
_suffix,
|
||||
_torrent_path,
|
||||
_wallet_path,
|
||||
_block_path,
|
||||
db_path,
|
||||
_balance_path,
|
||||
_log_path,
|
||||
) = block_extension_and_paths();
|
||||
let db = match sled::open(&db_path) {
|
||||
Ok(db) => db,
|
||||
Err(err) => {
|
||||
eprintln!("Failed to open wallet registry database: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let signature = match resolve_pubkey_from_short_address(&db, address.trim()) {
|
||||
Ok(Some(pubkey)) => {
|
||||
Wallet::verify_transaction_with_public_key_bytes(&message_hash, &signature, &pubkey)
|
||||
prompt_visible("Please enter the wallet address: ")
|
||||
.await
|
||||
}
|
||||
_ => false,
|
||||
.trim()
|
||||
.to_string()
|
||||
};
|
||||
|
||||
if signature {
|
||||
let signature = if args.len() == 4 {
|
||||
args[3].trim().to_string()
|
||||
} else {
|
||||
prompt_visible("Please enter the signature: ")
|
||||
.await
|
||||
.trim()
|
||||
.to_string()
|
||||
};
|
||||
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
"Wallet key cannot be empty. Please try again.",
|
||||
)
|
||||
.await;
|
||||
|
||||
let pubkey =
|
||||
match lookup_pubkey_from_live_registry(&address, &wallet_path, &encryption_key).await {
|
||||
Ok(pubkey) => pubkey,
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Hash the message exactly as sign_message does before verifying the detached signature.
|
||||
let message_hash = skein_256_hash_data(message.as_str());
|
||||
let valid =
|
||||
Wallet::verify_transaction_with_public_key_bytes(&message_hash, &signature, &pubkey).await;
|
||||
|
||||
if valid {
|
||||
println!("valid signature");
|
||||
} else {
|
||||
println!("invalid signature");
|
||||
|
|
|
|||
|
|
@ -279,5 +279,5 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
println!("Transaction: {output_str}");
|
||||
println!("{output_str}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -250,5 +250,5 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
println!("Transaction: {output_str}");
|
||||
println!("{output_str}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use crate::rpc::command_maps::{
|
|||
RPC_TIME, RPC_TOKEN_CATALOG, RPC_TOKEN_DETAILS, RPC_TOKEN_LIST, RPC_TORRENT_BY_HEIGHT,
|
||||
RPC_TOTAL_CONFIRMED_TX, RPC_TRANSACTION_BY_TXID, RPC_UNBLOCK_IP, RPC_VALIDATE_MESSAGE,
|
||||
RPC_VANITY_LOOKUP, RPC_VANITY_OWNER_LOOKUP, RPC_WALLET_REGISTRATION_STATUS,
|
||||
RPC_WALLET_REGISTRY_SYNC,
|
||||
};
|
||||
use crate::standalone_tools::transaction_creator::create_transaction_request;
|
||||
use crate::timeout;
|
||||
|
|
@ -486,6 +487,12 @@ async fn build_request_bytes(
|
|||
bin_msg.extend_from_slice(&public_key_bytes);
|
||||
bin_msg.extend_from_slice(&signature_bytes);
|
||||
}
|
||||
// Request the deterministic wallet registry snapshot.
|
||||
39 => {
|
||||
let command_number: u8 = RPC_WALLET_REGISTRY_SYNC;
|
||||
bin_msg.extend_from_slice(&command_number.to_le_bytes());
|
||||
bin_msg.extend_from_slice(&hashmap_key);
|
||||
}
|
||||
// Lookup remote balance by long, short, or vanity address.
|
||||
40 => {
|
||||
let command_number: u8 = RPC_VANITY_LOOKUP;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
use crate::{json, Map, Value};
|
||||
|
||||
fn scalar_value(value: &str) -> Value {
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Value::String(String::new());
|
||||
}
|
||||
|
||||
if trimmed.chars().all(|ch| ch.is_ascii_digit()) {
|
||||
if let Ok(number) = trimmed.parse::<u64>() {
|
||||
return json!(number);
|
||||
}
|
||||
}
|
||||
|
||||
if trimmed.contains('.')
|
||||
&& trimmed
|
||||
.chars()
|
||||
.all(|ch| ch.is_ascii_digit() || ch == '.' || ch == '-')
|
||||
{
|
||||
if let Ok(number) = trimmed.parse::<f64>() {
|
||||
return json!(number);
|
||||
}
|
||||
}
|
||||
|
||||
Value::String(trimmed.to_string())
|
||||
}
|
||||
|
||||
fn insert_payment_field(payments: &mut Vec<Map<String, Value>>, key: &str, value: &str) -> bool {
|
||||
let Some(rest) = key.strip_prefix("payment_") else {
|
||||
return false;
|
||||
};
|
||||
let Some((index_text, field)) = rest.split_once('_') else {
|
||||
return false;
|
||||
};
|
||||
let Ok(index) = index_text.parse::<usize>() else {
|
||||
return false;
|
||||
};
|
||||
if index == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
while payments.len() < index {
|
||||
payments.push(Map::new());
|
||||
}
|
||||
payments[index - 1].insert(field.to_string(), scalar_value(value));
|
||||
true
|
||||
}
|
||||
|
||||
pub fn parse_loan_summary(text: &str) -> Value {
|
||||
let mut loan = Map::new();
|
||||
let mut payments = Vec::<Map<String, Value>>::new();
|
||||
|
||||
for line in text.lines().map(str::trim).filter(|line| !line.is_empty()) {
|
||||
let Some((key, value)) = line.split_once('=') else {
|
||||
continue;
|
||||
};
|
||||
if insert_payment_field(&mut payments, key.trim(), value) {
|
||||
continue;
|
||||
}
|
||||
loan.insert(key.trim().to_string(), scalar_value(value));
|
||||
}
|
||||
|
||||
loan.insert(
|
||||
"payments".to_string(),
|
||||
Value::Array(payments.into_iter().map(Value::Object).collect()),
|
||||
);
|
||||
Value::Object(loan)
|
||||
}
|
||||
|
||||
pub fn parse_loan_lookup_by_address(text: &str) -> Value {
|
||||
let mut address = String::new();
|
||||
let mut declared_contracts = None;
|
||||
let mut contracts = Vec::<Value>::new();
|
||||
let mut current_contract = Vec::<String>::new();
|
||||
|
||||
for line in text.lines().map(str::trim) {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(value) = line.strip_prefix("address=") {
|
||||
address = value.trim().to_string();
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(value) = line.strip_prefix("contracts=") {
|
||||
declared_contracts = value.trim().parse::<u64>().ok();
|
||||
continue;
|
||||
}
|
||||
|
||||
if line.starts_with("contract_") && line.ends_with(':') {
|
||||
if !current_contract.is_empty() {
|
||||
contracts.push(parse_loan_summary(¤t_contract.join("\n")));
|
||||
current_contract.clear();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
current_contract.push(line.to_string());
|
||||
}
|
||||
|
||||
if !current_contract.is_empty() {
|
||||
contracts.push(parse_loan_summary(¤t_contract.join("\n")));
|
||||
}
|
||||
|
||||
json!({
|
||||
"address": address,
|
||||
"contracts": declared_contracts.unwrap_or(contracts.len() as u64),
|
||||
"loans": contracts
|
||||
})
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
// The standalone_tools module holds utility helpers that mirror node serialization and connection behavior.
|
||||
pub mod connections;
|
||||
pub mod loan_lookup_json;
|
||||
pub mod transaction_creator;
|
||||
pub mod vanity_resolver;
|
||||
pub mod wallet_registry_lookup;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
use crate::common::network_startup::get_connections;
|
||||
use crate::records::memory::response_channels::generate_uid;
|
||||
use crate::rpc::commands::wallet_registry_sync::WALLET_REGISTRY_RECORD_BYTES;
|
||||
use crate::standalone_tools::connections::handshake;
|
||||
use crate::wallets::structures::Wallet;
|
||||
|
||||
pub async fn lookup_pubkey_from_live_registry(
|
||||
address: &str,
|
||||
wallet_path: &str,
|
||||
encryption_key: &str,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
let short_address = Wallet::normalize_to_short_address(address)
|
||||
.ok_or_else(|| format!("Invalid wallet address: {address}"))?;
|
||||
let short_address_bytes = Wallet::short_address_to_bytes(&short_address)
|
||||
.ok_or_else(|| format!("Invalid short wallet address: {short_address}"))?;
|
||||
|
||||
for conn in get_connections().await {
|
||||
let Ok(socket_address) = conn.parse() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let result = handshake::connect_and_handshake(
|
||||
socket_address,
|
||||
String::new(),
|
||||
39,
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.to_string(),
|
||||
wallet_path: wallet_path.to_string(),
|
||||
},
|
||||
generate_uid(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let Ok(response) = result else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for chunk in response.chunks_exact(WALLET_REGISTRY_RECORD_BYTES) {
|
||||
let registered_address = &chunk[..Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||
if registered_address == short_address_bytes.as_slice() {
|
||||
let public_key = chunk[Wallet::SHORT_ADDRESS_BYTES_LENGTH..].to_vec();
|
||||
if Wallet::has_valid_public_key_bytes(&public_key) {
|
||||
return Ok(public_key);
|
||||
}
|
||||
return Err(format!(
|
||||
"Registry returned an invalid public key for {short_address}"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"Could not find registered public key for {short_address} from any configured peer."
|
||||
))
|
||||
}
|
||||
Loading…
Reference in New Issue