Skip to content

Commit

Permalink
Add /r/utxo/:outpoint endpoint (#4148)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Dec 23, 2024
1 parent a0da0c1 commit a2d397a
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 1 deletion.
2 changes: 1 addition & 1 deletion docs/src/guides/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5129,4 +5129,4 @@ curl -s -H "Accept: application/json" \

See [Recursion](../inscriptions/recursion.md) for an explanation of these.

{{#include ../inscriptions/recursion.md:35:3371}}
{{#include ../inscriptions/recursion.md:35:3429}}
58 changes: 58 additions & 0 deletions docs/src/inscriptions/recursion.md
Original file line number Diff line number Diff line change
Expand Up @@ -3370,6 +3370,64 @@ curl -s -H "Accept: application/json" \
```
</details>

<details>
<summary>
<code>GET</code>
<code><b>/r/utxo/&lt;OUTPOINT&gt;</b></code>
</summary>

### Description

Get assets held by an unspent transaction output.

### Examples

Unspent transaction output with server without any indices:

```bash
curl -s -H "Accept: application/json" \
http://0.0.0.0:80/r/utxo/4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0
```

```json
{
"inscriptions": null,
"runes": null,
"sat_ranges": null,
"value": 5000000000
}
```

With rune, inscription, and sat index:

```bash
curl -s -H "Accept: application/json" \
http://0.0.0.0:80/r/utxo/626860df36c1047194866c6812f04c15ab84f3690e7cc06fd600c841f1943e05:0
```

```json
{
"inscriptions": [
"6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0"
],
"runes": {
"UNCOMMON•GOODS": {
"amount": 6845,
"divisibility": 0,
"symbol": ""
}
},
"sat_ranges": [
[
1905800627509113,
1905800627509443
]
],
"value": 330
}
```
</details>

&nbsp;
&nbsp;

Expand Down
8 changes: 8 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ pub struct Inscriptions {
pub page_index: u32,
}

#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct UtxoRecursive {
pub inscriptions: Option<Vec<InscriptionId>>,
pub runes: Option<BTreeMap<SpacedRune, Pile>>,
pub sat_ranges: Option<Vec<(u64, u64)>>,
pub value: u64,
}

#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct Output {
pub address: Option<Address<NetworkUnchecked>>,
Expand Down
21 changes: 21 additions & 0 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2358,6 +2358,27 @@ impl Index {
Ok(acc)
}

pub(crate) fn get_utxo_recursive(
&self,
outpoint: OutPoint,
) -> Result<Option<api::UtxoRecursive>> {
let Some(utxo_entry) = self
.database
.begin_read()?
.open_table(OUTPOINT_TO_UTXO_ENTRY)?
.get(&outpoint.store())?
else {
return Ok(None);
};

Ok(Some(api::UtxoRecursive {
inscriptions: self.get_inscriptions_for_output(outpoint)?,
runes: self.get_rune_balances_for_output(outpoint)?,
sat_ranges: self.list(outpoint)?,
value: utxo_entry.value().parse(self).total_value(),
}))
}

pub(crate) fn get_output_info(&self, outpoint: OutPoint) -> Result<Option<(api::Output, TxOut)>> {
let sat_ranges = self.list(outpoint)?;

Expand Down
137 changes: 137 additions & 0 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ impl Server {
"/r/sat/:sat_number/at/:index",
get(Self::sat_inscription_at_index),
)
.route("/r/utxo/:outpoint", get(Self::utxo_recursive))
.route("/rare.txt", get(Self::rare_txt))
.route("/rune/:rune", get(Self::rune))
.route("/runes", get(Self::runes))
Expand Down Expand Up @@ -659,6 +660,22 @@ impl Server {
})
}

async fn utxo_recursive(
Extension(index): Extension<Arc<Index>>,
Path(outpoint): Path<OutPoint>,
) -> ServerResult {
task::block_in_place(|| {
Ok(
Json(
index
.get_utxo_recursive(outpoint)?
.ok_or_not_found(|| format!("output {outpoint}"))?,
)
.into_response(),
)
})
}

async fn satpoint(
Extension(index): Extension<Arc<Index>>,
Path(satpoint): Path<SatPoint>,
Expand Down Expand Up @@ -6335,6 +6352,126 @@ next
);
}

#[test]
fn utxo_recursive_endpoint_all() {
let server = TestServer::builder()
.chain(Chain::Regtest)
.index_sats()
.index_runes()
.build();

let rune = Rune(RUNE);

let (txid, id) = server.etch(
Runestone {
edicts: vec![Edict {
id: RuneId::default(),
amount: u128::MAX,
output: 0,
}],
etching: Some(Etching {
divisibility: Some(1),
rune: Some(rune),
premine: Some(u128::MAX),
..default()
}),
..default()
},
1,
None,
);

pretty_assert_eq!(
server.index.runes().unwrap(),
[(
id,
RuneEntry {
block: id.block,
divisibility: 1,
etching: txid,
spaced_rune: SpacedRune { rune, spacers: 0 },
premine: u128::MAX,
timestamp: id.block,
..default()
}
)]
);

server.mine_blocks(1);

// merge rune with two inscriptions
let txid = server.core.broadcast_tx(TransactionTemplate {
inputs: &[
(6, 0, 0, inscription("text/plain", "foo").to_witness()),
(7, 0, 0, inscription("text/plain", "bar").to_witness()),
(7, 1, 0, Witness::new()),
],
..default()
});

server.mine_blocks(1);

let inscription_id = InscriptionId { txid, index: 0 };
let second_inscription_id = InscriptionId { txid, index: 1 };
let outpoint: OutPoint = OutPoint { txid, vout: 0 };

let utxo_recursive = server.get_json::<api::UtxoRecursive>(format!("/r/utxo/{}", outpoint));

pretty_assert_eq!(
utxo_recursive,
api::UtxoRecursive {
inscriptions: Some(vec![inscription_id, second_inscription_id]),
runes: Some(
[(
SpacedRune { rune, spacers: 0 },
Pile {
amount: u128::MAX,
divisibility: 1,
symbol: None
}
)]
.into_iter()
.collect()
),
sat_ranges: Some(vec![
(6 * 50 * COIN_VALUE, 7 * 50 * COIN_VALUE),
(7 * 50 * COIN_VALUE, 8 * 50 * COIN_VALUE),
(50 * COIN_VALUE, 2 * 50 * COIN_VALUE)
]),
value: 150 * COIN_VALUE,
}
);
}

#[test]
fn utxo_recursive_endpoint_only_inscriptions() {
let server = TestServer::builder().chain(Chain::Regtest).build();

server.mine_blocks(1);

let txid = server.core.broadcast_tx(TransactionTemplate {
inputs: &[(1, 0, 0, inscription("text/plain", "foo").to_witness())],
..default()
});

server.mine_blocks(1);

let inscription_id = InscriptionId { txid, index: 0 };
let outpoint: OutPoint = OutPoint { txid, vout: 0 };

let utxo_recursive = server.get_json::<api::UtxoRecursive>(format!("/r/utxo/{}", outpoint));

pretty_assert_eq!(
utxo_recursive,
api::UtxoRecursive {
inscriptions: Some(vec![inscription_id]),
runes: None,
sat_ranges: None,
value: 50 * COIN_VALUE,
}
);
}

#[test]
fn sat_recursive_endpoints() {
let server = TestServer::builder()
Expand Down

0 comments on commit a2d397a

Please sign in to comment.