ido: record created_at and launched_at timestamps

Add two timestamp columns to the ido table, both sourced from Delphi NFT
commitments (48-bit LE unix seconds):

- created_at (NOT NULL, default 0): from the Delphi NFT in the preinit's
  first output, set at IDO creation. 0 if the commitment is too short.
- launched_at (NULL): from the Delphi NFT in output#3 of the launch
  transaction, set in lockstep with launch_txid. Null until launched.

Both are threaded through the parse/context/update paths and exposed on
IdoRpcRecord.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hossein Zoda 2026-06-11 14:12:12 +00:00
parent d5bdf5a740
commit b8468a1256

View file

@ -708,7 +708,15 @@ pub async fn prepare_tables(pool: &SqlitePool) {
txchain_entrypoint INTEGER NULL, txchain_entrypoint INTEGER NULL,
txchain_head INTEGER NULL, txchain_head INTEGER NULL,
is_valid INTEGER NOT NULL, is_valid INTEGER NOT NULL,
is_token_created_at_preinit INTEGER NOT NULL is_token_created_at_preinit INTEGER NOT NULL,
-- Unix timestamp (seconds) the IDO was created, taken from the
-- Delphi NFT commitment in the preinit's first output. 0 if the
-- commitment was too short to carry a timestamp.
created_at INTEGER NOT NULL DEFAULT 0,
-- Unix timestamp (seconds) the IDO was launched, taken from the
-- Delphi NFT in output#3 of the launch transaction. NULL until the
-- IDO is launched (launch_txid set).
launched_at INTEGER NULL
)", )",
) )
.execute(pool) .execute(pool)
@ -799,6 +807,8 @@ pub struct IdoDBRecord {
txchain_head: Option<i64>, txchain_head: Option<i64>,
is_valid: bool, is_valid: bool,
is_token_created_at_preinit: bool, is_token_created_at_preinit: bool,
created_at: i64,
launched_at: Option<i64>,
} }
impl IdoDBRecord { impl IdoDBRecord {
fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self> { fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self> {
@ -817,6 +827,8 @@ impl IdoDBRecord {
txchain_head: row.get(11), txchain_head: row.get(11),
is_valid: row.get(12), is_valid: row.get(12),
is_token_created_at_preinit: row.get(13), is_token_created_at_preinit: row.get(13),
created_at: row.get(14),
launched_at: row.get(15),
}) })
} }
} }
@ -901,7 +913,7 @@ async fn ido_lookup(
) -> Result<Option<IdoDBRecord>> { ) -> Result<Option<IdoDBRecord>> {
let row = sqlx::query( let row = sqlx::query(
"SELECT internal_id, preinit_txid, init_txid, launch_txid, otoken_genesis_txid, offering_token_id, offered_token_id, "SELECT internal_id, preinit_txid, init_txid, launch_txid, otoken_genesis_txid, offering_token_id, offered_token_id,
status, parameters, state, txchain_entrypoint, txchain_head, is_valid, is_token_created_at_preinit status, parameters, state, txchain_entrypoint, txchain_head, is_valid, is_token_created_at_preinit, created_at, launched_at
FROM ido WHERE internal_id = ?", FROM ido WHERE internal_id = ?",
) )
.bind(internal_id) .bind(internal_id)
@ -1042,12 +1054,17 @@ struct IdoContext {
offered_token_id: Option<Vec<u8>>, offered_token_id: Option<Vec<u8>>,
offering_token_id: Option<Vec<u8>>, offering_token_id: Option<Vec<u8>>,
head_txid: Vec<u8>, head_txid: Vec<u8>,
created_at: i64,
launched_at: Option<i64>,
} }
struct IdoPreinitParseParamsResult { struct IdoPreinitParseParamsResult {
preinit_parameters: IdoPreInitParameters, preinit_parameters: IdoPreInitParameters,
offered_token_is_in_supply: bool, offered_token_is_in_supply: bool,
is_valid_ido: bool, is_valid_ido: bool,
/// Creation time (unix seconds) from the Delphi NFT commitment in the
/// preinit's first output; 0 if the commitment was too short to carry one.
created_at: i64,
} }
fn parse_ido_preinit_tx_params(tx: &Transaction, delphi_token_id: &[u8], platform_fee_nfth: &[u8], errors: &mut Vec<Error>, invalid_ido_reasons: &mut Vec<Error>) -> Option<IdoPreinitParseParamsResult> { fn parse_ido_preinit_tx_params(tx: &Transaction, delphi_token_id: &[u8], platform_fee_nfth: &[u8], errors: &mut Vec<Error>, invalid_ido_reasons: &mut Vec<Error>) -> Option<IdoPreinitParseParamsResult> {
@ -1209,6 +1226,8 @@ fn parse_ido_preinit_tx_params(tx: &Transaction, delphi_token_id: &[u8], platfor
// The first output must be the Delphi NFT. Its category must match the // The first output must be the Delphi NFT. Its category must match the
// announcement's delphiCategory, and the commitment timestamp (the current // announcement's delphiCategory, and the commitment timestamp (the current
// time) must place launchConditions.expiresAt within the allowed offering window. // time) must place launchConditions.expiresAt within the allowed offering window.
// We also record the timestamp as the IDO's creation time.
let mut created_at: i64 = 0;
match tx.output.first() { match tx.output.first() {
Some(delphiOut) => match delphiOut.token.as_ref() { Some(delphiOut) => match delphiOut.token.as_ref() {
Some(delphiToken) if delphiToken.has_nft() => { Some(delphiToken) if delphiToken.has_nft() => {
@ -1224,6 +1243,7 @@ fn parse_ido_preinit_tx_params(tx: &Transaction, delphi_token_id: &[u8], platfor
let c = &delphiToken.commitment; let c = &delphiToken.commitment;
// 48-bit little-endian timestamp (see riftenlabs_defi::delphi::parse_delphi_update). // 48-bit little-endian timestamp (see riftenlabs_defi::delphi::parse_delphi_update).
let delphi_timestamp = u64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], 0, 0]); let delphi_timestamp = u64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], 0, 0]);
created_at = delphi_timestamp as i64;
let offering_duration = &preinit_parameters.offering.launchConditions.expiresAt - BigInt::from(delphi_timestamp); let offering_duration = &preinit_parameters.offering.launchConditions.expiresAt - BigInt::from(delphi_timestamp);
if offering_duration < *MIN_OFFERING_DURATION { if offering_duration < *MIN_OFFERING_DURATION {
invalid_ido_reasons.push(anyhow::anyhow!("expiresAt - delphi timestamp < MIN_OFFERING_DURATION (1 day)")); invalid_ido_reasons.push(anyhow::anyhow!("expiresAt - delphi timestamp < MIN_OFFERING_DURATION (1 day)"));
@ -1250,6 +1270,7 @@ fn parse_ido_preinit_tx_params(tx: &Transaction, delphi_token_id: &[u8], platfor
preinit_parameters, preinit_parameters,
offered_token_is_in_supply, offered_token_is_in_supply,
is_valid_ido, is_valid_ido,
created_at,
}); });
} }
_ => { _ => {
@ -1269,6 +1290,7 @@ struct IdoPreinitParseResult {
is_valid_ido: bool, is_valid_ido: bool,
offered_token_is_in_supply: bool, offered_token_is_in_supply: bool,
offered_token_id: Option<Vec<u8>>, offered_token_id: Option<Vec<u8>>,
created_at: i64,
} }
fn parse_ido_preinit_tx(network: Option<Network>, tx: &Transaction, errors: &mut Vec<Error>, invalid_ido_reasons: &mut Vec<Error>) -> IdoPreinitParseResult { fn parse_ido_preinit_tx(network: Option<Network>, tx: &Transaction, errors: &mut Vec<Error>, invalid_ido_reasons: &mut Vec<Error>) -> IdoPreinitParseResult {
@ -1285,17 +1307,20 @@ fn parse_ido_preinit_tx(network: Option<Network>, tx: &Transaction, errors: &mut
let offered_token_is_in_supply: bool; let offered_token_is_in_supply: bool;
let mut is_valid_ido: bool; let mut is_valid_ido: bool;
let nullable_preinit_parameters: Option<IdoPreInitParameters>; let nullable_preinit_parameters: Option<IdoPreInitParameters>;
let created_at: i64;
// preinit announcement // preinit announcement
match parse_ido_preinit_tx_params(tx, &delphi_token_id, &platform_fee_nfth, errors, invalid_ido_reasons) { match parse_ido_preinit_tx_params(tx, &delphi_token_id, &platform_fee_nfth, errors, invalid_ido_reasons) {
Some(output) => { Some(output) => {
offered_token_is_in_supply = output.offered_token_is_in_supply; offered_token_is_in_supply = output.offered_token_is_in_supply;
is_valid_ido = output.is_valid_ido; is_valid_ido = output.is_valid_ido;
nullable_preinit_parameters = Some(output.preinit_parameters); nullable_preinit_parameters = Some(output.preinit_parameters);
created_at = output.created_at;
} }
None => { None => {
offered_token_is_in_supply = false; offered_token_is_in_supply = false;
is_valid_ido = false; is_valid_ido = false;
nullable_preinit_parameters = None; nullable_preinit_parameters = None;
created_at = 0;
} }
} }
@ -1526,6 +1551,7 @@ fn parse_ido_preinit_tx(network: Option<Network>, tx: &Transaction, errors: &mut
is_valid_ido, is_valid_ido,
offered_token_is_in_supply, offered_token_is_in_supply,
offered_token_id, offered_token_id,
created_at,
} }
} else { } else {
IdoPreinitParseResult { IdoPreinitParseResult {
@ -1534,6 +1560,7 @@ fn parse_ido_preinit_tx(network: Option<Network>, tx: &Transaction, errors: &mut
is_valid_ido, is_valid_ido,
offered_token_is_in_supply: false, offered_token_is_in_supply: false,
offered_token_id: None, offered_token_id: None,
created_at,
} }
} }
} }
@ -1558,6 +1585,8 @@ fn create_ido_context_from_preinit(network: Option<Network>, tx: &Transaction, e
offering_token_id: None, offering_token_id: None,
is_token_created_at_preinit: !result.offered_token_is_in_supply, is_token_created_at_preinit: !result.offered_token_is_in_supply,
head_txid: tx.compute_txid().to_blob(), head_txid: tx.compute_txid().to_blob(),
created_at: result.created_at,
launched_at: None,
}) })
} }
} }
@ -1589,8 +1618,8 @@ async fn on_create_ido(
// create the ido // create the ido
let mut dbtx = pool.begin().await?; let mut dbtx = pool.begin().await?;
let result = sqlx::query( let result = sqlx::query(
"INSERT INTO ido (preinit_txid, init_txid, launch_txid, otoken_genesis_txid, offering_token_id, offered_token_id, status, parameters, state, is_valid, is_token_created_at_preinit, txchain_entrypoint, txchain_head) "INSERT INTO ido (preinit_txid, init_txid, launch_txid, otoken_genesis_txid, offering_token_id, offered_token_id, status, parameters, state, is_valid, is_token_created_at_preinit, created_at, launched_at, txchain_entrypoint, txchain_head)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
) )
.bind(context.preinit_txid) .bind(context.preinit_txid)
.bind(context.init_txid) .bind(context.init_txid)
@ -1603,6 +1632,8 @@ async fn on_create_ido(
.bind(serde_json::to_vec(&context.state).unwrap()) .bind(serde_json::to_vec(&context.state).unwrap())
.bind(context.is_valid_ido) .bind(context.is_valid_ido)
.bind(context.is_token_created_at_preinit) .bind(context.is_token_created_at_preinit)
.bind(context.created_at)
.bind(context.launched_at)
.bind(None::<i64>) .bind(None::<i64>)
.bind(None::<i64>) .bind(None::<i64>)
.execute(&mut *dbtx) .execute(&mut *dbtx)
@ -1655,7 +1686,10 @@ struct IdoUpdateEntry {
#[derive(Clone)] #[derive(Clone)]
enum IdoUpdate { enum IdoUpdate {
InitTxId(Vec<u8>), InitTxId(Vec<u8>),
LaunchTxId(Vec<u8>), LaunchTxId {
txid: Vec<u8>,
launched_at: Option<i64>,
},
OTokenGenesisTxId(Vec<u8>), OTokenGenesisTxId(Vec<u8>),
Status(String), Status(String),
State(IdoState), State(IdoState),
@ -1677,6 +1711,26 @@ fn rev_blob(v: &[u8]) -> Vec<u8> {
v.iter().copied().rev().collect() v.iter().copied().rev().collect()
} }
/// Read the 48-bit little-endian unix timestamp (seconds) carried in the first
/// 6 bytes of a Delphi NFT commitment. Returns None if the commitment is too
/// short to contain one. (see riftenlabs_defi::delphi::parse_delphi_update)
fn delphi_commitment_timestamp(commitment: &[u8]) -> Option<i64> {
if commitment.len() < 6 {
return None;
}
let c = commitment;
Some(u64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], 0, 0]) as i64)
}
/// Read `launched_at` (unix seconds) from the Delphi NFT in output#3 of a launch
/// transaction. Returns None if the output, its token, or its timestamp is absent.
fn launch_tx_launched_at(tx: &Transaction) -> Option<i64> {
tx.output
.get(3)
.and_then(|o| o.token.as_ref())
.and_then(|t| delphi_commitment_timestamp(&t.commitment))
}
fn print_updates(updates: &[IdoUpdate]) -> () { fn print_updates(updates: &[IdoUpdate]) -> () {
for update in updates { for update in updates {
match update { match update {
@ -1684,7 +1738,7 @@ fn print_updates(updates: &[IdoUpdate]) -> () {
let v=rev_blob(txid); let v=rev_blob(txid);
debug!("IDO IdoUpdate::InitTxId(txid): {}", hex::encode(v)); debug!("IDO IdoUpdate::InitTxId(txid): {}", hex::encode(v));
}, },
IdoUpdate::LaunchTxId(txid) => { IdoUpdate::LaunchTxId { txid, .. } => {
let v=rev_blob(txid); let v=rev_blob(txid);
debug!("IDO IdoUpdate::LaunchTxId(txid): {}", hex::encode(v)); debug!("IDO IdoUpdate::LaunchTxId(txid): {}", hex::encode(v));
}, },
@ -1882,7 +1936,10 @@ fn ido_add_tx(
return Err(anyhow::anyhow!("Incorrect commitment size at output#0")); return Err(anyhow::anyhow!("Incorrect commitment size at output#0"));
} }
updates.push(IdoUpdate::Status("DISTRIBUTING".to_string())); updates.push(IdoUpdate::Status("DISTRIBUTING".to_string()));
updates.push(IdoUpdate::LaunchTxId(tx.compute_txid().to_blob())); updates.push(IdoUpdate::LaunchTxId {
txid: tx.compute_txid().to_blob(),
launched_at: launch_tx_launched_at(tx),
});
updates.push(IdoUpdate::State( updates.push(IdoUpdate::State(
IdoState::Distributing(IdoDistributingState { IdoState::Distributing(IdoDistributingState {
authguardCategory: active_state.authguardCategory.clone(), authguardCategory: active_state.authguardCategory.clone(),
@ -1904,7 +1961,10 @@ fn ido_add_tx(
return Err(anyhow::anyhow!("Incorrect commitment size at output#0")); return Err(anyhow::anyhow!("Incorrect commitment size at output#0"));
} }
updates.push(IdoUpdate::Status("POSTLAUNCH".to_string())); updates.push(IdoUpdate::Status("POSTLAUNCH".to_string()));
updates.push(IdoUpdate::LaunchTxId(tx.compute_txid().to_blob())); updates.push(IdoUpdate::LaunchTxId {
txid: tx.compute_txid().to_blob(),
launched_at: launch_tx_launched_at(tx),
});
updates.push(IdoUpdate::State( updates.push(IdoUpdate::State(
IdoState::PostLaunch(IdoPostLaunchState { IdoState::PostLaunch(IdoPostLaunchState {
authguardCategory: active_state.authguardCategory.clone(), authguardCategory: active_state.authguardCategory.clone(),
@ -2057,7 +2117,7 @@ async fn update_ido(
) -> Result<()> { ) -> Result<()> {
sqlx::query( sqlx::query(
"UPDATE ido set init_txid = ?, launch_txid = ?, otoken_genesis_txid = ?, status = ?, parameters = ?, state = ?, "UPDATE ido set init_txid = ?, launch_txid = ?, otoken_genesis_txid = ?, status = ?, parameters = ?, state = ?,
offering_token_id = ?, offered_token_id = ?, txchain_head = ?, is_valid = ? WHERE internal_id = ?", offering_token_id = ?, offered_token_id = ?, txchain_head = ?, is_valid = ?, launched_at = ? WHERE internal_id = ?",
) )
.bind(&context.init_txid) .bind(&context.init_txid)
.bind(&context.launch_txid) .bind(&context.launch_txid)
@ -2069,6 +2129,7 @@ async fn update_ido(
.bind(&context.offered_token_id) .bind(&context.offered_token_id)
.bind(txchain_head) .bind(txchain_head)
.bind(context.is_valid_ido) .bind(context.is_valid_ido)
.bind(context.launched_at)
.bind(internal_id) .bind(internal_id)
.execute(&mut **dbtx) .execute(&mut **dbtx)
.await?; .await?;
@ -2084,8 +2145,9 @@ fn apply_updates_to_context(
IdoUpdate::InitTxId(txid) => { IdoUpdate::InitTxId(txid) => {
context.init_txid = Some(txid.clone()); context.init_txid = Some(txid.clone());
}, },
IdoUpdate::LaunchTxId(txid) => { IdoUpdate::LaunchTxId { txid, launched_at } => {
context.launch_txid = Some(txid.clone()); context.launch_txid = Some(txid.clone());
context.launched_at = *launched_at;
}, },
IdoUpdate::OTokenGenesisTxId(txid) => { IdoUpdate::OTokenGenesisTxId(txid) => {
context.otoken_genesis_txid = Some(txid.clone()); context.otoken_genesis_txid = Some(txid.clone());
@ -2306,6 +2368,8 @@ async fn on_add_ido_tx(
offered_token_id: ido.offered_token_id.clone(), offered_token_id: ido.offered_token_id.clone(),
offering_token_id: ido.offering_token_id.clone(), offering_token_id: ido.offering_token_id.clone(),
head_txid: prev_txchain_item.txid.clone(), head_txid: prev_txchain_item.txid.clone(),
created_at: ido.created_at,
launched_at: ido.launched_at,
}; };
// add to the chain // add to the chain
let result = ido_add_tx(&context, tx)?; let result = ido_add_tx(&context, tx)?;
@ -2449,6 +2513,12 @@ pub struct IdoRpcRecord {
/// Txid (display hex) of the txchain head record, or null if there is no head. /// Txid (display hex) of the txchain head record, or null if there is no head.
pub txchain_head: Option<String>, pub txchain_head: Option<String>,
pub is_valid: bool, pub is_valid: bool,
/// Unix timestamp (seconds) the IDO was created, from the preinit's Delphi
/// NFT commitment; 0 if unavailable.
pub created_at: i64,
/// Unix timestamp (seconds) the IDO was launched, from the Delphi NFT in
/// output#3 of the launch transaction; null until launched.
pub launched_at: Option<i64>,
} }
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
@ -2500,6 +2570,8 @@ impl IdoDBRecord {
state: serde_json::from_slice(&self.state)?, state: serde_json::from_slice(&self.state)?,
txchain_head: txchain_head_txid.as_deref().map(blob_to_display_hex::<Txid>).transpose()?, txchain_head: txchain_head_txid.as_deref().map(blob_to_display_hex::<Txid>).transpose()?,
is_valid: self.is_valid, is_valid: self.is_valid,
created_at: self.created_at,
launched_at: self.launched_at,
}) })
} }
} }
@ -2591,7 +2663,7 @@ pub async fn list_idos(
let mut qb: sqlx::QueryBuilder<sqlx::Sqlite> = sqlx::QueryBuilder::new( let mut qb: sqlx::QueryBuilder<sqlx::Sqlite> = sqlx::QueryBuilder::new(
"SELECT ido.internal_id, ido.preinit_txid, ido.init_txid, ido.launch_txid, ido.otoken_genesis_txid, \ "SELECT ido.internal_id, ido.preinit_txid, ido.init_txid, ido.launch_txid, ido.otoken_genesis_txid, \
ido.offering_token_id, ido.offered_token_id, \ ido.offering_token_id, ido.offered_token_id, \
ido.status, ido.parameters, ido.state, ido.txchain_entrypoint, ido.txchain_head, ido.is_valid, ido.is_token_created_at_preinit, \ ido.status, ido.parameters, ido.state, ido.txchain_entrypoint, ido.txchain_head, ido.is_valid, ido.is_token_created_at_preinit, ido.created_at, ido.launched_at, \
head_tx.txid \ head_tx.txid \
FROM ido LEFT JOIN ido_txchain head_tx ON head_tx.id = ido.txchain_head WHERE 1=1", FROM ido LEFT JOIN ido_txchain head_tx ON head_tx.id = ido.txchain_head WHERE 1=1",
); );
@ -2617,7 +2689,7 @@ pub async fn list_idos(
.await? .await?
.into_iter() .into_iter()
.map(|row| { .map(|row| {
let txchain_head_txid: Option<Vec<u8>> = row.get(14); let txchain_head_txid: Option<Vec<u8>> = row.get(16);
IdoDBRecord::from_row(&row)?.into_rpc_record(txchain_head_txid) IdoDBRecord::from_row(&row)?.into_rpc_record(txchain_head_txid)
}) })
.collect() .collect()
@ -2629,7 +2701,7 @@ pub async fn get_ido_by_preinit_txid(
) -> Result<Option<IdoRpcRecord>> { ) -> Result<Option<IdoRpcRecord>> {
let row = sqlx::query( let row = sqlx::query(
"SELECT ido.internal_id, ido.preinit_txid, ido.init_txid, ido.launch_txid, ido.otoken_genesis_txid, ido.offering_token_id, ido.offered_token_id, \ "SELECT ido.internal_id, ido.preinit_txid, ido.init_txid, ido.launch_txid, ido.otoken_genesis_txid, ido.offering_token_id, ido.offered_token_id, \
ido.status, ido.parameters, ido.state, ido.txchain_entrypoint, ido.txchain_head, ido.is_valid, ido.is_token_created_at_preinit, \ ido.status, ido.parameters, ido.state, ido.txchain_entrypoint, ido.txchain_head, ido.is_valid, ido.is_token_created_at_preinit, ido.created_at, ido.launched_at, \
head_tx.txid \ head_tx.txid \
FROM ido LEFT JOIN ido_txchain head_tx ON head_tx.id = ido.txchain_head WHERE ido.preinit_txid = ?", FROM ido LEFT JOIN ido_txchain head_tx ON head_tx.id = ido.txchain_head WHERE ido.preinit_txid = ?",
) )
@ -2637,7 +2709,7 @@ pub async fn get_ido_by_preinit_txid(
.fetch_optional(pool) .fetch_optional(pool)
.await?; .await?;
row.map(|r| { row.map(|r| {
let txchain_head_txid: Option<Vec<u8>> = r.get(14); let txchain_head_txid: Option<Vec<u8>> = r.get(16);
IdoDBRecord::from_row(&r)?.into_rpc_record(txchain_head_txid) IdoDBRecord::from_row(&r)?.into_rpc_record(txchain_head_txid)
}) })
.transpose() .transpose()
@ -2649,7 +2721,7 @@ pub async fn get_ido_by_offering_token_id(
) -> Result<Option<IdoRpcRecord>> { ) -> Result<Option<IdoRpcRecord>> {
let row = sqlx::query( let row = sqlx::query(
"SELECT ido.internal_id, ido.preinit_txid, ido.init_txid, ido.launch_txid, ido.otoken_genesis_txid, ido.offering_token_id, ido.offered_token_id, \ "SELECT ido.internal_id, ido.preinit_txid, ido.init_txid, ido.launch_txid, ido.otoken_genesis_txid, ido.offering_token_id, ido.offered_token_id, \
ido.status, ido.parameters, ido.state, ido.txchain_entrypoint, ido.txchain_head, ido.is_valid, ido.is_token_created_at_preinit, \ ido.status, ido.parameters, ido.state, ido.txchain_entrypoint, ido.txchain_head, ido.is_valid, ido.is_token_created_at_preinit, ido.created_at, ido.launched_at, \
head_tx.txid \ head_tx.txid \
FROM ido LEFT JOIN ido_txchain head_tx ON head_tx.id = ido.txchain_head WHERE ido.offering_token_id = ?", FROM ido LEFT JOIN ido_txchain head_tx ON head_tx.id = ido.txchain_head WHERE ido.offering_token_id = ?",
) )
@ -2657,7 +2729,7 @@ pub async fn get_ido_by_offering_token_id(
.fetch_optional(pool) .fetch_optional(pool)
.await?; .await?;
row.map(|r| { row.map(|r| {
let txchain_head_txid: Option<Vec<u8>> = r.get(14); let txchain_head_txid: Option<Vec<u8>> = r.get(16);
IdoDBRecord::from_row(&r)?.into_rpc_record(txchain_head_txid) IdoDBRecord::from_row(&r)?.into_rpc_record(txchain_head_txid)
}) })
.transpose() .transpose()
@ -3157,7 +3229,7 @@ mod querytests {
let preinit_hex = hex::encode(txid(1)); let preinit_hex = hex::encode(txid(1));
insert_test_entry(&pool, ido_id, txid(10), false).await; insert_test_entry(&pool, ido_id, txid(10), false).await;
insert_test_entry(&pool, ido_id, txid(11), true).await; insert_test_entry(&pool, ido_id, txid(11), true).await;
let all = list_ido_entries(&pool, ido_id, &preinit_hex, None, 0, 100).await.unwrap(); let all = list_ido_entries(&pool, ido_id, &preinit_hex, None, &[], 0, 100).await.unwrap();
assert_eq!(all.len(), 2); assert_eq!(all.len(), 2);
} }
@ -3169,10 +3241,10 @@ mod querytests {
insert_test_entry(&pool, ido_id, txid(10), false).await; insert_test_entry(&pool, ido_id, txid(10), false).await;
insert_test_entry(&pool, ido_id, txid(11), true).await; insert_test_entry(&pool, ido_id, txid(11), true).await;
insert_test_entry(&pool, ido_id, txid(12), true).await; insert_test_entry(&pool, ido_id, txid(12), true).await;
let dist = list_ido_entries(&pool, ido_id, &preinit_hex, Some(true), 0, 100).await.unwrap(); let dist = list_ido_entries(&pool, ido_id, &preinit_hex, Some(true), &[], 0, 100).await.unwrap();
assert_eq!(dist.len(), 2); assert_eq!(dist.len(), 2);
assert!(dist.iter().all(|e| e.distributed)); assert!(dist.iter().all(|e| e.distributed));
let undist = list_ido_entries(&pool, ido_id, &preinit_hex, Some(false), 0, 100).await.unwrap(); let undist = list_ido_entries(&pool, ido_id, &preinit_hex, Some(false), &[], 0, 100).await.unwrap();
assert_eq!(undist.len(), 1); assert_eq!(undist.len(), 1);
assert!(!undist[0].distributed); assert!(!undist[0].distributed);
} }
@ -3187,8 +3259,8 @@ mod querytests {
insert_test_entry(&pool, ido1, txid(10), false).await; insert_test_entry(&pool, ido1, txid(10), false).await;
insert_test_entry(&pool, ido1, txid(11), false).await; insert_test_entry(&pool, ido1, txid(11), false).await;
insert_test_entry(&pool, ido2, txid(12), false).await; insert_test_entry(&pool, ido2, txid(12), false).await;
let e1 = list_ido_entries(&pool, ido1, &preinit_hex_1, None, 0, 100).await.unwrap(); let e1 = list_ido_entries(&pool, ido1, &preinit_hex_1, None, &[], 0, 100).await.unwrap();
let e2 = list_ido_entries(&pool, ido2, &preinit_hex_2, None, 0, 100).await.unwrap(); let e2 = list_ido_entries(&pool, ido2, &preinit_hex_2, None, &[], 0, 100).await.unwrap();
assert_eq!(e1.len(), 2); assert_eq!(e1.len(), 2);
assert_eq!(e2.len(), 1); assert_eq!(e2.len(), 1);
} }