From c85bd1acafb80acf8c364737406410c099364b33 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Mon, 3 Jun 2024 23:05:21 +0200 Subject: [PATCH 1/5] Make recursive endpoints proxiable --- src/subcommand/server.rs | 99 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 6 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 9995c66bf3..6689b07bc4 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1005,16 +1005,26 @@ impl Server { async fn metadata( Extension(index): Extension>, + Extension(server_config): Extension>, Path(inscription_id): Path, - ) -> ServerResult> { + ) -> ServerResult { task::block_in_place(|| { - let metadata = index - .get_inscription_by_id(inscription_id)? - .ok_or_not_found(|| format!("inscription {inscription_id}"))? + let Some(inscription) = index.get_inscription_by_id(inscription_id)? else { + return if let Some(proxy) = server_config.content_proxy.as_ref() { + Self::proxy_recursive(proxy, &format!("/r/metadata/{}", inscription_id)) + } else { + Err(ServerError::NotFound(format!( + "{} not found", + inscription_id + ))) + }; + }; + + let metadata = inscription .metadata .ok_or_not_found(|| format!("inscription {inscription_id} metadata"))?; - Ok(Json(hex::encode(metadata))) + Ok(Json(hex::encode(metadata)).into_response()) }) } @@ -1386,6 +1396,32 @@ impl Server { Redirect::to("https://docs.ordinals.com/bounty/") } + fn proxy_recursive(proxy: &Url, path: &String) -> ServerResult { + let response = reqwest::blocking::Client::new() + .get(format!("{}{}", proxy, path)) + .send() + .map_err(|err| anyhow!(err))?; + + let mut headers = response.headers().clone(); + + headers.insert( + header::CONTENT_SECURITY_POLICY, + HeaderValue::from_str(&format!( + "default-src 'self' {proxy} 'unsafe-eval' 'unsafe-inline' data: blob:" + )) + .map_err(|err| ServerError::Internal(Error::from(err)))?, + ); + + Ok( + ( + response.status(), + headers, + response.bytes().map_err(|err| anyhow!(err))?, + ) + .into_response(), + ) + } + fn proxy_content(proxy: &Url, inscription_id: InscriptionId) -> ServerResult { let response = reqwest::blocking::Client::new() .get(format!("{}content/{}", proxy, inscription_id)) @@ -6551,7 +6587,7 @@ next } #[test] - fn proxy() { + fn content_proxy() { let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); @@ -6584,6 +6620,57 @@ next server_with_proxy.assert_response(format!("/content/{id}"), StatusCode::OK, "foo"); } + #[test] + fn metadata_proxy() { + let server = TestServer::builder().chain(Chain::Regtest).build(); + + server.mine_blocks(1); + + let mut metadata = Vec::new(); + ciborium::into_writer("bar", &mut metadata).unwrap(); + + let inscription = Inscription { + content_type: Some("text/html".into()), + body: Some("foo".into()), + metadata: Some(metadata.clone()), // TODO + ..default() + }; + + let txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription.to_witness())], + ..default() + }); + + server.mine_blocks(1); + + let id = InscriptionId { txid, index: 0 }; + + server.assert_response( + format!("/r/metadata/{id}"), + StatusCode::OK, + &format!("\"{}\"", hex::encode(metadata.clone())), + ); + + let server_with_proxy = TestServer::builder() + .chain(Chain::Regtest) + .server_option("--content-proxy", server.url.as_ref()) + .build(); + + server_with_proxy.mine_blocks(1); + + server.assert_response( + format!("/r/metadata/{id}"), + StatusCode::OK, + &format!("\"{}\"", hex::encode(metadata.clone())), + ); + + server_with_proxy.assert_response( + format!("/r/metadata/{id}"), + StatusCode::OK, + &format!("\"{}\"", hex::encode(metadata.clone())), + ); + } + #[test] fn block_info() { let server = TestServer::new(); From 2fa85a8815de8718a25f9cc12a14606221389416 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Mon, 3 Jun 2024 23:18:06 +0200 Subject: [PATCH 2/5] fix test --- src/subcommand/server.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 6689b07bc4..6eec1ffb85 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1011,7 +1011,7 @@ impl Server { task::block_in_place(|| { let Some(inscription) = index.get_inscription_by_id(inscription_id)? else { return if let Some(proxy) = server_config.content_proxy.as_ref() { - Self::proxy_recursive(proxy, &format!("/r/metadata/{}", inscription_id)) + Self::proxy_recursive(proxy, &format!("/metadata/{}", inscription_id)) } else { Err(ServerError::NotFound(format!( "{} not found", @@ -1398,7 +1398,7 @@ impl Server { fn proxy_recursive(proxy: &Url, path: &String) -> ServerResult { let response = reqwest::blocking::Client::new() - .get(format!("{}{}", proxy, path)) + .get(format!("{}r{}", proxy, path)) .send() .map_err(|err| anyhow!(err))?; @@ -6632,7 +6632,7 @@ next let inscription = Inscription { content_type: Some("text/html".into()), body: Some("foo".into()), - metadata: Some(metadata.clone()), // TODO + metadata: Some(metadata.clone()), ..default() }; From 2a6556718e3ed31bf88ac6bba07bd0a0f3979be1 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Wed, 5 Jun 2024 12:24:24 +0200 Subject: [PATCH 3/5] Amend --- src/subcommand/server.rs | 246 ++++++++++++++++++++++++++++++++------- 1 file changed, 206 insertions(+), 40 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 6eec1ffb85..880dd01fb8 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1011,10 +1011,10 @@ impl Server { task::block_in_place(|| { let Some(inscription) = index.get_inscription_by_id(inscription_id)? else { return if let Some(proxy) = server_config.content_proxy.as_ref() { - Self::proxy_recursive(proxy, &format!("/metadata/{}", inscription_id)) + Self::proxy(proxy, &format!("r/metadata/{}", inscription_id)) } else { Err(ServerError::NotFound(format!( - "{} not found", + "inscription {} not found", inscription_id ))) }; @@ -1030,12 +1030,20 @@ impl Server { async fn inscription_recursive( Extension(index): Extension>, + Extension(server_config): Extension>, Path(inscription_id): Path, ) -> ServerResult { task::block_in_place(|| { - let inscription = index - .get_inscription_by_id(inscription_id)? - .ok_or_not_found(|| format!("inscription {inscription_id}"))?; + let Some(inscription) = index.get_inscription_by_id(inscription_id)? else { + return if let Some(proxy) = server_config.content_proxy.as_ref() { + Self::proxy(proxy, &format!("r/inscription/{}", inscription_id)) + } else { + Err(ServerError::NotFound(format!( + "inscription {} not found", + inscription_id + ))) + }; + }; let entry = index .get_inscription_entry(inscription_id) @@ -1396,35 +1404,9 @@ impl Server { Redirect::to("https://docs.ordinals.com/bounty/") } - fn proxy_recursive(proxy: &Url, path: &String) -> ServerResult { + fn proxy(proxy: &Url, path: &String) -> ServerResult { let response = reqwest::blocking::Client::new() - .get(format!("{}r{}", proxy, path)) - .send() - .map_err(|err| anyhow!(err))?; - - let mut headers = response.headers().clone(); - - headers.insert( - header::CONTENT_SECURITY_POLICY, - HeaderValue::from_str(&format!( - "default-src 'self' {proxy} 'unsafe-eval' 'unsafe-inline' data: blob:" - )) - .map_err(|err| ServerError::Internal(Error::from(err)))?, - ); - - Ok( - ( - response.status(), - headers, - response.bytes().map_err(|err| anyhow!(err))?, - ) - .into_response(), - ) - } - - fn proxy_content(proxy: &Url, inscription_id: InscriptionId) -> ServerResult { - let response = reqwest::blocking::Client::new() - .get(format!("{}content/{}", proxy, inscription_id)) + .get(format!("{}{}", proxy, path)) .send() .map_err(|err| anyhow!(err))?; @@ -1462,10 +1444,10 @@ impl Server { let Some(mut inscription) = index.get_inscription_by_id(inscription_id)? else { return if let Some(proxy) = server_config.content_proxy.as_ref() { - Self::proxy_content(proxy, inscription_id) + Self::proxy(proxy, &format!("content/{}", inscription_id)) } else { Err(ServerError::NotFound(format!( - "{} not found", + "inscription {} not found", inscription_id ))) }; @@ -1810,20 +1792,35 @@ impl Server { async fn children_recursive( Extension(index): Extension>, + Extension(server_config): Extension>, Path(inscription_id): Path, ) -> ServerResult { - Self::children_recursive_paginated(Extension(index), Path((inscription_id, 0))).await + Self::children_recursive_paginated( + Extension(index), + Extension(server_config), + Path((inscription_id, 0)), + ) + .await } async fn children_recursive_paginated( Extension(index): Extension>, + Extension(server_config): Extension>, Path((parent, page)): Path<(InscriptionId, usize)>, ) -> ServerResult { task::block_in_place(|| { - let parent_sequence_number = index - .get_inscription_entry(parent)? - .ok_or_not_found(|| format!("inscription {parent}"))? - .sequence_number; + let Some(parent) = index.get_inscription_entry(parent)? else { + return if let Some(proxy) = server_config.content_proxy.as_ref() { + Self::proxy(proxy, &format!("r/children/{}/{}", parent, page)) + } else { + Err(ServerError::NotFound(format!( + "inscription {} not found", + parent + ))) + }; + }; + + let parent_sequence_number = parent.sequence_number; let (ids, more) = index.get_children_by_sequence_number_paginated(parent_sequence_number, 100, page)?; @@ -6671,6 +6668,175 @@ next ); } + #[test] + fn children_proxy() { + let server = TestServer::builder().chain(Chain::Regtest).build(); + + server.mine_blocks(1); + + let parent_txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())], + ..default() + }); + + let parent_id = InscriptionId { + txid: parent_txid, + index: 0, + }; + + server.assert_response( + format!("/r/children/{parent_id}"), + StatusCode::NOT_FOUND, + &format!("inscription {parent_id} not found"), + ); + + server.mine_blocks(1); + + let children = server.get_json::(format!("/r/children/{parent_id}")); + + assert_eq!(children.ids.len(), 0); + + let mut builder = script::Builder::new(); + for _ in 0..11 { + builder = Inscription { + content_type: Some("text/plain".into()), + body: Some("hello".into()), + parents: vec![parent_id.value()], + unrecognized_even_field: false, + ..default() + } + .append_reveal_script_to_builder(builder); + } + + let witness = Witness::from_slice(&[builder.into_bytes(), Vec::new()]); + + let txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(2, 0, 0, witness), (2, 1, 0, Default::default())], + ..default() + }); + + server.mine_blocks(1); + + let first_child_id = InscriptionId { txid, index: 0 }; + + let children = server.get_json::(format!("/r/children/{parent_id}")); + + assert_eq!(children.ids.len(), 11); + assert_eq!(first_child_id, children.ids[0]); + + let server_with_proxy = TestServer::builder() + .chain(Chain::Regtest) + .server_option("--content-proxy", server.url.as_ref()) + .build(); + + server_with_proxy.mine_blocks(1); + + let children = server.get_json::(format!("/r/children/{parent_id}")); + + assert_eq!(children.ids.len(), 11); + assert_eq!(first_child_id, children.ids[0]); + + let children = server_with_proxy.get_json::(format!("/r/children/{parent_id}")); + + assert_eq!(children.ids.len(), 11); + assert_eq!(first_child_id, children.ids[0]); + } + + #[test] + fn inscription_proxy() { + let server = TestServer::builder().chain(Chain::Regtest).build(); + + server.mine_blocks(1); + + let inscription = Inscription { + content_type: Some("text/html".into()), + body: Some("foo".into()), + ..default() + }; + + let txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription.to_witness())], + ..default() + }); + + server.mine_blocks(1); + + let id = InscriptionId { txid, index: 0 }; + + pretty_assert_eq!( + server.get_json::(format!("/r/inscription/{id}")), + api::InscriptionRecursive { + charms: Vec::new(), + content_type: Some("text/html".into()), + content_length: Some(3), + delegate: None, + fee: 0, + height: 2, + id, + number: 0, + output: OutPoint { txid, vout: 0 }, + sat: None, + satpoint: SatPoint { + outpoint: OutPoint { txid, vout: 0 }, + offset: 0 + }, + timestamp: 2, + value: Some(50 * COIN_VALUE), + } + ); + + let server_with_proxy = TestServer::builder() + .chain(Chain::Regtest) + .server_option("--content-proxy", server.url.as_ref()) + .build(); + + server_with_proxy.mine_blocks(1); + + pretty_assert_eq!( + server.get_json::(format!("/r/inscription/{id}")), + api::InscriptionRecursive { + charms: Vec::new(), + content_type: Some("text/html".into()), + content_length: Some(3), + delegate: None, + fee: 0, + height: 2, + id, + number: 0, + output: OutPoint { txid, vout: 0 }, + sat: None, + satpoint: SatPoint { + outpoint: OutPoint { txid, vout: 0 }, + offset: 0 + }, + timestamp: 2, + value: Some(50 * COIN_VALUE), + } + ); + + assert_eq!( + server_with_proxy.get_json::(format!("/r/inscription/{id}")), + api::InscriptionRecursive { + charms: Vec::new(), + content_type: Some("text/html".into()), + content_length: Some(3), + delegate: None, + fee: 0, + height: 2, + id, + number: 0, + output: OutPoint { txid, vout: 0 }, + sat: None, + satpoint: SatPoint { + outpoint: OutPoint { txid, vout: 0 }, + offset: 0 + }, + timestamp: 2, + value: Some(50 * COIN_VALUE), + } + ); + } + #[test] fn block_info() { let server = TestServer::new(); From d5dc78d8fa3b0ad2eb9b76bd94e047e3599d56ea Mon Sep 17 00:00:00 2001 From: raphjaph Date: Wed, 5 Jun 2024 12:34:24 +0200 Subject: [PATCH 4/5] Amend --- docs/po/zh.po | 4 ++-- docs/src/guides/testing.md | 4 ++-- src/subcommand/env.rs | 10 +++++----- src/subcommand/server.rs | 22 +++++++++++----------- src/subcommand/server/server_config.rs | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/po/zh.po b/docs/po/zh.po index 05e20a5a21..f8f70d6d82 100644 --- a/docs/po/zh.po +++ b/docs/po/zh.po @@ -7642,7 +7642,7 @@ msgid "" "of the mainnet inscriptions." msgstr "" "为了避免在测试时必须将依赖铭文ID更改为主网铭文ID,你可以在测试时使用内容代" -"理。`ord server`接受一个`--content-proxy`选项,它需要另一个`ord server`实例的" +"理。`ord server`接受一个`--proxy`选项,它需要另一个`ord server`实例的" "URL。当设置了内容代理并且铭文未找到时,向`/content/`发出请" "求,`ord server`将会将请求转发给内容代理。这允许你运行一个带有主网内容代理的" "测试`ord server`实例。然后你可以在测试铭文中使用主网铭文ID,这将返回主网铭文" @@ -7651,7 +7651,7 @@ msgstr "" #: src/guides/testing.md:155 msgid "" "```\n" -"ord --regtest server --content-proxy https://ordinals.com\n" +"ord --regtest server --proxy https://ordinals.com\n" "```" msgstr "" diff --git a/docs/src/guides/testing.md b/docs/src/guides/testing.md index 26a13272b4..09765b2866 100644 --- a/docs/src/guides/testing.md +++ b/docs/src/guides/testing.md @@ -145,7 +145,7 @@ bitcoin-cli generatetoaddress 6 To avoid having to change dependency inscription IDs to mainnet inscription IDs, you may utilize a content proxy when testing. `ord server` accepts a -`--content-proxy` option, which takes the URL of a another `ord server` +`--proxy` option, which takes the URL of a another `ord server` instance. When making a request to `/content/` when a content proxy is set and the inscription is not found, `ord server` will forward the request to the content proxy. This allows you to run a test `ord server` @@ -154,5 +154,5 @@ in your test inscription, which will then return the content of the mainnet inscriptions. ``` -ord --regtest server --content-proxy https://ordinals.com +ord --regtest server --proxy https://ordinals.com ``` diff --git a/src/subcommand/env.rs b/src/subcommand/env.rs index ed3340b2f4..b968b5c355 100644 --- a/src/subcommand/env.rs +++ b/src/subcommand/env.rs @@ -23,9 +23,9 @@ pub(crate) struct Env { pub(crate) decompress: bool, #[arg( long, - help = "Proxy `/content/INSCRIPTION_ID` requests to `/content/INSCRIPTION_ID` if the inscription is not present on current chain." + help = "Proxy `/content/INSCRIPTION_ID` and `/r/metadata/INSCRIPTION_ID` requests to `/content/INSCRIPTION_ID` if the inscription is not present on current chain." )] - pub(crate) content_proxy: Option, + pub(crate) proxy: Option, } #[derive(Serialize)] @@ -137,7 +137,7 @@ rpcport={bitcoind_port} let ord = std::env::current_exe()?; let decompress = self.decompress; - let content_proxy = self.content_proxy.map(|url| url.to_string()); + let proxy = self.proxy.map(|url| url.to_string()); let mut command = Command::new(&ord); let ord_server = command @@ -152,8 +152,8 @@ rpcport={bitcoind_port} ord_server.arg("--decompress"); } - if let Some(content_proxy) = content_proxy { - ord_server.arg("--content-proxy").arg(content_proxy); + if let Some(proxy) = proxy { + ord_server.arg("--proxy").arg(proxy); } let _ord = KillOnDrop(ord_server.spawn()?); diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 880dd01fb8..ba9a6346c8 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -128,9 +128,9 @@ pub struct Server { pub(crate) no_sync: bool, #[arg( long, - help = "Proxy `/content/INSCRIPTION_ID` requests to `/content/INSCRIPTION_ID` if the inscription is not present on current chain." + help = "Proxy `/content/INSCRIPTION_ID` and `/r/metadata/INSCRIPTION_ID` requests to `/content/INSCRIPTION_ID` if the inscription is not present on current chain." )] - pub(crate) content_proxy: Option, + pub(crate) proxy: Option, #[arg( long, default_value = "5s", @@ -170,7 +170,7 @@ impl Server { let server_config = Arc::new(ServerConfig { chain: settings.chain(), - content_proxy: self.content_proxy.clone(), + proxy: self.proxy.clone(), csp_origin: self.csp_origin.clone(), decompress: self.decompress, domain: acme_domains.first().cloned(), @@ -1010,7 +1010,7 @@ impl Server { ) -> ServerResult { task::block_in_place(|| { let Some(inscription) = index.get_inscription_by_id(inscription_id)? else { - return if let Some(proxy) = server_config.content_proxy.as_ref() { + return if let Some(proxy) = server_config.proxy.as_ref() { Self::proxy(proxy, &format!("r/metadata/{}", inscription_id)) } else { Err(ServerError::NotFound(format!( @@ -1035,7 +1035,7 @@ impl Server { ) -> ServerResult { task::block_in_place(|| { let Some(inscription) = index.get_inscription_by_id(inscription_id)? else { - return if let Some(proxy) = server_config.content_proxy.as_ref() { + return if let Some(proxy) = server_config.proxy.as_ref() { Self::proxy(proxy, &format!("r/inscription/{}", inscription_id)) } else { Err(ServerError::NotFound(format!( @@ -1443,7 +1443,7 @@ impl Server { } let Some(mut inscription) = index.get_inscription_by_id(inscription_id)? else { - return if let Some(proxy) = server_config.content_proxy.as_ref() { + return if let Some(proxy) = server_config.proxy.as_ref() { Self::proxy(proxy, &format!("content/{}", inscription_id)) } else { Err(ServerError::NotFound(format!( @@ -1810,7 +1810,7 @@ impl Server { ) -> ServerResult { task::block_in_place(|| { let Some(parent) = index.get_inscription_entry(parent)? else { - return if let Some(proxy) = server_config.content_proxy.as_ref() { + return if let Some(proxy) = server_config.proxy.as_ref() { Self::proxy(proxy, &format!("r/children/{}/{}", parent, page)) } else { Err(ServerError::NotFound(format!( @@ -6608,7 +6608,7 @@ next let server_with_proxy = TestServer::builder() .chain(Chain::Regtest) - .server_option("--content-proxy", server.url.as_ref()) + .server_option("--proxy", server.url.as_ref()) .build(); server_with_proxy.mine_blocks(1); @@ -6650,7 +6650,7 @@ next let server_with_proxy = TestServer::builder() .chain(Chain::Regtest) - .server_option("--content-proxy", server.url.as_ref()) + .server_option("--proxy", server.url.as_ref()) .build(); server_with_proxy.mine_blocks(1); @@ -6726,7 +6726,7 @@ next let server_with_proxy = TestServer::builder() .chain(Chain::Regtest) - .server_option("--content-proxy", server.url.as_ref()) + .server_option("--proxy", server.url.as_ref()) .build(); server_with_proxy.mine_blocks(1); @@ -6787,7 +6787,7 @@ next let server_with_proxy = TestServer::builder() .chain(Chain::Regtest) - .server_option("--content-proxy", server.url.as_ref()) + .server_option("--proxy", server.url.as_ref()) .build(); server_with_proxy.mine_blocks(1); diff --git a/src/subcommand/server/server_config.rs b/src/subcommand/server/server_config.rs index 79f141ebdd..ffa9c02ca8 100644 --- a/src/subcommand/server/server_config.rs +++ b/src/subcommand/server/server_config.rs @@ -3,7 +3,7 @@ use {super::*, axum::http::HeaderName}; #[derive(Default)] pub(crate) struct ServerConfig { pub(crate) chain: Chain, - pub(crate) content_proxy: Option, + pub(crate) proxy: Option, pub(crate) csp_origin: Option, pub(crate) decompress: bool, pub(crate) domain: Option, From e29bfabf7f3c57e60aa7c8850fd1cda0d688437f Mon Sep 17 00:00:00 2001 From: raphjaph Date: Wed, 5 Jun 2024 13:26:26 +0200 Subject: [PATCH 5/5] Amend --- src/subcommand/env.rs | 2 +- src/subcommand/server.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/subcommand/env.rs b/src/subcommand/env.rs index b968b5c355..1e38a8a576 100644 --- a/src/subcommand/env.rs +++ b/src/subcommand/env.rs @@ -23,7 +23,7 @@ pub(crate) struct Env { pub(crate) decompress: bool, #[arg( long, - help = "Proxy `/content/INSCRIPTION_ID` and `/r/metadata/INSCRIPTION_ID` requests to `/content/INSCRIPTION_ID` if the inscription is not present on current chain." + help = "Proxy `/content/INSCRIPTION_ID` and other recursive endpoints to `` if the inscription is not present on current chain." )] pub(crate) proxy: Option, } diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index ba9a6346c8..dc0e79b96c 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -128,7 +128,7 @@ pub struct Server { pub(crate) no_sync: bool, #[arg( long, - help = "Proxy `/content/INSCRIPTION_ID` and `/r/metadata/INSCRIPTION_ID` requests to `/content/INSCRIPTION_ID` if the inscription is not present on current chain." + help = "Proxy `/content/INSCRIPTION_ID` and other recursive endpoints to `` if the inscription is not present on current chain." )] pub(crate) proxy: Option, #[arg(