From d93a57272de8e83142ad91da2e6c0c999b299a37 Mon Sep 17 00:00:00 2001 From: joundy Date: Thu, 28 Mar 2024 01:26:09 +0700 Subject: [PATCH 1/4] feat(rune): ordzaar custom rune api --- src/ordzaar/mod.rs | 1 + src/ordzaar/runes.rs | 89 +++++++++++++++++++++++++++++++ src/subcommand/server.rs | 96 +++++++++++++++++++++++++++++++++- src/subcommand/server/query.rs | 2 +- 4 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 src/ordzaar/runes.rs diff --git a/src/ordzaar/mod.rs b/src/ordzaar/mod.rs index 7c01e31dcb..bf2718e0d0 100644 --- a/src/ordzaar/mod.rs +++ b/src/ordzaar/mod.rs @@ -2,3 +2,4 @@ use super::*; pub mod inscriptions; pub mod ordinals; +pub mod runes; diff --git a/src/ordzaar/runes.rs b/src/ordzaar/runes.rs new file mode 100644 index 0000000000..f6e781ba37 --- /dev/null +++ b/src/ordzaar/runes.rs @@ -0,0 +1,89 @@ +use super::*; + +// Custom Ordzaar Rune Response +// one of the reasons to create a custom response is to +// convert some of the bigint values into string +// and also to make the response consistent +// (prevent broken responses when bumping to the latest Ord version) + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct RuneOutpoint { + pub rune: String, + pub amount: String, + pub divisibility: u8, + pub symbol: Option, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct RuneDetailMint { + pub deadline: Option, + pub end: Option, + pub limit: Option, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct RuneDetail { + pub rune_id: RuneId, + pub burned: String, + pub divisibility: u8, + pub etching: Txid, + pub mint: Option, + pub mints: String, + pub number: String, + pub rune: Rune, + pub spacers: u32, + pub supply: String, + pub symbol: Option, + pub timestamp: u32, +} + +impl RuneOutpoint { + pub fn from_spaced_rune_pile(spaced_rune_piled: (SpacedRune, Pile)) -> Self { + Self { + rune: format!("{}", spaced_rune_piled.0), + amount: spaced_rune_piled.1.amount.to_string(), + divisibility: spaced_rune_piled.1.divisibility, + symbol: spaced_rune_piled.1.symbol, + } + } +} + +impl RuneDetail { + pub fn from_rune(rune_id: RuneId, entry: RuneEntry) -> Self { + let mut mint: Option = None; + if let Some(mint_value) = entry.mint { + mint = Some(RuneDetailMint { + deadline: mint_value.deadline, + end: mint_value.end, + limit: match mint_value.limit { + Some(value) => Some(value.to_string()), + None => None, + }, + }) + } + + Self { + rune_id, + burned: entry.burned.to_string(), + divisibility: entry.divisibility, + etching: entry.etching, + mint, + mints: entry.mints.to_string(), + number: entry.number.to_string(), + rune: entry.rune, + spacers: entry.spacers, + supply: entry.supply.to_string(), + symbol: entry.symbol, + timestamp: entry.timestamp, + } + } +} + +pub fn str_coma_to_array(str_coma: &str) -> Vec { + str_coma.split(',').map(|s| s.trim().to_string()).collect() +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct RuneOutputBulkQuery { + pub outpoints: String, +} diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index e269845dc4..82e4be1657 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -257,8 +257,16 @@ impl Server { .route("/status", get(Self::status)) .route("/tx/:txid", get(Self::transaction)) // ---- Ordzaar routes ---- + // Deprecated: Ordzaar custom routes should use"ordzar" prefix/path + // to prevent duplication with the Ord server paths. .route("/inscriptions", post(Self::ordzaar_inscriptions_from_ids)) .route("/ordinals/:outpoint", get(Self::ordzaar_ordinals_from_outpoint)) + + .route("/ordzaar/inscriptions", post(Self::ordzaar_inscriptions_from_ids)) + .route("/ordzaar/ordinals/:outpoint", get(Self::ordzaar_ordinals_from_outpoint)) + .route("/ordzaar/rune/:rune", get(Self::ordzaar_rune_detail)) + .route("/ordzaar/rune/output/:outpoint", get(Self::ordzaar_rune_output)) + .route("/ordzaar/rune/outputs/bulk", get(Self::ordzaar_rune_output_bulk)) // ---- Ordzaar routes ---- .layer(Extension(index)) .layer(Extension(server_config.clone())) @@ -382,6 +390,89 @@ impl Server { } Ok(Json(inscriptions).into_response()) } + + async fn ordzaar_rune_output( + Extension(index): Extension>, + Path(outpoint): Path, + ) -> ServerResult { + task::block_in_place(|| { + if !index.has_rune_index() { + return Err(ServerError::NotFound( + "this server has no rune index".to_string(), + )); + } + let runes = index.get_rune_balances_for_outpoint(outpoint)?; + + let response: Vec = runes + .into_iter() + .map(|rune| ordzaar::runes::RuneOutpoint::from_spaced_rune_pile(rune)) + .collect(); + + Ok(Json(response).into_response()) + }) + } + + async fn ordzaar_rune_output_bulk( + Extension(index): Extension>, + Query(query): Query, + ) -> ServerResult { + task::block_in_place(|| { + if !index.has_rune_index() { + return Err(ServerError::NotFound( + "this server has no rune index".to_string(), + )); + } + + let mut results = Vec::new(); + for outpoint_str in ordzaar::runes::str_coma_to_array(&query.outpoints) { + let outpoint_result = OutPoint::from_str(&outpoint_str); + if !outpoint_result.is_ok() { + return Err(ServerError::BadRequest(format!( + "error parsing outpoint: {outpoint_str}" + ))); + } + let outpoint = outpoint_result.as_ref().unwrap(); + + let runes = index.get_rune_balances_for_outpoint(outpoint.clone())?; + + let response: Vec = runes + .into_iter() + .map(|rune| ordzaar::runes::RuneOutpoint::from_spaced_rune_pile(rune)) + .collect(); + + results.push(response) + } + + Ok(Json(results).into_response()) + }) + } + + async fn ordzaar_rune_detail( + Extension(index): Extension>, + Path(DeserializeFromStr(rune_query)): Path>, + ) -> ServerResult { + task::block_in_place(|| { + if !index.has_rune_index() { + return Err(ServerError::NotFound( + "this server has no rune index".to_string(), + )); + } + + let rune = match rune_query { + query::Rune::SpacedRune(spaced_rune) => spaced_rune.rune, + query::Rune::RuneId(rune_id) => index + .get_rune_by_id(rune_id)? + .ok_or_not_found(|| format!("rune {rune_id}"))?, + }; + + let (id, entry, _) = index + .rune(rune)? + .ok_or_not_found(|| format!("rune {rune}"))?; + + Ok(Json(ordzaar::runes::RuneDetail::from_rune(id, entry)).into_response()) + }) + } + // ---- Ordzaar methods ---- fn spawn( @@ -1549,7 +1640,10 @@ impl Server { // ---- Ordzaar ---- inscription_sequence: info.entry.sequence_number, delegate: info.inscription.delegate(), - content_encoding: info.inscription.content_encoding_str().map(|s| s.to_string()), + content_encoding: info + .inscription + .content_encoding_str() + .map(|s| s.to_string()), // ---- Ordzaar ---- }) .into_response() diff --git a/src/subcommand/server/query.rs b/src/subcommand/server/query.rs index 6a57b59349..d34eef4b59 100644 --- a/src/subcommand/server/query.rs +++ b/src/subcommand/server/query.rs @@ -44,7 +44,7 @@ impl Display for Inscription { } } -pub(super) enum Rune { +pub enum Rune { SpacedRune(SpacedRune), RuneId(RuneId), } From 2012c9b7d2da87fd24f2e15858263d62ecb35ad8 Mon Sep 17 00:00:00 2001 From: joundy Date: Thu, 28 Mar 2024 13:14:30 +0700 Subject: [PATCH 2/4] feat(rune): use hashmap instead of vec --- src/subcommand/server.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 82e4be1657..0aa531ac35 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -423,7 +423,7 @@ impl Server { )); } - let mut results = Vec::new(); + let mut results = HashMap::new(); for outpoint_str in ordzaar::runes::str_coma_to_array(&query.outpoints) { let outpoint_result = OutPoint::from_str(&outpoint_str); if !outpoint_result.is_ok() { @@ -440,7 +440,7 @@ impl Server { .map(|rune| ordzaar::runes::RuneOutpoint::from_spaced_rune_pile(rune)) .collect(); - results.push(response) + results.insert(outpoint_str, response); } Ok(Json(results).into_response()) From 76c359b86d5050a9ba9e2f9b99ff452e37976f0f Mon Sep 17 00:00:00 2001 From: joundy Date: Thu, 28 Mar 2024 13:56:25 +0700 Subject: [PATCH 3/4] feat(rune): unecessary clone --- src/subcommand/server.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 0aa531ac35..1b95d5e6c3 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -431,9 +431,7 @@ impl Server { "error parsing outpoint: {outpoint_str}" ))); } - let outpoint = outpoint_result.as_ref().unwrap(); - - let runes = index.get_rune_balances_for_outpoint(outpoint.clone())?; + let runes = index.get_rune_balances_for_outpoint(outpoint_result.unwrap())?; let response: Vec = runes .into_iter() From 316b7a1cb3728cd0e51dba70826d4297812a6030 Mon Sep 17 00:00:00 2001 From: joundy Date: Thu, 28 Mar 2024 16:15:20 +0700 Subject: [PATCH 4/4] feat(rune): add rune_spaced on reponse --- src/ordzaar/runes.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/ordzaar/runes.rs b/src/ordzaar/runes.rs index f6e781ba37..a28c3ef9ad 100644 --- a/src/ordzaar/runes.rs +++ b/src/ordzaar/runes.rs @@ -8,7 +8,7 @@ use super::*; #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct RuneOutpoint { - pub rune: String, + pub rune_spaced: String, pub amount: String, pub divisibility: u8, pub symbol: Option, @@ -24,13 +24,14 @@ pub struct RuneDetailMint { #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct RuneDetail { pub rune_id: RuneId, + pub rune: Rune, + pub rune_spaced: String, pub burned: String, pub divisibility: u8, pub etching: Txid, pub mint: Option, pub mints: String, pub number: String, - pub rune: Rune, pub spacers: u32, pub supply: String, pub symbol: Option, @@ -40,7 +41,7 @@ pub struct RuneDetail { impl RuneOutpoint { pub fn from_spaced_rune_pile(spaced_rune_piled: (SpacedRune, Pile)) -> Self { Self { - rune: format!("{}", spaced_rune_piled.0), + rune_spaced: spaced_rune_piled.0.to_string(), amount: spaced_rune_piled.1.amount.to_string(), divisibility: spaced_rune_piled.1.divisibility, symbol: spaced_rune_piled.1.symbol, @@ -62,15 +63,21 @@ impl RuneDetail { }) } + let rune_spaced = SpacedRune{ + rune: entry.rune, + spacers: entry.spacers + }; + Self { rune_id, + rune: entry.rune, + rune_spaced: rune_spaced.to_string(), burned: entry.burned.to_string(), divisibility: entry.divisibility, etching: entry.etching, mint, mints: entry.mints.to_string(), number: entry.number.to_string(), - rune: entry.rune, spacers: entry.spacers, supply: entry.supply.to_string(), symbol: entry.symbol,