diff --git a/src/serve/minibf/mod.rs b/src/serve/minibf/mod.rs index 5021298..3323d71 100644 --- a/src/serve/minibf/mod.rs +++ b/src/serve/minibf/mod.rs @@ -40,9 +40,11 @@ pub async fn serve( .mount( "/", routes![ + // Accounts + routes::accounts::stake_address::utxos::route, // Addresses - routes::addresses::address::utxo::route, - routes::addresses::address::utxo::asset::route, + routes::addresses::address::utxos::route, + routes::addresses::address::utxos::asset::route, // Blocks routes::blocks::latest::route, routes::blocks::latest::txs::route, diff --git a/src/serve/minibf/routes/accounts/mod.rs b/src/serve/minibf/routes/accounts/mod.rs new file mode 100644 index 0000000..1d694c0 --- /dev/null +++ b/src/serve/minibf/routes/accounts/mod.rs @@ -0,0 +1 @@ +pub mod stake_address; diff --git a/src/serve/minibf/routes/accounts/stake_address/mod.rs b/src/serve/minibf/routes/accounts/stake_address/mod.rs new file mode 100644 index 0000000..8ace578 --- /dev/null +++ b/src/serve/minibf/routes/accounts/stake_address/mod.rs @@ -0,0 +1 @@ +pub mod utxos; diff --git a/src/serve/minibf/routes/accounts/stake_address/utxos.rs b/src/serve/minibf/routes/accounts/stake_address/utxos.rs new file mode 100644 index 0000000..fc2437c --- /dev/null +++ b/src/serve/minibf/routes/accounts/stake_address/utxos.rs @@ -0,0 +1,111 @@ +use pallas::ledger::{addresses::Address, primitives::conway, traverse::MultiEraAsset}; +use rocket::{get, http::Status, State}; +use serde::{Deserialize, Serialize}; + +use crate::{ + ledger::{EraCbor, TxoRef}, + state::LedgerStore, +}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Amount { + pub unit: String, + pub quantity: String, +} + +impl Amount { + fn lovelace(quantity: u64) -> Self { + Self { + unit: "lovelace".to_string(), + quantity: quantity.to_string(), + } + } +} + +impl From> for Amount { + fn from(value: MultiEraAsset<'_>) -> Self { + Self { + unit: value.policy().to_string(), + quantity: value.any_coin().to_string(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct AccountUtxo { + pub address: String, + pub tx_hash: String, + pub output_index: u32, + pub amount: Vec, + pub block: String, + pub data_hash: Option, + pub inline_datum: Option, + pub reference_script_hash: Option, + // Note: tx_index is deprecated, but included here for completeness. + pub tx_index: u32, +} + +impl TryFrom<(TxoRef, EraCbor)> for AccountUtxo { + type Error = Status; + + fn try_from((txo, era): (TxoRef, EraCbor)) -> Result { + let parsed = pallas::ledger::traverse::MultiEraOutput::decode(era.0, &era.1) + .map_err(|_| Status::InternalServerError)?; + + let value = parsed.value(); + let lovelace = Amount::lovelace(value.coin()); + let assets: Vec = value + .assets() + .iter() + .flat_map(|x| x.assets()) + .map(|x| x.into()) + .collect(); + + Ok(Self { + address: parsed + .address() + .map_err(|_| Status::InternalServerError)? + .to_string(), + tx_index: txo.1, + output_index: txo.1, + tx_hash: txo.0.to_string(), + amount: std::iter::once(lovelace).chain(assets).collect(), + data_hash: parsed.datum().and_then(|x| match x { + conway::PseudoDatumOption::Hash(hash) => Some(hash.to_string()), + conway::PseudoDatumOption::Data(_) => None, + }), + inline_datum: parsed.datum().and_then(|x| match x { + conway::PseudoDatumOption::Hash(_) => None, + conway::PseudoDatumOption::Data(x) => Some(hex::encode(x.raw_cbor())), + }), + ..Default::default() + }) + } +} + +#[get("/accounts//utxos")] +pub fn route( + stake_address: String, + ledger: &State, +) -> Result>, Status> { + let stake = match pallas::ledger::addresses::Address::from_bech32(&stake_address) + .map_err(|_| Status::BadRequest)? + { + Address::Shelley(x) => x.delegation().to_vec(), + Address::Stake(x) => x.to_vec(), + Address::Byron(_) => return Err(Status::BadRequest), + }; + + let refs = ledger + .get_utxo_by_stake(&stake) + .map_err(|_| Status::InternalServerError)?; + + let utxos: Vec<_> = ledger + .get_utxos(refs.into_iter().collect()) + .map_err(|_| Status::InternalServerError)? + .into_iter() + .map(AccountUtxo::try_from) + .collect::>()?; + + Ok(rocket::serde::json::Json(utxos)) +} diff --git a/src/serve/minibf/routes/addresses/address/mod.rs b/src/serve/minibf/routes/addresses/address/mod.rs index 5b379e4..8ace578 100644 --- a/src/serve/minibf/routes/addresses/address/mod.rs +++ b/src/serve/minibf/routes/addresses/address/mod.rs @@ -1 +1 @@ -pub mod utxo; +pub mod utxos; diff --git a/src/serve/minibf/routes/addresses/address/utxo/asset.rs b/src/serve/minibf/routes/addresses/address/utxos/asset.rs similarity index 91% rename from src/serve/minibf/routes/addresses/address/utxo/asset.rs rename to src/serve/minibf/routes/addresses/address/utxos/asset.rs index f9f2285..30db6dc 100644 --- a/src/serve/minibf/routes/addresses/address/utxo/asset.rs +++ b/src/serve/minibf/routes/addresses/address/utxos/asset.rs @@ -1,6 +1,7 @@ use rocket::{get, http::Status, State}; -use crate::{serve::minibf::routes::addresses::address::utxo::Utxo, state::LedgerStore}; +use super::Utxo; +use crate::state::LedgerStore; #[get("/addresses/
/utxos/")] pub fn route( diff --git a/src/serve/minibf/routes/addresses/address/utxo/mod.rs b/src/serve/minibf/routes/addresses/address/utxos/mod.rs similarity index 100% rename from src/serve/minibf/routes/addresses/address/utxo/mod.rs rename to src/serve/minibf/routes/addresses/address/utxos/mod.rs diff --git a/src/serve/minibf/routes/mod.rs b/src/serve/minibf/routes/mod.rs index a0e0101..500891f 100644 --- a/src/serve/minibf/routes/mod.rs +++ b/src/serve/minibf/routes/mod.rs @@ -1,2 +1,3 @@ +pub mod accounts; pub mod addresses; pub mod blocks;