Skip to content

Commit aa6fd03

Browse files
committed
CreatePtr Data Parsing
1 parent b8a5786 commit aa6fd03

File tree

11 files changed

+567
-7
lines changed

11 files changed

+567
-7
lines changed

SUBSPACES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ You can create an on-chain identifier that only the controller of the script pub
8686
$ space-cli createptr 5120d3c3196cb3ed7fa79c882ed62f8e5942e546130d5ae5983da67dbb6c9bdd2e79
8787
```
8888

89+
Optionally, you can set hex-encoded data on the created pointer using the `--data` parameter:
90+
91+
```bash
92+
$ space-cli createptr 5120d3c3196cb3ed7fa79c882ed62f8e5942e546130d5ae5983da67dbb6c9bdd2e79 --data deadbeef
93+
```
94+
8995
This command creates a UTXO with the same script pubkey and "mints" a space pointer (sptr) derived from it:
9096

9197
```

client/src/bin/space-cli.rs

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use spaces_client::{
2424
config::{default_cookie_path, default_spaces_rpc_port, ExtendedNetwork},
2525
deserialize_base64,
2626
format::{
27-
print_error_rpc_response, print_list_bidouts, print_list_spaces_response,
27+
print_error_rpc_response, print_list_all_spaces, print_list_bidouts, print_list_spaces_response,
2828
print_list_transactions, print_list_unspent, print_list_wallets, print_server_info,
2929
print_wallet_balance_response, print_wallet_info, print_wallet_response, Format,
3030
},
@@ -157,6 +157,10 @@ enum Commands {
157157
/// The script public key as hex string
158158
spk: String,
159159

160+
/// Hex encoded data to set on the created ptr
161+
#[arg(long)]
162+
data: Option<String>,
163+
160164
#[arg(long, short)]
161165
fee_rate: Option<u64>,
162166
},
@@ -166,6 +170,13 @@ enum Commands {
166170
/// The sha256 hash of the spk or the spk itself prefixed with hex:
167171
spk: String,
168172
},
173+
/// Get all ptrs info (same output format as getptr)
174+
#[command(name = "getallptrs")]
175+
GetAllPtrs {
176+
/// Only return PTRs with non-null data
177+
#[arg(long)]
178+
with_data: bool,
179+
},
169180
/// Transfer ownership of spaces and/or PTRs to the given name or address
170181
#[command(
171182
name = "transfer",
@@ -425,6 +436,9 @@ enum Commands {
425436
/// still in auction with a winning bid
426437
#[command(name = "listspaces")]
427438
ListSpaces,
439+
/// List all spaces in the chain state (not just wallet-related)
440+
#[command(name = "listallspaces")]
441+
ListAllSpaces,
428442
/// List unspent auction outputs i.e. outputs that can be
429443
/// auctioned off in the bidding process
430444
#[command(name = "listbidouts")]
@@ -672,6 +686,40 @@ async fn main() -> anyhow::Result<()> {
672686
Ok(())
673687
}
674688

689+
fn parse_ptr_for_json(ptr: &spaces_ptr::FullPtrOut) -> serde_json::Value {
690+
use spaces_ptr::vtlv;
691+
692+
let mut ptr_json = serde_json::to_value(ptr).expect("ptr should be serializable");
693+
694+
// Since ptrout and sptr are flattened via serde(flatten), the data field
695+
// appears directly in the JSON object, not nested. Look for "data" at the top level.
696+
if let Some(obj) = ptr_json.as_object_mut() {
697+
if let Some(data) = obj.remove("data") {
698+
// Bytes serializes as hex string in JSON
699+
if let Some(hex_str) = data.as_str() {
700+
if let Ok(data_bytes) = hex::decode(hex_str) {
701+
match vtlv::parse_vtlv(&data_bytes) {
702+
Ok(parsed) => {
703+
obj.insert("parsed".to_string(), serde_json::to_value(parsed).expect("parsed should be serializable"));
704+
}
705+
Err(_) => {
706+
// If parsing fails, keep the original data
707+
obj.insert("data".to_string(), data);
708+
}
709+
}
710+
} else {
711+
obj.insert("data".to_string(), data);
712+
}
713+
} else {
714+
// Not a string, keep as-is
715+
obj.insert("data".to_string(), data);
716+
}
717+
}
718+
}
719+
720+
ptr_json
721+
}
722+
675723
async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), ClientError> {
676724
match command {
677725
Commands::GetRollout {
@@ -894,6 +942,9 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client
894942
.await?;
895943
} else {
896944
// TODO: support set data for spaces
945+
return Err(ClientError::Custom(format!(
946+
"setrawfallback: setting data for spaces is not yet supported. Use an SPTR (sptr1...) instead of a space name."
947+
)));
897948
// // Space fallback: use existing space script
898949
// let space = normalize_space(&space_or_sptr);
899950
// let space_script =
@@ -931,6 +982,11 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client
931982
let spaces = cli.client.wallet_list_spaces(&cli.wallet).await?;
932983
print_list_spaces_response(tip.tip.height, spaces, cli.format);
933984
}
985+
Commands::ListAllSpaces => {
986+
let tip = cli.client.get_server_info().await?;
987+
let spaces = cli.client.get_all_spaces().await?;
988+
print_list_all_spaces(tip.tip.height, spaces, cli.format);
989+
}
934990
Commands::Balance => {
935991
let balance = cli.client.wallet_get_balance(&cli.wallet).await?;
936992
print_wallet_balance_response(balance, cli.format);
@@ -1100,15 +1156,25 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client
11001156

11011157
println!("{}", serde_json::to_string(&event).expect("result"));
11021158
}
1103-
Commands::CreatePtr { spk, fee_rate } => {
1159+
Commands::CreatePtr { spk, data, fee_rate } => {
11041160
let spk = ScriptBuf::from(hex::decode(spk)
11051161
.map_err(|_| ClientError::Custom("Invalid spk hex".to_string()))?);
11061162

1163+
let data = match data {
1164+
Some(data_hex) => {
1165+
Some(hex::decode(data_hex).map_err(|e| {
1166+
ClientError::Custom(format!("Could not hex decode data: {}", e))
1167+
})?)
1168+
}
1169+
None => None,
1170+
};
1171+
11071172
let sptr = Sptr::from_spk::<Sha256>(spk.clone());
11081173
println!("Creating sptr: {}", sptr);
11091174
cli.send_request(
11101175
Some(RpcWalletRequest::CreatePtr(CreatePtrParams {
11111176
spk: hex::encode(spk.as_bytes()),
1177+
data,
11121178
})),
11131179
None,
11141180
fee_rate,
@@ -1127,6 +1193,17 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client
11271193
.map_err(|e| ClientError::Custom(e.to_string()))?;
11281194
println!("{}", serde_json::to_string(&ptr).expect("result"));
11291195
}
1196+
Commands::GetAllPtrs { with_data } => {
1197+
let ptrs = cli
1198+
.client
1199+
.get_all_ptrs(with_data)
1200+
.await
1201+
.map_err(|e| ClientError::Custom(e.to_string()))?;
1202+
let parsed_ptrs: Vec<serde_json::Value> = ptrs.iter()
1203+
.map(|ptr| parse_ptr_for_json(ptr))
1204+
.collect();
1205+
println!("{}", serde_json::to_string(&parsed_ptrs).expect("result"));
1206+
}
11301207

11311208
Commands::GetPtrOut { outpoint } => {
11321209
let ptrout = cli

client/src/format.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use colored::{Color, Colorize};
33
use jsonrpsee::core::Serialize;
44
use serde::Deserialize;
55
use spaces_protocol::{
6-
bitcoin::{Amount, Network, OutPoint}, Covenant
6+
bitcoin::{Amount, Network, OutPoint}, Covenant, FullSpaceOut
77
};
88
use spaces_wallet::{
99
address::SpaceAddress,
@@ -124,6 +124,48 @@ pub fn print_list_bidouts(bidouts: Vec<DoubleUtxo>, format: Format) {
124124
}
125125
}
126126

127+
pub fn print_list_all_spaces(
128+
_current_block: u32,
129+
spaces: Vec<FullSpaceOut>,
130+
format: Format,
131+
) {
132+
match format {
133+
Format::Text => {
134+
#[derive(Tabled)]
135+
struct AllSpaces {
136+
space: String,
137+
status: String,
138+
value: String,
139+
txid: String,
140+
}
141+
142+
let mut table_data = Vec::new();
143+
for space_out in spaces {
144+
let space = space_out.spaceout.space.as_ref();
145+
let space_name = space.map(|s| s.name.to_string()).unwrap_or_else(|| "unknown".to_string());
146+
// All spaces returned are owned (filtered in get_all_spaces)
147+
table_data.push(AllSpaces {
148+
space: space_name,
149+
status: "OWNED".to_string(),
150+
value: format!("{} sats", space_out.spaceout.value.to_sat()),
151+
txid: format!("{}", space_out.txid),
152+
});
153+
}
154+
155+
if table_data.is_empty() {
156+
println!("No spaces found.");
157+
} else {
158+
println!("All Spaces ({} total):", table_data.len());
159+
let table = Table::new(table_data);
160+
println!("{}", table);
161+
}
162+
}
163+
Format::Json => {
164+
println!("{}", serde_json::to_string_pretty(&spaces).unwrap());
165+
}
166+
}
167+
}
168+
127169
pub fn print_list_transactions(txs: Vec<TxInfo>, format: Format) {
128170
match format {
129171
Format::Text => {

client/src/rpc.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,13 @@ pub enum ChainStateCommand {
169169
outpoint: OutPoint,
170170
resp: Responder<anyhow::Result<Option<PtrOut>>>,
171171
},
172+
GetAllSpaces {
173+
resp: Responder<anyhow::Result<Vec<FullSpaceOut>>>,
174+
},
175+
GetAllPtrs {
176+
with_data: bool,
177+
resp: Responder<anyhow::Result<Vec<FullPtrOut>>>,
178+
},
172179
GetTxMeta {
173180
txid: Txid,
174181
resp: Responder<anyhow::Result<Option<TxEntry>>>,
@@ -277,6 +284,12 @@ pub trait Rpc {
277284
#[method(name = "getdelegator")]
278285
async fn get_delegator(&self, sptr: Sptr) -> Result<Option<SLabel>, ErrorObjectOwned>;
279286

287+
#[method(name = "getallspaces")]
288+
async fn get_all_spaces(&self) -> Result<Vec<FullSpaceOut>, ErrorObjectOwned>;
289+
290+
#[method(name = "getallptrs")]
291+
async fn get_all_ptrs(&self, with_data: bool) -> Result<Vec<FullPtrOut>, ErrorObjectOwned>;
292+
280293
#[method(name = "checkpackage")]
281294
async fn check_package(
282295
&self,
@@ -546,6 +559,8 @@ pub struct TransferSpacesParams {
546559
#[derive(Clone, Serialize, Deserialize)]
547560
pub struct CreatePtrParams {
548561
pub spk: String,
562+
#[serde(skip_serializing_if = "Option::is_none")]
563+
pub data: Option<Vec<u8>>,
549564
}
550565

551566
#[derive(Clone, Serialize, Deserialize)]
@@ -1075,6 +1090,19 @@ impl RpcServer for RpcServerImpl {
10751090
Ok(delegator)
10761091
}
10771092

1093+
async fn get_all_spaces(&self) -> Result<Vec<FullSpaceOut>, ErrorObjectOwned> {
1094+
let spaces = self.store.get_all_spaces()
1095+
.await
1096+
.map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::<String>))?;
1097+
Ok(spaces)
1098+
}
1099+
1100+
async fn get_all_ptrs(&self, with_data: bool) -> Result<Vec<FullPtrOut>, ErrorObjectOwned> {
1101+
let ptrs = self.store.get_all_ptrs(with_data)
1102+
.await
1103+
.map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::<String>))?;
1104+
Ok(ptrs)
1105+
}
10781106

10791107
async fn check_package(
10801108
&self,
@@ -1630,6 +1658,14 @@ impl AsyncChainState {
16301658
.context("could not fetch ptrouts");
16311659
let _ = resp.send(result);
16321660
}
1661+
ChainStateCommand::GetAllSpaces { resp } => {
1662+
let result = get_all_spaces(state);
1663+
let _ = resp.send(result);
1664+
}
1665+
ChainStateCommand::GetAllPtrs { with_data, resp } => {
1666+
let result = get_all_ptrs(state, with_data);
1667+
let _ = resp.send(result);
1668+
}
16331669
ChainStateCommand::GetBlockMeta {
16341670
height_or_hash,
16351671
resp,
@@ -2198,6 +2234,22 @@ impl AsyncChainState {
21982234
resp_rx.await?
21992235
}
22002236

2237+
pub async fn get_all_spaces(&self) -> anyhow::Result<Vec<FullSpaceOut>> {
2238+
let (resp, resp_rx) = oneshot::channel();
2239+
self.sender
2240+
.send(ChainStateCommand::GetAllSpaces { resp })
2241+
.await?;
2242+
resp_rx.await?
2243+
}
2244+
2245+
pub async fn get_all_ptrs(&self, with_data: bool) -> anyhow::Result<Vec<FullPtrOut>> {
2246+
let (resp, resp_rx) = oneshot::channel();
2247+
self.sender
2248+
.send(ChainStateCommand::GetAllPtrs { with_data, resp })
2249+
.await?;
2250+
resp_rx.await?
2251+
}
2252+
22012253
pub async fn get_block_meta(
22022254
&self,
22032255
height_or_hash: HeightOrHash,
@@ -2310,6 +2362,14 @@ fn get_delegation(state: &mut Chain, space: SLabel) -> anyhow::Result<Option<Spt
23102362
}
23112363
}
23122364

2365+
fn get_all_spaces(state: &mut Chain) -> anyhow::Result<Vec<FullSpaceOut>> {
2366+
state.get_all_spaces()
2367+
}
2368+
2369+
fn get_all_ptrs(state: &mut Chain, with_data: bool) -> anyhow::Result<Vec<FullPtrOut>> {
2370+
state.get_all_ptrs(with_data)
2371+
}
2372+
23132373
fn get_commitment(state: &mut Chain, space: SLabel, root: Option<Hash>) -> anyhow::Result<Option<Commitment>> {
23142374
let root = match root {
23152375
None => {

client/src/store/chain.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,18 @@ impl Chain {
9191
self.db.sp.state.get_space_info(space_hash)
9292
}
9393

94+
pub fn get_all_spaces(&mut self) -> anyhow::Result<Vec<FullSpaceOut>> {
95+
self.db.sp.state.get_all_spaces()
96+
}
97+
9498
pub fn get_ptr_info(&mut self, key: &Sptr) -> anyhow::Result<Option<FullPtrOut>> {
9599
self.db.pt.state.get_ptr_info(key)
96100
}
97101

102+
pub fn get_all_ptrs(&mut self, with_data: bool) -> anyhow::Result<Vec<FullPtrOut>> {
103+
self.db.pt.state.get_all_ptrs(with_data)
104+
}
105+
98106
pub fn load(_network: Network, genesis: ChainAnchor, ptrs_genesis: ChainAnchor, dir: &Path, index_spaces: bool, index_ptrs: bool) -> anyhow::Result<Self> {
99107
let proto_db_path = dir.join("protocol.sdb");
100108
let ptrs_db_path = dir.join("ptrs.sdb");

0 commit comments

Comments
 (0)