11use rusqlite:: { Connection , OptionalExtension } ;
22use serde_json;
3- use crate :: activity:: { Activity , ActivityError , ActivityFilter , LightningActivity , OnchainActivity , PaymentState , PaymentType , SortDirection , ClosedChannelDetails , ActivityTags , PreActivityMetadata } ;
3+ use crate :: activity:: { Activity , ActivityError , ActivityFilter , LightningActivity , OnchainActivity , PaymentState , PaymentType , SortDirection , ClosedChannelDetails , ActivityTags , PreActivityMetadata , TransactionDetails , TxInput , TxOutput } ;
44
55pub struct ActivityDB {
66 pub conn : Connection ,
@@ -96,6 +96,14 @@ const CREATE_CLOSED_CHANNELS_TABLE: &str = "
9696 channel_closure_reason TEXT NOT NULL
9797 )" ;
9898
99+ const CREATE_TRANSACTION_DETAILS_TABLE : & str = "
100+ CREATE TABLE IF NOT EXISTS transaction_details (
101+ tx_id TEXT PRIMARY KEY,
102+ amount_sats INTEGER NOT NULL,
103+ inputs TEXT NOT NULL,
104+ outputs TEXT NOT NULL
105+ )" ;
106+
99107const UPSERT_CLOSED_CHANNEL_SQL : & str = "
100108 INSERT OR REPLACE INTO closed_channels (
101109 channel_id, counterparty_node_id, funding_txo_txid, funding_txo_index,
@@ -248,6 +256,13 @@ impl ActivityDB {
248256 } ) ;
249257 }
250258
259+ // Create transaction details table
260+ if let Err ( e) = self . conn . execute ( CREATE_TRANSACTION_DETAILS_TABLE , [ ] ) {
261+ return Err ( ActivityError :: InitializationError {
262+ error_details : format ! ( "Error creating transaction_details table: {}" , e) ,
263+ } ) ;
264+ }
265+
251266 // Create indexes
252267 for statement in INDEX_STATEMENTS {
253268 if let Err ( e) = self . conn . execute ( statement, [ ] ) {
@@ -2051,6 +2066,157 @@ impl ActivityDB {
20512066 Ok ( rows > 0 )
20522067 }
20532068
2069+ /// Upserts transaction details for one or more onchain transactions.
2070+ pub fn upsert_transaction_details ( & mut self , details_list : & [ TransactionDetails ] ) -> Result < ( ) , ActivityError > {
2071+ if details_list. is_empty ( ) {
2072+ return Ok ( ( ) ) ;
2073+ }
2074+
2075+ let tx = self . conn . transaction ( ) . map_err ( |e| ActivityError :: DataError {
2076+ error_details : format ! ( "Failed to start transaction: {}" , e) ,
2077+ } ) ?;
2078+
2079+ {
2080+ let mut stmt = tx. prepare (
2081+ "INSERT OR REPLACE INTO transaction_details (tx_id, amount_sats, inputs, outputs) VALUES (?1, ?2, ?3, ?4)"
2082+ ) . map_err ( |e| ActivityError :: DataError {
2083+ error_details : format ! ( "Failed to prepare statement: {}" , e) ,
2084+ } ) ?;
2085+
2086+ for details in details_list {
2087+ if details. tx_id . is_empty ( ) {
2088+ return Err ( ActivityError :: DataError {
2089+ error_details : "Transaction ID cannot be empty" . to_string ( ) ,
2090+ } ) ;
2091+ }
2092+
2093+ let inputs_json = serde_json:: to_string ( & details. inputs ) . map_err ( |e| ActivityError :: DataError {
2094+ error_details : format ! ( "Failed to serialize inputs: {}" , e) ,
2095+ } ) ?;
2096+
2097+ let outputs_json = serde_json:: to_string ( & details. outputs ) . map_err ( |e| ActivityError :: DataError {
2098+ error_details : format ! ( "Failed to serialize outputs: {}" , e) ,
2099+ } ) ?;
2100+
2101+ stmt. execute ( rusqlite:: params![
2102+ & details. tx_id,
2103+ details. amount_sats,
2104+ & inputs_json,
2105+ & outputs_json,
2106+ ] ) . map_err ( |e| ActivityError :: InsertError {
2107+ error_details : format ! ( "Failed to upsert transaction details: {}" , e) ,
2108+ } ) ?;
2109+ }
2110+ }
2111+
2112+ tx. commit ( ) . map_err ( |e| ActivityError :: DataError {
2113+ error_details : format ! ( "Failed to commit transaction: {}" , e) ,
2114+ } ) ?;
2115+
2116+ Ok ( ( ) )
2117+ }
2118+
2119+ /// Retrieves transaction details by transaction ID.
2120+ pub fn get_transaction_details ( & self , tx_id : & str ) -> Result < Option < TransactionDetails > , ActivityError > {
2121+ let sql = "SELECT tx_id, amount_sats, inputs, outputs FROM transaction_details WHERE tx_id = ?1" ;
2122+
2123+ let mut stmt = self . conn . prepare ( sql) . map_err ( |e| ActivityError :: RetrievalError {
2124+ error_details : format ! ( "Failed to prepare statement: {}" , e) ,
2125+ } ) ?;
2126+
2127+ match stmt. query_row ( [ tx_id] , |row| {
2128+ let tx_id: String = row. get ( 0 ) ?;
2129+ let amount_sats: i64 = row. get ( 1 ) ?;
2130+ let inputs_json: String = row. get ( 2 ) ?;
2131+ let outputs_json: String = row. get ( 3 ) ?;
2132+
2133+ let inputs: Vec < TxInput > = serde_json:: from_str ( & inputs_json) . map_err ( |_| {
2134+ rusqlite:: Error :: InvalidColumnType ( 2 , "inputs" . to_string ( ) , rusqlite:: types:: Type :: Text )
2135+ } ) ?;
2136+
2137+ let outputs: Vec < TxOutput > = serde_json:: from_str ( & outputs_json) . map_err ( |_| {
2138+ rusqlite:: Error :: InvalidColumnType ( 3 , "outputs" . to_string ( ) , rusqlite:: types:: Type :: Text )
2139+ } ) ?;
2140+
2141+ Ok ( TransactionDetails {
2142+ tx_id,
2143+ amount_sats,
2144+ inputs,
2145+ outputs,
2146+ } )
2147+ } ) {
2148+ Ok ( details) => Ok ( Some ( details) ) ,
2149+ Err ( rusqlite:: Error :: QueryReturnedNoRows ) => Ok ( None ) ,
2150+ Err ( e) => Err ( ActivityError :: RetrievalError {
2151+ error_details : format ! ( "Failed to get transaction details: {}" , e) ,
2152+ } ) ,
2153+ }
2154+ }
2155+
2156+ /// Retrieves all transaction details.
2157+ pub fn get_all_transaction_details ( & self ) -> Result < Vec < TransactionDetails > , ActivityError > {
2158+ let sql = "SELECT tx_id, amount_sats, inputs, outputs FROM transaction_details ORDER BY tx_id" ;
2159+
2160+ let mut stmt = self . conn . prepare ( sql) . map_err ( |e| ActivityError :: RetrievalError {
2161+ error_details : format ! ( "Failed to prepare statement: {}" , e) ,
2162+ } ) ?;
2163+
2164+ let rows = stmt. query_map ( [ ] , |row| {
2165+ let tx_id: String = row. get ( 0 ) ?;
2166+ let amount_sats: i64 = row. get ( 1 ) ?;
2167+ let inputs_json: String = row. get ( 2 ) ?;
2168+ let outputs_json: String = row. get ( 3 ) ?;
2169+
2170+ let inputs: Vec < TxInput > = serde_json:: from_str ( & inputs_json) . map_err ( |_| {
2171+ rusqlite:: Error :: InvalidColumnType ( 2 , "inputs" . to_string ( ) , rusqlite:: types:: Type :: Text )
2172+ } ) ?;
2173+
2174+ let outputs: Vec < TxOutput > = serde_json:: from_str ( & outputs_json) . map_err ( |_| {
2175+ rusqlite:: Error :: InvalidColumnType ( 3 , "outputs" . to_string ( ) , rusqlite:: types:: Type :: Text )
2176+ } ) ?;
2177+
2178+ Ok ( TransactionDetails {
2179+ tx_id,
2180+ amount_sats,
2181+ inputs,
2182+ outputs,
2183+ } )
2184+ } ) . map_err ( |e| ActivityError :: RetrievalError {
2185+ error_details : format ! ( "Failed to execute query: {}" , e) ,
2186+ } ) ?;
2187+
2188+ let mut results = Vec :: new ( ) ;
2189+ for row in rows {
2190+ results. push ( row. map_err ( |e| ActivityError :: DataError {
2191+ error_details : format ! ( "Failed to process row: {}" , e) ,
2192+ } ) ?) ;
2193+ }
2194+
2195+ Ok ( results)
2196+ }
2197+
2198+ /// Deletes transaction details by transaction ID.
2199+ pub fn delete_transaction_details ( & mut self , tx_id : & str ) -> Result < bool , ActivityError > {
2200+ let rows = self . conn . execute (
2201+ "DELETE FROM transaction_details WHERE tx_id = ?1" ,
2202+ [ tx_id] ,
2203+ ) . map_err ( |e| ActivityError :: DataError {
2204+ error_details : format ! ( "Failed to delete transaction details: {}" , e) ,
2205+ } ) ?;
2206+
2207+ Ok ( rows > 0 )
2208+ }
2209+
2210+ /// Wipes all transaction details from the database.
2211+ pub fn wipe_all_transaction_details ( & mut self ) -> Result < ( ) , ActivityError > {
2212+ self . conn . execute ( "DELETE FROM transaction_details" , [ ] )
2213+ . map_err ( |e| ActivityError :: DataError {
2214+ error_details : format ! ( "Failed to delete all transaction details: {}" , e) ,
2215+ } ) ?;
2216+
2217+ Ok ( ( ) )
2218+ }
2219+
20542220 /// Wipes all activity data from the database
20552221 /// This deletes all activities, which cascades to delete all activity_tags due to foreign key constraints.
20562222 /// Also deletes all pre_activity_metadata and closed_channels.
@@ -2077,6 +2243,12 @@ impl ActivityDB {
20772243 error_details : format ! ( "Failed to delete all closed channels: {}" , e) ,
20782244 } ) ?;
20792245
2246+ // Delete all transaction details
2247+ tx. execute ( "DELETE FROM transaction_details" , [ ] )
2248+ . map_err ( |e| ActivityError :: DataError {
2249+ error_details : format ! ( "Failed to delete all transaction details: {}" , e) ,
2250+ } ) ?;
2251+
20802252 tx. commit ( ) . map_err ( |e| ActivityError :: DataError {
20812253 error_details : format ! ( "Failed to commit transaction: {}" , e) ,
20822254 } ) ?;
0 commit comments