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
97 changes: 93 additions & 4 deletions client/src/bin/space-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use spaces_client::{
},
rpc::{
BidParams, ExecuteParams, OpenParams, RegisterParams, RpcClient, RpcWalletRequest,
RpcWalletTxBuilder, SendCoinsParams, TransferSpacesParams,
RpcWalletTxBuilder, SendCoinsParams, SetPtrParams, TransferSpacesParams,
},
serialize_base64,
wallets::{AddressKind, WalletResponse},
Expand Down Expand Up @@ -419,6 +419,20 @@ enum Commands {
#[arg(long, short)]
fee_rate: Option<u64>,
},
/// Set data for a PTR record
#[command(name = "setptr")]
SetPtr {
/// The sha256 hash of the spk or the spk itself prefixed with hex:
spk: String,
/// Hex encoded data
data: String,
/// Hex encoded private key (for testing purposes)
#[arg(long)]
prv: Option<String>,
/// Fee rate to use in sat/vB
#[arg(long, short)]
fee_rate: Option<u64>,
},
/// List last transactions
#[command(name = "listtransactions")]
ListTransactions {
Expand Down Expand Up @@ -482,6 +496,24 @@ struct Base64Bytes(
Vec<u8>,
);

#[derive(Serialize, Deserialize)]
struct HexCommitment {
pub state_root: String,
pub prev_root: Option<String>,
pub history_hash: String,
pub block_height: u32,
}

#[derive(Serialize, Deserialize)]
struct HexFullPtrOut {
pub txid: String,
pub n: usize,
pub value: u64, // Amount in satoshis
pub script_pubkey: String,
pub genesis_spk: Option<String>,
pub data: Option<String>,
}

impl SpaceCli {
async fn configure() -> anyhow::Result<(Self, Args)> {
let mut args = Args::parse();
Expand Down Expand Up @@ -868,6 +900,39 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client
)
.await?;
}
Commands::SetPtr { spk, data, prv, fee_rate } => {
let sptr = Sptr::from_str(&spk)
.map_err(|e| ClientError::Custom(format!("input error: {}", e.to_string())))?;

let data = match hex::decode(&data) {
Ok(data) => data,
Err(e) => {
return Err(ClientError::Custom(format!(
"Could not hex decode data: {}",
e
)))
}
};

// Validate private key if provided
if let Some(ref prv_hex) = prv {
if hex::decode(prv_hex).is_err() {
return Err(ClientError::Custom("Invalid hex encoded private key".to_string()));
}
}

cli.send_request(
Some(RpcWalletRequest::SetPtr(SetPtrParams {
spk: sptr.to_string(),
data: hex::encode(&data),
prv: prv.clone(),
})),
None,
fee_rate,
false,
)
.await?;
}
Commands::ListUnspent => {
let utxos = cli.client.wallet_list_unspent(&cli.wallet).await?;
print_list_unspent(utxos, cli.format);
Expand Down Expand Up @@ -1095,12 +1160,25 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client
let sptr = Sptr::from_str(&spk)
.map_err(|e| ClientError::Custom(format!("input error: {}", e.to_string())))?;

let ptr = cli
let full_ptr_out = cli
.client
.get_ptr(sptr)
.await
.map_err(|e| ClientError::Custom(e.to_string()))?;
println!("{}", serde_json::to_string(&ptr).expect("result"));

if let Some(full_ptr_out) = full_ptr_out {
let hex_full_ptr_out = HexFullPtrOut {
txid: full_ptr_out.txid.to_string(),
n: full_ptr_out.ptrout.n,
value: full_ptr_out.ptrout.value.to_sat(),
script_pubkey: hex::encode(full_ptr_out.ptrout.script_pubkey.as_bytes()),
genesis_spk: full_ptr_out.ptrout.sptr.as_ref().map(|ptr| hex::encode(&ptr.genesis_spk)),
data: full_ptr_out.ptrout.sptr.as_ref().and_then(|ptr| ptr.data.as_ref().map(|data| hex::encode(data))),
};
println!("{}", serde_json::to_string_pretty(&hex_full_ptr_out).expect("result"));
} else {
println!("null");
}
}

Commands::GetPtrOut { outpoint } => {
Expand Down Expand Up @@ -1221,7 +1299,18 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client
.get_commitment(space, root)
.await
.map_err(|e| ClientError::Custom(e.to_string()))?;
println!("{}", serde_json::to_string(& c).expect("result"));

if let Some(commitment) = c {
let hex_commitment = HexCommitment {
state_root: hex::encode(commitment.state_root),
prev_root: commitment.prev_root.map(|root| hex::encode(root)),
history_hash: hex::encode(commitment.history_hash),
block_height: commitment.block_height,
};
println!("{}", serde_json::to_string_pretty(&hex_commitment).expect("result"));
} else {
println!("null");
}
}
}

Expand Down
9 changes: 9 additions & 0 deletions client/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,8 @@ pub enum RpcWalletRequest {
CreatePtr(CreatePtrParams),
#[serde(rename = "commit")]
Commit(CommitParams),
#[serde(rename = "setptr")]
SetPtr(SetPtrParams),
#[serde(rename = "send")]
SendCoins(SendCoinsParams),
}
Expand Down Expand Up @@ -457,6 +459,13 @@ pub struct CommitParams {
pub root: sha256::Hash,
}

#[derive(Clone, Serialize, Deserialize)]
pub struct SetPtrParams {
pub spk: String,
pub data: String,
pub prv: Option<String>,
}

#[derive(Clone, Serialize, Deserialize)]
pub struct SendCoinsParams {
pub amount: Amount,
Expand Down
43 changes: 43 additions & 0 deletions client/src/wallets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,49 @@ impl RpcWallet {
root: *params.root.as_ref()
})
}
RpcWalletRequest::SetPtr(params) => {
let sptr = Sptr::from_str(&params.spk)
.map_err(|e| anyhow!("setptr: invalid sptr: {}", e))?;

let _data = hex::decode(&params.data)
.map_err(|e| anyhow!("setptr: invalid hex data: {}", e))?;

// Verify the PTR exists
let ptr_info = chain.get_ptr_info(&sptr)?;
if ptr_info.is_none() {
return Err(anyhow!("setptr: PTR not found"));
}

let ptr_info = ptr_info.unwrap();

// If private key is provided, validate it matches the script_pubkey
if let Some(ref prv_hex) = params.prv {
let _prv_bytes = hex::decode(prv_hex)
.map_err(|e| anyhow!("setptr: invalid private key hex: {}", e))?;

// For testing purposes, we'll assume the private key is valid
// In a real implementation, you'd verify the private key corresponds to the script_pubkey
info!("setptr: Using provided private key for signing (testing mode)");

// TODO: Implement proper private key validation and transaction signing
// This would involve:
// 1. Converting the private key to a keypair
// 2. Verifying it corresponds to the script_pubkey
// 3. Using it to sign the transaction

return Err(anyhow!("setptr: Private key signing not yet implemented - requires keypair validation and transaction signing"));
} else {
// No private key provided, check wallet ownership
if !wallet.is_mine(ptr_info.ptrout.script_pubkey.clone()) {
return Err(anyhow!("setptr: you don't control this PTR and no private key provided"));
}
}

// For now, we'll use the Execute approach similar to setrawfallback
// This creates a transaction that updates the PTR data
// TODO: Implement proper PTR data update mechanism
return Err(anyhow!("setptr: PTR data update not yet implemented - this requires PTR script creation"));
}
}
}

Expand Down