Skip to content

Commit

Permalink
Add transactions page
Browse files Browse the repository at this point in the history
  • Loading branch information
Rigidity committed Dec 22, 2024
1 parent 6ebc6d0 commit 1b02282
Show file tree
Hide file tree
Showing 19 changed files with 618 additions and 116 deletions.
2 changes: 2 additions & 0 deletions crates/sage-api/src/records.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod offer;
mod offer_summary;
mod peer;
mod pending_transaction;
mod transaction;
mod transaction_summary;

pub use cat::*;
Expand All @@ -20,4 +21,5 @@ pub use offer::*;
pub use offer_summary::*;
pub use peer::*;
pub use pending_transaction::*;
pub use transaction::*;
pub use transaction_summary::*;
20 changes: 20 additions & 0 deletions crates/sage-api/src/records/transaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use serde::{Deserialize, Serialize};
use specta::Type;

use crate::{Amount, AssetKind};

#[derive(Debug, Clone, Serialize, Deserialize, Type)]
pub struct TransactionRecord {
pub height: u32,
pub spent: Vec<TransactionCoin>,
pub created: Vec<TransactionCoin>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Type)]
pub struct TransactionCoin {
pub coin_id: String,
pub amount: Amount,
pub address: Option<String>,
#[serde(flatten)]
pub kind: AssetKind,
}
14 changes: 13 additions & 1 deletion crates/sage-api/src/requests/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use specta::Type;

use crate::{
Amount, CatRecord, CoinRecord, DerivationRecord, DidRecord, NftCollectionRecord, NftData,
NftRecord, PendingTransactionRecord, Unit,
NftRecord, PendingTransactionRecord, TransactionRecord, Unit,
};

#[derive(Debug, Clone, Copy, Serialize, Deserialize, Type)]
Expand Down Expand Up @@ -82,6 +82,18 @@ pub struct GetPendingTransactionsResponse {
pub transactions: Vec<PendingTransactionRecord>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, Type)]
pub struct GetTransactions {
pub offset: u32,
pub limit: u32,
}

#[derive(Debug, Clone, Serialize, Deserialize, Type)]
pub struct GetTransactionsResponse {
pub transactions: Vec<TransactionRecord>,
pub total: u32,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, Type)]
pub struct GetNftStatus {}

