ido: filter list_ido_entries by owner_nfthash
Add an optional owner_nfthash query param to GET /<id>/entries. Accepts up to 20 comma-delimited 32-byte hex hashes; an entry matches any listed value via an owner_nfthash IN (...) clause. Rejects malformed hex, wrong-length values, and >20 filters with a 400. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
aec0a6f0f8
commit
d5bdf5a740
2 changed files with 64 additions and 2 deletions
|
|
@ -2668,6 +2668,7 @@ pub async fn list_ido_entries(
|
||||||
internal_id: i64,
|
internal_id: i64,
|
||||||
preinit_txid_hex: &str,
|
preinit_txid_hex: &str,
|
||||||
distributed: Option<bool>,
|
distributed: Option<bool>,
|
||||||
|
owner_nfthashes: &[Vec<u8>],
|
||||||
offset: i64,
|
offset: i64,
|
||||||
limit: i64,
|
limit: i64,
|
||||||
) -> Result<Vec<IdoEntryRpcRecord>> {
|
) -> Result<Vec<IdoEntryRpcRecord>> {
|
||||||
|
|
@ -2680,6 +2681,14 @@ pub async fn list_ido_entries(
|
||||||
qb.push(" AND distributed = ");
|
qb.push(" AND distributed = ");
|
||||||
qb.push_bind(v as i64);
|
qb.push_bind(v as i64);
|
||||||
}
|
}
|
||||||
|
if !owner_nfthashes.is_empty() {
|
||||||
|
qb.push(" AND owner_nfthash IN (");
|
||||||
|
let mut separated = qb.separated(", ");
|
||||||
|
for hash in owner_nfthashes {
|
||||||
|
separated.push_bind(hash.clone());
|
||||||
|
}
|
||||||
|
separated.push_unseparated(")");
|
||||||
|
}
|
||||||
qb.push(" ORDER BY rowid ASC LIMIT ");
|
qb.push(" ORDER BY rowid ASC LIMIT ");
|
||||||
qb.push_bind(limit);
|
qb.push_bind(limit);
|
||||||
qb.push(" OFFSET ");
|
qb.push(" OFFSET ");
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,12 @@ use serde_json::Value;
|
||||||
const LIST_DEFAULT_LIMIT: i64 = 20;
|
const LIST_DEFAULT_LIMIT: i64 = 20;
|
||||||
const LIST_MAX_LIMIT: i64 = 100;
|
const LIST_MAX_LIMIT: i64 = 100;
|
||||||
|
|
||||||
|
/// Maximum number of comma-delimited owner_nfthash filters accepted per request.
|
||||||
|
const MAX_OWNER_NFTHASH_FILTERS: usize = 20;
|
||||||
|
|
||||||
|
/// Length in bytes of an owner_nfthash.
|
||||||
|
const OWNER_NFTHASH_LEN: usize = 32;
|
||||||
|
|
||||||
const DEBUG_DEFAULT_LIMIT: i64 = 100;
|
const DEBUG_DEFAULT_LIMIT: i64 = 100;
|
||||||
const DEBUG_MAX_LIMIT: i64 = 10_000;
|
const DEBUG_MAX_LIMIT: i64 = 10_000;
|
||||||
|
|
||||||
|
|
@ -96,16 +102,61 @@ pub async fn get_ido_by_offering_token(
|
||||||
Ok(cached_ok(serde_json::to_value(item).unwrap(), CACHE_NONE))
|
Ok(cached_ok(serde_json::to_value(item).unwrap(), CACHE_NONE))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse the comma-delimited `owner_nfthash` query parameter into raw byte
|
||||||
|
/// filters. Returns an empty vec when the parameter is absent. Rejects more
|
||||||
|
/// than [`MAX_OWNER_NFTHASH_FILTERS`] values, blank entries, and invalid hex.
|
||||||
|
fn parse_owner_nfthash_filters(
|
||||||
|
owner_nfthash: Option<&str>,
|
||||||
|
) -> Result<Vec<Vec<u8>>, rocket::response::status::Custom<rocket::serde::json::Json<Value>>> {
|
||||||
|
let Some(raw) = owner_nfthash else {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
};
|
||||||
|
|
||||||
|
let parts: Vec<&str> = raw.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect();
|
||||||
|
if parts.len() > MAX_OWNER_NFTHASH_FILTERS {
|
||||||
|
return Err(bad_request(
|
||||||
|
ApiErrorCode::InvalidParameters,
|
||||||
|
&format!("Too many owner_nfthash filters (max {MAX_OWNER_NFTHASH_FILTERS})"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
parts
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| {
|
||||||
|
let bytes = hex::decode(s).map_err(|e| {
|
||||||
|
bad_request(
|
||||||
|
ApiErrorCode::InvalidParameters,
|
||||||
|
&format!("Invalid owner_nfthash '{s}': {e}"),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
if bytes.len() != OWNER_NFTHASH_LEN {
|
||||||
|
return Err(bad_request(
|
||||||
|
ApiErrorCode::InvalidParameters,
|
||||||
|
&format!(
|
||||||
|
"Invalid owner_nfthash '{s}': expected {OWNER_NFTHASH_LEN} bytes, got {}",
|
||||||
|
bytes.len()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(bytes)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// List entries for an IDO.
|
/// List entries for an IDO.
|
||||||
///
|
///
|
||||||
/// - `id`: the IDO's public id (preinit txid, 64-char hex)
|
/// - `id`: the IDO's public id (preinit txid, 64-char hex)
|
||||||
/// - `distributed`: optional boolean filter
|
/// - `distributed`: optional boolean filter
|
||||||
|
/// - `owner_nfthash`: optional filter by owner nfthash (hex). Multiple hashes
|
||||||
|
/// may be supplied comma-delimited; up to 20 per request. An entry matches
|
||||||
|
/// if its owner_nfthash equals any of the supplied values.
|
||||||
///
|
///
|
||||||
/// Pagination: `offset` (default 0), `limit` (default 20, max 100)
|
/// Pagination: `offset` (default 0), `limit` (default 20, max 100)
|
||||||
#[get("/<id>/entries?<distributed>&<offset>&<limit>")]
|
#[get("/<id>/entries?<distributed>&<owner_nfthash>&<offset>&<limit>")]
|
||||||
pub async fn list_ido_entries(
|
pub async fn list_ido_entries(
|
||||||
id: &str,
|
id: &str,
|
||||||
distributed: Option<bool>,
|
distributed: Option<bool>,
|
||||||
|
owner_nfthash: Option<&str>,
|
||||||
offset: Option<i64>,
|
offset: Option<i64>,
|
||||||
limit: Option<i64>,
|
limit: Option<i64>,
|
||||||
db: &State<DB>,
|
db: &State<DB>,
|
||||||
|
|
@ -113,9 +164,11 @@ pub async fn list_ido_entries(
|
||||||
let offset = offset.unwrap_or(0).max(0);
|
let offset = offset.unwrap_or(0).max(0);
|
||||||
let limit = limit.unwrap_or(LIST_DEFAULT_LIMIT).clamp(1, LIST_MAX_LIMIT);
|
let limit = limit.unwrap_or(LIST_DEFAULT_LIMIT).clamp(1, LIST_MAX_LIMIT);
|
||||||
|
|
||||||
|
let owner_nfthashes = parse_owner_nfthash_filters(owner_nfthash)?;
|
||||||
|
|
||||||
let (internal_id, preinit_txid_hex) = resolve_ido(id, &db.ido_r).await?;
|
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)
|
let items = crate::db::ido::list_ido_entries(&db.ido_r, internal_id, &preinit_txid_hex, distributed, &owner_nfthashes, offset, limit)
|
||||||
.await
|
.await
|
||||||
.map_err(db_error)?;
|
.map_err(db_error)?;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue