Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add /address/:address/cardinals API #3979

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
55 changes: 52 additions & 3 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ impl Server {
let router = Router::new()
.route("/", get(Self::home))
.route("/address/:address", get(Self::address))
.route("/address/:address/cardinals", get(Self::address_cardinals))
.route("/block/:query", get(Self::block))
.route("/blockcount", get(Self::block_count))
.route("/blockhash", get(Self::block_hash))
Expand Down Expand Up @@ -855,9 +856,7 @@ impl Server {
.require_network(server_config.chain.network())
.map_err(|err| ServerError::BadRequest(err.to_string()))?;

let mut outputs = index.get_address_info(&address)?;

outputs.sort();
let outputs = index.get_address_info(&address)?;

let sat_balance = index.get_sat_balances_for_outputs(&outputs)?;

Expand Down Expand Up @@ -887,6 +886,56 @@ impl Server {
})
}

async fn address_cardinals(
Extension(server_config): Extension<Arc<ServerConfig>>,
Extension(index): Extension<Arc<Index>>,
Path(address): Path<Address<NetworkUnchecked>>,
AcceptJson(accept_json): AcceptJson,
) -> ServerResult {
task::block_in_place(|| {
Ok(if accept_json {
let address = address
.require_network(server_config.chain.network())
.map_err(|err| ServerError::BadRequest(err.to_string()))?;

let mut outputs = index.get_address_info(&address)?;

outputs.sort();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do people rely on lexicographical ordering? Or should it be ordered by size?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure either! But followed the precedence of address

outputs.sort();


let cardinal_outputs: Vec<OutPoint> = outputs
.into_iter()
.filter(|output| {
let has_inscriptions = index
.get_inscriptions_on_output_with_satpoints(*output)
.map(|inscriptions| !inscriptions.is_empty())
.unwrap_or(false);

let has_runes = index
.get_rune_balances_for_output(*output)
.map(|runes| !runes.is_empty())
.unwrap_or(false);

!has_inscriptions && !has_runes
})
.collect();

let mut response = Vec::new();

for outpoint in cardinal_outputs {
let (output_info, _) = index
.get_output_info(outpoint)?
.ok_or_not_found(|| format!("output {outpoint}"))?;

response.push(output_info);
}

Json(response).into_response()
} else {
StatusCode::NOT_FOUND.into_response()
})
})
}

async fn block(
Extension(server_config): Extension<Arc<ServerConfig>>,
Extension(index): Extension<Arc<Index>>,
Expand Down
48 changes: 48 additions & 0 deletions tests/json_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,54 @@ fn get_output() {
);
}

#[test]
fn address_cardinals_api() {
let core = mockcore::spawn();

let ord = TestServer::spawn_with_args(
&core,
&["--index-runes", "--index-sats", "--index-addresses"],
);

create_wallet(&core, &ord);
core.mine_blocks(1);

let address = "bc1qxma2dwmutht4vxyl6u395slew5ectfpn35ug9l";

let send = CommandBuilder::new(format!("wallet send --fee-rate 13.3 {address} 2btc"))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();

core.mine_blocks(1);

let response = ord.json_request(format!("/address/{}/cardinals", address));

assert_eq!(response.status(), StatusCode::OK);

let cardinals_json: Vec<api::Output> = serde_json::from_str(&response.text().unwrap()).unwrap();

pretty_assert_eq!(
cardinals_json,
vec![api::Output {
address: Some(address.parse().unwrap()),
inscriptions: vec![],
indexed: true,
runes: BTreeMap::new(),
sat_ranges: Some(vec![(5000000000, 5200000000),]),
script_pubkey: ScriptBuf::from(
address
.parse::<Address<NetworkUnchecked>>()
.unwrap()
.assume_checked()
),
spent: false,
transaction: send.txid,
value: 2 * COIN_VALUE,
}]
);
}

Comment on lines +365 to +412
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to add a test that asserts that runic and inscription utxos are not returned.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, will add them!

Do you think we should have /address/:address/ordinals endpoint for fetching just inscriptions and runes? I'm not sure about naming it /ordinals if it includes Runes, but it goes well with /cardinals.

#[test]
fn json_request_fails_when_disabled() {
let core = mockcore::spawn();
Expand Down
Loading