Expand Down
1 change: 1 addition & 0 deletions crates/sage-cli/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ routes!(
get_cat await: GetCat = "/get_cat",
get_dids await: GetDids = "/get_dids",
get_pending_transactions await: GetPendingTransactions = "/get_pending_transactions",
get_transactions await: GetTransactions = "/get_transactions",
get_nft_status await: GetNftStatus = "/get_nft_status",
get_nft_collections await: GetNftCollections = "/get_nft_collections",
get_nft_collection await: GetNftCollection = "/get_nft_collection",
Expand Down
122 changes: 111 additions & 11 deletions crates/sage-database/src/coin_states.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use std::cmp::Reverse;

use chia::protocol::{Bytes32, CoinState};
use sqlx::SqliteExecutor;

use crate::{to_bytes32, CoinStateSql, Database, DatabaseTx, IntoRow, Result};
use crate::{
into_row, to_bytes32, CoinKind, CoinStateRow, CoinStateSql, Database, DatabaseTx, IntoRow,
Result,
};

impl Database {
pub async fn unsynced_coin_states(&self, limit: usize) -> Result<Vec<CoinState>> {
Expand Down Expand Up @@ -36,13 +41,33 @@ impl Database {
synced_coin_count(&self.pool).await
}

pub async fn sync_coin(&self, coin_id: Bytes32, hint: Option<Bytes32>) -> Result<()> {
sync_coin(&self.pool, coin_id, hint).await
pub async fn sync_coin(
&self,
coin_id: Bytes32,
hint: Option<Bytes32>,
kind: CoinKind,
) -> Result<()> {
sync_coin(&self.pool, coin_id, hint, kind).await
}

pub async fn is_coin_locked(&self, coin_id: Bytes32) -> Result<bool> {
is_coin_locked(&self.pool, coin_id).await
}

pub async fn get_block_heights(&self) -> Result<Vec<u32>> {
get_block_heights(&self.pool).await
}

pub async fn get_coin_states_by_created_height(
&self,
height: u32,
) -> Result<Vec<CoinStateRow>> {
get_coin_states_by_created_height(&self.pool, height).await
}

pub async fn get_coin_states_by_spent_height(&self, height: u32) -> Result<Vec<CoinStateRow>> {
get_coin_states_by_spent_height(&self.pool, height).await
}
}

impl<'a> DatabaseTx<'a> {
Expand Down Expand Up @@ -72,8 +97,13 @@ impl<'a> DatabaseTx<'a> {
.await
}

pub async fn sync_coin(&mut self, coin_id: Bytes32, hint: Option<Bytes32>) -> Result<()> {
sync_coin(&mut *self.tx, coin_id, hint).await
pub async fn sync_coin(
&mut self,
coin_id: Bytes32,
hint: Option<Bytes32>,
kind: CoinKind,
) -> Result<()> {
sync_coin(&mut *self.tx, coin_id, hint, kind).await
}

pub async fn unsync_coin(&mut self, coin_id: Bytes32) -> Result<()> {
Expand Down Expand Up @@ -179,7 +209,7 @@ async fn unsynced_coin_states(
let rows = sqlx::query_as!(
CoinStateSql,
"
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `created_height`, `spent_height`, `transaction_id`
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `created_height`, `spent_height`, `transaction_id`, `kind`
FROM `coin_states`
WHERE `synced` = 0 AND `created_height` IS NOT NULL
ORDER BY `spent_height` ASC
Expand All @@ -198,15 +228,18 @@ async fn sync_coin(
conn: impl SqliteExecutor<'_>,
coin_id: Bytes32,
hint: Option<Bytes32>,
kind: CoinKind,
) -> Result<()> {
let coin_id = coin_id.as_ref();
let kind = kind as u32;
let hint = hint.as_deref();

sqlx::query!(
"
UPDATE `coin_states` SET `synced` = 1, `hint` = ? WHERE `coin_id` = ?
UPDATE `coin_states` SET `synced` = 1, `hint` = ?, `kind` = ? WHERE `coin_id` = ?
",
hint,
kind,
coin_id
)
.execute(conn)
Expand Down Expand Up @@ -274,7 +307,7 @@ async fn coin_state(conn: impl SqliteExecutor<'_>, coin_id: Bytes32) -> Result<O
let Some(sql) = sqlx::query_as!(
CoinStateSql,
"
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `created_height`, `spent_height`, `transaction_id`
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `created_height`, `spent_height`, `transaction_id`, `kind`
FROM `coin_states`
WHERE `coin_id` = ?
",
Expand Down Expand Up @@ -345,16 +378,16 @@ async fn is_p2_coin(conn: impl SqliteExecutor<'_>, coin_id: Bytes32) -> Result<b

let row = sqlx::query!(
"
SELECT COUNT(*) AS `count`
FROM `p2_coins`
SELECT `kind`
FROM `coin_states`
WHERE `coin_id` = ?
",
coin_id
)
.fetch_one(conn)
.await?;

Ok(row.count > 0)
Ok(row.kind == 1)
}

async fn is_coin_locked(conn: impl SqliteExecutor<'_>, coin_id: Bytes32) -> Result<bool> {
Expand All @@ -378,3 +411,70 @@ async fn is_coin_locked(conn: impl SqliteExecutor<'_>, coin_id: Bytes32) -> Resu

Ok(row.count > 0)
}

async fn get_block_heights(conn: impl SqliteExecutor<'_>) -> Result<Vec<u32>> {
let rows = sqlx::query!(
"
SELECT DISTINCT height FROM (
SELECT created_height as height FROM coin_states INDEXED BY `coin_created`
WHERE created_height IS NOT NULL
UNION ALL
SELECT spent_height as height FROM coin_states INDEXED BY `coin_spent`
WHERE spent_height IS NOT NULL
)
GROUP BY height
"
)
.fetch_all(conn)
.await?;

let mut heights = Vec::with_capacity(rows.len());

for row in rows {
if let Some(height) = row.height {
heights.push(height.try_into()?);
}
}

heights.sort_by_key(|height| Reverse(*height));

Ok(heights)
}

async fn get_coin_states_by_created_height(
conn: impl SqliteExecutor<'_>,
height: u32,
) -> Result<Vec<CoinStateRow>> {
let rows = sqlx::query_as!(
CoinStateSql,
"
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `created_height`, `spent_height`, `transaction_id`, `kind`
FROM `coin_states` INDEXED BY `coin_created`
WHERE `created_height` = ?
",
height
)
.fetch_all(conn)
.await?;

rows.into_iter().map(into_row).collect()
}

async fn get_coin_states_by_spent_height(
conn: impl SqliteExecutor<'_>,
height: u32,
) -> Result<Vec<CoinStateRow>> {
let rows = sqlx::query_as!(
CoinStateSql,
"
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `created_height`, `spent_height`, `transaction_id`, `kind`
FROM `coin_states` INDEXED BY `coin_spent`
WHERE `spent_height` = ?
",
height
)
.fetch_all(conn)
.await?;

rows.into_iter().map(into_row).collect()
}
4 changes: 2 additions & 2 deletions crates/sage-database/src/primitives/cats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ async fn cat_coin_states(
let rows = sqlx::query_as!(
CoinStateSql,
"
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`, `kind`
FROM `cat_coins` INDEXED BY `cat_asset_id`
INNER JOIN `coin_states` ON `coin_states`.coin_id = `cat_coins`.coin_id
WHERE `asset_id` = ?
Expand All @@ -308,7 +308,7 @@ async fn created_unspent_cat_coin_states(
let rows = sqlx::query_as!(
CoinStateSql,
"
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`, `kind`
FROM `coin_states`
INNER JOIN `cat_coins` ON `coin_states`.coin_id = `cat_coins`.coin_id
WHERE `asset_id` = ?
Expand Down
4 changes: 2 additions & 2 deletions crates/sage-database/src/primitives/dids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ async fn created_unspent_did_coin_states(
let rows = sqlx::query_as!(
CoinStateSql,
"
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`, `kind`
FROM `coin_states`
INNER JOIN `did_coins` ON `coin_states`.coin_id = `did_coins`.coin_id
WHERE `spent_height` IS NULL
Expand All @@ -434,7 +434,7 @@ async fn created_unspent_did_coin_state(
let rows = sqlx::query_as!(
CoinStateSql,
"
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`, `kind`
FROM `did_coins`
INNER JOIN `coin_states` ON `coin_states`.coin_id = `did_coins`.coin_id
WHERE `launcher_id` = ?
Expand Down
4 changes: 2 additions & 2 deletions crates/sage-database/src/primitives/nfts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1207,7 +1207,7 @@ async fn created_unspent_nft_coin_states(
let rows = sqlx::query_as!(
CoinStateSql,
"
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`, `kind`
FROM `coin_states`
INNER JOIN `nft_coins` ON `coin_states`.coin_id = `nft_coins`.coin_id
WHERE `spent_height` IS NULL
Expand All @@ -1232,7 +1232,7 @@ async fn created_unspent_nft_coin_state(
let rows = sqlx::query_as!(
CoinStateSql,
"
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`, `kind`
FROM `coin_states`
INNER JOIN `nft_coins` ON `coin_states`.coin_id = `nft_coins`.coin_id
WHERE `launcher_id` = ?
Expand Down
17 changes: 8 additions & 9 deletions crates/sage-database/src/primitives/xch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async fn insert_p2_coin(conn: impl SqliteExecutor<'_>, coin_id: Bytes32) -> Resu

sqlx::query!(
"
REPLACE INTO `p2_coins` (`coin_id`) VALUES (?)
UPDATE `coin_states` SET `kind` = 1 WHERE `coin_id` = ?
",
coin_id
)
Expand All @@ -51,11 +51,11 @@ async fn insert_p2_coin(conn: impl SqliteExecutor<'_>, coin_id: Bytes32) -> Resu
async fn balance(conn: impl SqliteExecutor<'_>) -> Result<u128> {
let row = sqlx::query!(
"
SELECT `coin_states`.`amount` FROM `coin_states` INDEXED BY `coin_spent`
INNER JOIN `p2_coins` ON `coin_states`.`coin_id` = `p2_coins`.`coin_id`
SELECT `coin_states`.`amount` FROM `coin_states` INDEXED BY `coin_kind_spent`
LEFT JOIN `transaction_spends` ON `coin_states`.`coin_id` = `transaction_spends`.`coin_id`
WHERE `coin_states`.`spent_height` IS NULL
AND `transaction_spends`.`coin_id` IS NULL
AND `kind` = 1
"
)
.fetch_all(conn)
Expand All @@ -71,14 +71,14 @@ async fn spendable_coins(conn: impl SqliteExecutor<'_>) -> Result<Vec<Coin>> {
CoinSql,
"
SELECT `coin_states`.`parent_coin_id`, `coin_states`.`puzzle_hash`, `coin_states`.`amount` FROM `coin_states`
INNER JOIN `p2_coins` ON `coin_states`.`coin_id` = `p2_coins`.`coin_id`
LEFT JOIN `transaction_spends` ON `coin_states`.`coin_id` = `transaction_spends`.`coin_id`
LEFT JOIN `offered_coins` ON `coin_states`.`coin_id` = `offered_coins`.`coin_id`
LEFT JOIN `offers` ON `offered_coins`.`offer_id` = `offers`.`offer_id`
WHERE `coin_states`.`spent_height` IS NULL
AND `transaction_spends`.`coin_id` IS NULL
AND (`offered_coins`.`coin_id` IS NULL OR `offers`.`status` > 0)
AND `coin_states`.`transaction_id` IS NULL
AND `kind` = 1
"
)
.fetch_all(conn)
Expand All @@ -92,9 +92,8 @@ async fn p2_coin_states(conn: impl SqliteExecutor<'_>) -> Result<Vec<CoinStateRo
let rows = sqlx::query_as!(
CoinStateSql,
"
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`
FROM `coin_states`
INNER JOIN `p2_coins` ON `coin_states`.`coin_id` = `p2_coins`.`coin_id`
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`, `kind`
FROM `coin_states` WHERE `kind` = 1
"
)
.fetch_all(conn)
Expand All @@ -111,11 +110,11 @@ async fn created_unspent_p2_coin_states(
let rows = sqlx::query_as!(
CoinStateSql,
"
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`
SELECT `parent_coin_id`, `puzzle_hash`, `amount`, `spent_height`, `created_height`, `transaction_id`, `kind`
FROM `coin_states`
INNER JOIN `p2_coins` ON `coin_states`.`coin_id` = `p2_coins`.`coin_id`
WHERE `spent_height` IS NULL
AND `created_height` IS NOT NULL
AND `kind` = 1
ORDER BY `created_height`, `coin_states`.`coin_id` LIMIT ? OFFSET ?
",
limit,
Expand Down
Loading

0 comments on commit 1b02282

Please sign in to comment.