Change get_txchain_tx to take the public txid (a unique field) instead of the non-public internal txchain item id, and promote it to a production endpoint. Mount the two remaining debug endpoints (list_ido_txchain, list_txchain_tracker_map) only when `debug = true` in the config. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
192 lines
6.4 KiB
Rust
192 lines
6.4 KiB
Rust
// Copyright (C) 2024-2026 Whiterun LLC
|
|
//
|
|
// This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later.
|
|
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html
|
|
|
|
use crate::db::blob::{blob_to_display_hex, display_hex_to_blob};
|
|
use crate::db::DB;
|
|
use crate::rpc::err::{bad_request, db_error, not_found, ApiErrorCode, CachedApiResult};
|
|
use crate::rpc::response::{cached_ok, CACHE_NONE};
|
|
use bitcoincash::{TokenID, Txid};
|
|
use rocket::{get, State};
|
|
use serde_json::Value;
|
|
|
|
const LIST_DEFAULT_LIMIT: i64 = 20;
|
|
const LIST_MAX_LIMIT: i64 = 100;
|
|
|
|
const DEBUG_DEFAULT_LIMIT: i64 = 100;
|
|
const DEBUG_MAX_LIMIT: i64 = 10_000;
|
|
|
|
/// List IDOs with optional filters and pagination.
|
|
///
|
|
/// Filters:
|
|
/// - `is_valid`: boolean filter on the is_valid flag
|
|
/// - `status`: one of PREINIT, ACTIVE, DISTRIBUTING, DISTRIBUTED
|
|
/// - `offered_token_id`: 64-char hex token ID (reversed display format)
|
|
///
|
|
/// Pagination: `offset` (default 0), `limit` (default 20, max 100)
|
|
#[get("/list?<is_valid>&<status>&<offered_token_id>&<offset>&<limit>")]
|
|
pub async fn list_idos(
|
|
is_valid: Option<bool>,
|
|
status: Option<String>,
|
|
offered_token_id: Option<&str>,
|
|
offset: Option<i64>,
|
|
limit: Option<i64>,
|
|
db: &State<DB>,
|
|
) -> CachedApiResult<Value> {
|
|
let offset = offset.unwrap_or(0).max(0);
|
|
let limit = limit.unwrap_or(LIST_DEFAULT_LIMIT).clamp(1, LIST_MAX_LIMIT);
|
|
|
|
let offered_token_id_blob = offered_token_id
|
|
.map(|s| {
|
|
display_hex_to_blob::<TokenID>(s).map_err(|e| {
|
|
bad_request(
|
|
ApiErrorCode::InvalidParameters,
|
|
&format!("Invalid offered_token_id: {e}"),
|
|
)
|
|
})
|
|
})
|
|
.transpose()?;
|
|
|
|
let items =
|
|
crate::db::ido::list_idos(&db.ido_r, is_valid, status, offered_token_id_blob, offset, limit)
|
|
.await
|
|
.map_err(db_error)?;
|
|
|
|
Ok(cached_ok(serde_json::to_value(items).unwrap(), CACHE_NONE))
|
|
}
|
|
|
|
/// Get a single IDO by its offering token ID. Returns null if not found.
|
|
#[get("/by-offering-token/<offering_token_id>")]
|
|
pub async fn get_ido_by_offering_token(
|
|
offering_token_id: &str,
|
|
db: &State<DB>,
|
|
) -> CachedApiResult<Value> {
|
|
let blob = display_hex_to_blob::<TokenID>(offering_token_id).map_err(|e| {
|
|
bad_request(
|
|
ApiErrorCode::InvalidParameters,
|
|
&format!("Invalid offering_token_id: {e}"),
|
|
)
|
|
})?;
|
|
|
|
let item = crate::db::ido::get_ido_by_offering_token_id(&db.ido_r, blob)
|
|
.await
|
|
.map_err(db_error)?;
|
|
|
|
Ok(cached_ok(serde_json::to_value(item).unwrap(), CACHE_NONE))
|
|
}
|
|
|
|
/// List entries for an IDO.
|
|
///
|
|
/// - `id`: the IDO's public id (preinit txid, 64-char hex)
|
|
/// - `distributed`: optional boolean filter
|
|
///
|
|
/// Pagination: `offset` (default 0), `limit` (default 20, max 100)
|
|
#[get("/<id>/entries?<distributed>&<offset>&<limit>")]
|
|
pub async fn list_ido_entries(
|
|
id: &str,
|
|
distributed: Option<bool>,
|
|
offset: Option<i64>,
|
|
limit: Option<i64>,
|
|
db: &State<DB>,
|
|
) -> CachedApiResult<Value> {
|
|
let offset = offset.unwrap_or(0).max(0);
|
|
let limit = limit.unwrap_or(LIST_DEFAULT_LIMIT).clamp(1, LIST_MAX_LIMIT);
|
|
|
|
let (internal_id, preinit_txid_hex) = resolve_ido(id, &db.ido_r).await?;
|
|
|
|
let items = crate::db::ido::list_ido_entries(&db.ido_r, internal_id, &preinit_txid_hex, distributed, offset, limit)
|
|
.await
|
|
.map_err(db_error)?;
|
|
|
|
Ok(cached_ok(serde_json::to_value(items).unwrap(), CACHE_NONE))
|
|
}
|
|
|
|
/// Get the raw transaction hex for a txchain item by its txid.
|
|
/// Returns `{"tx": "<hex>"}` or `{"tx": null}` if not found.
|
|
#[get("/txchain/<txid>/tx")]
|
|
pub async fn get_txchain_tx(
|
|
txid: &str,
|
|
db: &State<DB>,
|
|
) -> CachedApiResult<Value> {
|
|
let txid_blob = display_hex_to_blob::<Txid>(txid).map_err(|e| {
|
|
bad_request(
|
|
ApiErrorCode::InvalidParameters,
|
|
&format!("Invalid txid hex: {e}"),
|
|
)
|
|
})?;
|
|
|
|
let tx_hex = crate::db::ido::get_txchain_tx_hex(&db.ido_r, &txid_blob)
|
|
.await
|
|
.map_err(db_error)?;
|
|
|
|
Ok(cached_ok(
|
|
serde_json::json!({ "tx": tx_hex }),
|
|
CACHE_NONE,
|
|
))
|
|
}
|
|
|
|
/// [Debug] List the txchain for an IDO, sorted oldest-first (head is last).
|
|
///
|
|
/// - `id`: the IDO's public id (preinit txid, 64-char hex)
|
|
///
|
|
/// Pagination: `offset` (default 0), `limit` (default 100, max 10000)
|
|
#[get("/<id>/txchain?<offset>&<limit>")]
|
|
pub async fn list_ido_txchain(
|
|
id: &str,
|
|
offset: Option<i64>,
|
|
limit: Option<i64>,
|
|
db: &State<DB>,
|
|
) -> CachedApiResult<Value> {
|
|
let offset = offset.unwrap_or(0).max(0);
|
|
let limit = limit.unwrap_or(DEBUG_DEFAULT_LIMIT).clamp(1, DEBUG_MAX_LIMIT);
|
|
|
|
let (internal_id, preinit_txid_hex) = resolve_ido(id, &db.ido_r).await?;
|
|
|
|
let items = crate::db::ido::list_ido_txchain(&db.ido_r, internal_id, &preinit_txid_hex, offset, limit)
|
|
.await
|
|
.map_err(db_error)?;
|
|
|
|
Ok(cached_ok(serde_json::to_value(items).unwrap(), CACHE_NONE))
|
|
}
|
|
|
|
async fn resolve_ido(
|
|
id: &str,
|
|
pool: &sqlx::SqlitePool,
|
|
) -> Result<(i64, String), rocket::response::status::Custom<rocket::serde::json::Json<Value>>> {
|
|
let preinit_blob = display_hex_to_blob::<Txid>(id).map_err(|e| {
|
|
bad_request(
|
|
ApiErrorCode::InvalidParameters,
|
|
&format!("Invalid IDO id (expected preinit_txid hex): {e}"),
|
|
)
|
|
})?;
|
|
|
|
let internal_id = crate::db::ido::lookup_internal_id_by_preinit_txid(pool, &preinit_blob)
|
|
.await
|
|
.map_err(db_error)?
|
|
.ok_or_else(|| not_found(ApiErrorCode::IdoNotFound, "IDO not found"))?;
|
|
|
|
let preinit_txid_hex = blob_to_display_hex::<Txid>(&preinit_blob)
|
|
.map_err(|e| bad_request(ApiErrorCode::InvalidParameters, &format!("Invalid IDO id: {e}")))?;
|
|
|
|
Ok((internal_id, preinit_txid_hex))
|
|
}
|
|
|
|
/// [Debug] List all entries in the txchain tracker map.
|
|
///
|
|
/// Pagination: `offset` (default 0), `limit` (default 100, max 10000)
|
|
#[get("/txchain-tracker?<offset>&<limit>")]
|
|
pub async fn list_txchain_tracker_map(
|
|
offset: Option<i64>,
|
|
limit: Option<i64>,
|
|
db: &State<DB>,
|
|
) -> CachedApiResult<Value> {
|
|
let offset = offset.unwrap_or(0).max(0);
|
|
let limit = limit.unwrap_or(DEBUG_DEFAULT_LIMIT).clamp(1, DEBUG_MAX_LIMIT);
|
|
|
|
let items = crate::db::ido::list_txchain_tracker_map(&db.ido_r, offset, limit)
|
|
.await
|
|
.map_err(db_error)?;
|
|
|
|
Ok(cached_ok(serde_json::to_value(items).unwrap(), CACHE_NONE))
|
|
}
|