Clean
This commit is contained in:
parent
f603baf2ef
commit
08310c62c0
2 changed files with 53 additions and 22 deletions
|
|
@ -169,12 +169,14 @@ pub async fn create_tables(pool: &SqlitePool) {
|
|||
|
||||
// Unspent entry NFT UTXOs created by participants.
|
||||
// Populated once offering_category is known (after createIDO fires).
|
||||
// token_amount = purchasedAmount (the offered tokens the participant is buying).
|
||||
sqlx::query(
|
||||
"CREATE TABLE IF NOT EXISTS ido_entry (
|
||||
utxo_txid BLOB NOT NULL,
|
||||
utxo_n INTEGER NOT NULL,
|
||||
offering_category BLOB NOT NULL,
|
||||
sats BIGINT NOT NULL,
|
||||
token_amount BIGINT NOT NULL DEFAULT 0,
|
||||
commitment BLOB NOT NULL,
|
||||
spent INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (utxo_txid, utxo_n)
|
||||
|
|
@ -259,11 +261,18 @@ pub(crate) async fn insert_ido(
|
|||
|
||||
/// Advance the pre-init UTXO chain for any IDO whose tracked UTXO is spent by this tx.
|
||||
///
|
||||
/// In each idoPreInitStep, the pre-init contract UTXO moves from output[1] of the previous tx
|
||||
/// to output[1] of the current tx, carrying the offered token. When createIDO fires, the
|
||||
/// pre-init UTXO is consumed without a continuation at output[1] (no offered token there),
|
||||
/// and the offering initiator appears at output[0]. At that point, this tx's txid becomes
|
||||
/// the offering_category (the category used by entry NFTs).
|
||||
/// In each idoPreInitStep the output layout is:
|
||||
/// output[0]: storage or OP_RETURN (no token)
|
||||
/// output[1]: pre-init contract UTXO (BCH only, **never** has a token)
|
||||
/// output[2]: offered token storage (always carries the offered FT)
|
||||
///
|
||||
/// In the final step (not isPreInitStep / createIDO), the offering initiator is placed at
|
||||
/// output[0] and the offered tokens move to output[5] and output[7]; output[2] is now a
|
||||
/// bytecode storage with **no** token. Checking output[2] for the offered token therefore
|
||||
/// correctly distinguishes the final step from all preceding pre-init steps.
|
||||
///
|
||||
/// When the final step fires, this tx's txid becomes offering_category (the token category
|
||||
/// used by participant entry NFTs).
|
||||
async fn advance_preinit_chain(conn: &mut SqliteConnection, tx: &Transaction) -> Result<()> {
|
||||
let txid = tx.txid();
|
||||
|
||||
|
|
@ -294,10 +303,12 @@ async fn advance_preinit_chain(conn: &mut SqliteConnection, tx: &Transaction) ->
|
|||
.execute(&mut *conn)
|
||||
.await?;
|
||||
|
||||
// If output[1] carries the offered token → this is another pre-init step
|
||||
// output[1] is the pre-init contract (BCH only, never has a token).
|
||||
// output[2] is the offered token storage — present in every pre-init step,
|
||||
// absent (replaced by bytecode storage) in the final createIDO step.
|
||||
let is_preinit_step = tx
|
||||
.output
|
||||
.get(1)
|
||||
.get(2)
|
||||
.and_then(|o| o.token.as_ref())
|
||||
.map(|t| t.id.to_blob() == offered_token_cat_blob)
|
||||
.unwrap_or(false);
|
||||
|
|
@ -370,10 +381,14 @@ async fn track_entry_nfts(conn: &mut SqliteConnection, tx: &Transaction) -> Resu
|
|||
.await?;
|
||||
}
|
||||
|
||||
// Create new entries: NFT outputs whose token category is a known offering_category
|
||||
// Create new entries: mutable NFT outputs whose token category is a known offering_category.
|
||||
// Entry NFTs are mutable (capability 0x01, per `offering.cash` line 93: tokenCategory ==
|
||||
// offeringCategory + 0x01). The launcher is immutable (capability 0x00), so the mutable
|
||||
// check cleanly excludes it and other non-entry NFTs with the same category.
|
||||
for (n, output) in tx.output.iter().enumerate() {
|
||||
let Some(token) = &output.token else { continue };
|
||||
if !token.has_nft() {
|
||||
// has_nft() checks bitfield & 0x20; capability() = bitfield & 0x0f
|
||||
if !token.has_nft() || token.capability() != 0x01 {
|
||||
continue;
|
||||
}
|
||||
let cat_blob = token.id.to_blob();
|
||||
|
|
@ -382,13 +397,14 @@ async fn track_entry_nfts(conn: &mut SqliteConnection, tx: &Transaction) -> Resu
|
|||
}
|
||||
sqlx::query(
|
||||
"INSERT OR IGNORE INTO ido_entry
|
||||
(utxo_txid, utxo_n, offering_category, sats, commitment)
|
||||
VALUES (?, ?, ?, ?, ?)",
|
||||
(utxo_txid, utxo_n, offering_category, sats, token_amount, commitment)
|
||||
VALUES (?, ?, ?, ?, ?, ?)",
|
||||
)
|
||||
.bind(txid.to_blob())
|
||||
.bind(n as i64)
|
||||
.bind(&cat_blob)
|
||||
.bind(output.value as i64)
|
||||
.bind(token.amount)
|
||||
.bind(&token.commitment)
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
|
|
@ -486,13 +502,13 @@ fn rows_to_ido_list(
|
|||
}
|
||||
|
||||
/// Returns unspent entry NFT UTXOs for a specific IDO (identified by creation txid).
|
||||
/// (utxo_txid_hex, utxo_n, sats, commitment_hex)
|
||||
/// (utxo_txid_hex, utxo_n, sats, token_amount, commitment_hex)
|
||||
pub async fn get_ido_entries(
|
||||
pool: &SqlitePool,
|
||||
creation_txid: &Txid,
|
||||
) -> Result<Vec<(String, u32, i64, String)>> {
|
||||
) -> Result<Vec<(String, u32, i64, i64, String)>> {
|
||||
let rows = sqlx::query(
|
||||
"SELECT e.utxo_txid, e.utxo_n, e.sats, e.commitment
|
||||
"SELECT e.utxo_txid, e.utxo_n, e.sats, e.token_amount, e.commitment
|
||||
FROM ido_entry e
|
||||
JOIN ido i ON i.offering_category = e.offering_category
|
||||
WHERE i.creation_txid = ? AND e.spent = 0",
|
||||
|
|
@ -506,11 +522,13 @@ pub async fn get_ido_entries(
|
|||
let txid_blob: Vec<u8> = row.get(0);
|
||||
let n: i64 = row.get(1);
|
||||
let sats: i64 = row.get(2);
|
||||
let commitment: Vec<u8> = row.get(3);
|
||||
let token_amount: i64 = row.get(3);
|
||||
let commitment: Vec<u8> = row.get(4);
|
||||
result.push((
|
||||
blob_to_display_hex::<Txid>(&txid_blob)?,
|
||||
n as u32,
|
||||
sats,
|
||||
token_amount,
|
||||
hex::encode(commitment),
|
||||
));
|
||||
}
|
||||
|
|
@ -625,7 +643,11 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// Build a pre-init step tx: spends prev_txid:1, output[1] carries offered token.
|
||||
/// Build a pre-init step tx matching the real contract layout:
|
||||
/// input: spends prev_txid:1 (the pre-init contract UTXO)
|
||||
/// output[0]: storage / OP_RETURN (no token)
|
||||
/// output[1]: new pre-init contract UTXO (BCH only, NO token)
|
||||
/// output[2]: offered token storage (HAS offered token) ← the key detection signal
|
||||
fn build_preinit_step_tx(prev_txid: Txid, offered_category: TokenID) -> Transaction {
|
||||
use bitcoincash::{OutPoint, TxIn};
|
||||
Transaction {
|
||||
|
|
@ -638,8 +660,11 @@ mod tests {
|
|||
witness: bitcoincash::Witness::default(),
|
||||
}],
|
||||
output: vec![
|
||||
// output[0]: storage (no token)
|
||||
TxOut { value: 0, script_pubkey: Script::new(), token: None },
|
||||
// output[1]: pre-init contract continues with offered token
|
||||
// output[1]: new pre-init contract UTXO — BCH only, never carries a token
|
||||
TxOut { value: 1000, script_pubkey: Script::new(), token: None },
|
||||
// output[2]: offered token storage (detection signal for is_preinit_step)
|
||||
TxOut {
|
||||
value: 1000,
|
||||
script_pubkey: Script::new(),
|
||||
|
|
@ -654,7 +679,12 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// Build a createIDO tx: spends prev_txid:1, output[1] does NOT carry offered token.
|
||||
/// Build the final pre-init step (createIDO equivalent):
|
||||
/// input: spends prev_txid:1
|
||||
/// output[0]: offering initiator (P2SH32, no token)
|
||||
/// output[1]: offeringBytecode storage (no token)
|
||||
/// output[2]: launcherBytecode storage (no token) ← offered token absent → final step
|
||||
/// output[5]: offeredTokenFunding (has offered token, but at index 5 not 2)
|
||||
fn build_create_ido_tx(prev_txid: Txid) -> Transaction {
|
||||
use bitcoincash::{OutPoint, TxIn};
|
||||
Transaction {
|
||||
|
|
@ -686,7 +716,7 @@ mod tests {
|
|||
script_pubkey: Script::new(),
|
||||
token: Some(OutputData {
|
||||
id: offering_category,
|
||||
bitfield: 0x20, // HasNFT
|
||||
bitfield: 0x21, // HasNFT | mutable capability
|
||||
amount: 0,
|
||||
commitment,
|
||||
}),
|
||||
|
|
@ -913,7 +943,7 @@ mod tests {
|
|||
|
||||
let entries = get_ido_entries(&pool, &ann_txid).await.unwrap();
|
||||
assert_eq!(entries.len(), 1);
|
||||
let (txid_hex, n, sats, commitment_hex) = &entries[0];
|
||||
let (txid_hex, n, sats, _token_amount, commitment_hex) = &entries[0];
|
||||
assert_eq!(txid_hex, &entry_txid.to_hex());
|
||||
assert_eq!(*n, 0u32);
|
||||
assert_eq!(*sats, 10_000i64);
|
||||
|
|
@ -967,7 +997,7 @@ mod tests {
|
|||
|
||||
let entries = get_ido_entries(&pool, &ann_txid).await.unwrap();
|
||||
assert_eq!(entries.len(), 1, "only unspent entry should be returned");
|
||||
let (txid_hex, _, sats, _) = &entries[0];
|
||||
let (txid_hex, _, sats, _, _) = &entries[0];
|
||||
assert_eq!(txid_hex, &entry_a_txid.to_hex());
|
||||
assert_eq!(*sats, 5_000i64);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,11 +102,12 @@ pub async fn get_ido_entries(creation_txid: &str, conn: &State<DB>) -> CachedApi
|
|||
|
||||
let entries_json: Vec<Value> = entries
|
||||
.into_iter()
|
||||
.map(|(txid, n, sats, commitment)| {
|
||||
.map(|(txid, n, sats, token_amount, commitment)| {
|
||||
json!({
|
||||
"txid": txid,
|
||||
"n": n,
|
||||
"sats": sats,
|
||||
"token_amount": token_amount,
|
||||
"commitment": commitment,
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue