Skip to content

Commit e4d06a1

Browse files
committed
Frontend,Backend: option to print BTC transactions
Added --print-transactions option that for given Bitcoin address prints all incoming transactions in reverse chronological order. Data printed: BTC amount, USD amount, date.
1 parent 8f97232 commit e4d06a1

File tree

6 files changed

+154
-0
lines changed

6 files changed

+154
-0
lines changed

src/GWallet.Backend/GWallet.Backend-legacy.fsproj

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
<Compile Include="UtxoCoin\UtxoCoinMinerFee.fs" />
7171
<Compile Include="UtxoCoin\TransactionTypes.fs" />
7272
<Compile Include="UtxoCoin\UtxoCoinAccount.fs" />
73+
<Compile Include="UtxoCoin\BtcTransactionPrinting.fs" />
7374
<Compile Include="Ether\EtherExceptions.fs" />
7475
<Compile Include="Ether\EtherMinerFee.fs" />
7576
<Compile Include="Ether\TransactionMetadata.fs" />

src/GWallet.Backend/GWallet.Backend.fsproj

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<Compile Include="UtxoCoin\UtxoCoinMinerFee.fs" />
4040
<Compile Include="UtxoCoin\TransactionTypes.fs" />
4141
<Compile Include="UtxoCoin\UtxoCoinAccount.fs" />
42+
<Compile Include="UtxoCoin\BtcTransactionPrinting.fs" />
4243
<Compile Include="Ether\EtherExceptions.fs" />
4344
<Compile Include="Ether\EtherMinerFee.fs" />
4445
<Compile Include="Ether\TransactionMetadata.fs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
namespace GWallet.Backend.UtxoCoin
2+
3+
open System
4+
open System.Net
5+
6+
open GWallet.Backend
7+
open GWallet.Backend.FSharpUtil
8+
open GWallet.Backend.FSharpUtil.UwpHacks
9+
10+
11+
module BtcTransactionPrinting =
12+
let GetBitcoinPriceForDate (date: DateTime) : Async<Result<decimal, Exception>> =
13+
async {
14+
try
15+
let baseUrl =
16+
let dateFormated = date.ToString("dd-MM-yyyy")
17+
SPrintF1 "https://api.coingecko.com/api/v3/coins/bitcoin/history?date=%s&localization=false" dateFormated
18+
let uri = Uri baseUrl
19+
use webClient = new WebClient()
20+
let task = webClient.DownloadStringTaskAsync uri
21+
let! result = Async.AwaitTask task
22+
let json = Newtonsoft.Json.Linq.JObject.Parse result
23+
return Ok(json.["market_data"].["current_price"].["usd"].ToObject<decimal>())
24+
with
25+
| ex ->
26+
return Error ex
27+
}
28+
29+
let PrintTransactions (maxTransactionsCount: uint32) (btcAddress: string) =
30+
let address = NBitcoin.BitcoinAddress.Create(btcAddress, NBitcoin.Network.Main)
31+
async {
32+
let scriptHash = Account.GetElectrumScriptHashFromPublicAddress Currency.BTC btcAddress
33+
let! history =
34+
Server.Query
35+
Currency.BTC
36+
(QuerySettings.Default ServerSelectionMode.Fast)
37+
(ElectrumClient.GetBlockchainScriptHashHistory scriptHash)
38+
None
39+
40+
let sortedHistory = history |> List.sortByDescending (fun entry -> entry.Height)
41+
42+
let rec processHistory history (maxTransactionsToPrint: uint32) =
43+
match history with
44+
| nextEntry :: rest when maxTransactionsToPrint > 0u ->
45+
async {
46+
let! transaction =
47+
Server.Query
48+
Currency.BTC
49+
(QuerySettings.Default ServerSelectionMode.Fast)
50+
(ElectrumClient.GetBlockchainTransactionVerbose nextEntry.TxHash)
51+
None
52+
let transactionInfo = NBitcoin.Transaction.Parse(transaction.Hex, NBitcoin.Network.Main)
53+
54+
let incomingOutputs =
55+
transactionInfo.Outputs
56+
|> Seq.filter (fun output -> output.IsTo address)
57+
58+
if not (Seq.isEmpty incomingOutputs) then
59+
let amount = incomingOutputs |> Seq.sumBy (fun txOut -> txOut.Value)
60+
let dateTime =
61+
let startOfUnixEpoch = DateTime(1970, 1, 1)
62+
startOfUnixEpoch + TimeSpan.FromSeconds(float transaction.Time)
63+
let! bitcoinPrice = GetBitcoinPriceForDate dateTime
64+
Console.WriteLine(SPrintF1 "BTC amount: %A" amount)
65+
match bitcoinPrice with
66+
| Ok price ->
67+
let bitcoinAmount = amount.ToDecimal(NBitcoin.MoneyUnit.BTC)
68+
Console.WriteLine(SPrintF1 "~USD amount: %s" ((bitcoinAmount * price).ToString("F2")))
69+
| Error exn ->
70+
Console.WriteLine("Could not get bitcoin price for the date. An error has occured:\n" + exn.ToString())
71+
Console.WriteLine(SPrintF1 "date: %A UTC" dateTime)
72+
Console.WriteLine()
73+
return! processHistory rest (maxTransactionsToPrint - 1u)
74+
else
75+
return! processHistory rest maxTransactionsToPrint
76+
}
77+
| _ -> async { return () }
78+
79+
do! processHistory sortedHistory maxTransactionsCount
80+
Console.WriteLine("End of results")
81+
Console.ReadKey() |> ignore
82+
return 0
83+
}
84+
|> Async.RunSynchronously

src/GWallet.Backend/UtxoCoin/ElectrumClient.fs

+12
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ module ElectrumClient =
100100
return unspentListResult.Result
101101
}
102102

103+
let GetBlockchainScriptHashHistory scriptHash (stratumServer: Async<StratumClient>) = async {
104+
let! stratumClient = stratumServer
105+
let! history = stratumClient.BlockchainScriptHashHistory scriptHash
106+
return history.Result
107+
}
108+
103109
let GetBlockchainTransaction txHash (stratumServer: Async<StratumClient>) = async {
104110
let! stratumClient = stratumServer
105111
let! blockchainTransactionResult = stratumClient.BlockchainTransactionGet txHash
@@ -113,6 +119,12 @@ module ElectrumClient =
113119
return blockchainTransactionResult.Result
114120
}
115121

122+
let GetBlockchainTransactionVerbose (txHash: string) (stratumServer: Async<StratumClient>) = async {
123+
let! stratumClient = stratumServer
124+
let! blockchainTransactionResult = stratumClient.BlockchainTransactionGetVerbose txHash
125+
return blockchainTransactionResult.Result
126+
}
127+
116128
let EstimateFee (numBlocksTarget: int) (stratumServer: Async<StratumClient>): Async<decimal> = async {
117129
let! stratumClient = stratumServer
118130
let! estimateFeeResult = stratumClient.BlockchainEstimateFee numBlocksTarget

src/GWallet.Backend/UtxoCoin/StratumClient.fs

+50
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ type BlockchainScriptHashListUnspentResult =
4646
Result: array<BlockchainScriptHashListUnspentInnerResult>
4747
}
4848

49+
type BlockchainScriptHashHistoryInnerResult =
50+
{
51+
TxHash: string
52+
Height: uint32
53+
}
54+
55+
type BlockchainScriptHashHistoryResult =
56+
{
57+
Id: int
58+
Result: List<BlockchainScriptHashHistoryInnerResult>
59+
}
60+
4961
type BlockchainTransactionGetResult =
5062
{
5163
Id: int;
@@ -59,6 +71,20 @@ type BlockchainTransactionIdFromPosResult =
5971
Result: string
6072
}
6173

74+
type BlockchainTransactionGetVerboseInnerResult =
75+
{
76+
Locktime: uint32
77+
Confirmations: uint32
78+
Hex: string
79+
Time: uint32
80+
}
81+
82+
type BlockchainTransactionGetVerboseResult =
83+
{
84+
Id: int
85+
Result: BlockchainTransactionGetVerboseInnerResult
86+
}
87+
6288
type BlockchainEstimateFeeResult =
6389
{
6490
Id: int;
@@ -277,6 +303,18 @@ type StratumClient (jsonRpcClient: JsonRpcTcpClient) =
277303
return resObj
278304
}
279305

306+
member self.BlockchainScriptHashHistory scriptHash: Async<BlockchainScriptHashHistoryResult> =
307+
let obj = {
308+
Id = 0
309+
Method = "blockchain.scripthash.get_history"
310+
Params = scriptHash :: List.Empty
311+
}
312+
let json = Serialize obj
313+
async {
314+
let! resObj,_ = self.Request<BlockchainScriptHashHistoryResult> json
315+
return resObj
316+
}
317+
280318
member self.BlockchainTransactionGet txHash: Async<BlockchainTransactionGetResult> =
281319
let obj = {
282320
Id = 0;
@@ -302,6 +340,18 @@ type StratumClient (jsonRpcClient: JsonRpcTcpClient) =
302340
return resObj
303341
}
304342

343+
member self.BlockchainTransactionGetVerbose (txHash: string): Async<BlockchainTransactionGetVerboseResult> =
344+
let obj = {
345+
Id = 0
346+
Method = "blockchain.transaction.get"
347+
Params = [txHash :> obj; true :> obj]
348+
}
349+
let json = Serialize obj
350+
async {
351+
let! resObj,_ = self.Request<BlockchainTransactionGetVerboseResult> json
352+
return resObj
353+
}
354+
305355
// NOTE: despite Electrum-X official docs claiming that this method is deprecated... it's not! go read the official
306356
// non-shitcoin forked version of the docs: https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-methods.html#blockchain-estimatefee
307357
member self.BlockchainEstimateFee (numBlocksTarget: int): Async<BlockchainEstimateFeeResult> =

src/GWallet.Frontend.Console/Program.fs

+6
Original file line numberDiff line numberDiff line change
@@ -535,5 +535,11 @@ module Program =
535535
UpdateServersFile()
536536
| 1 when argv.[0] = "--update-servers-stats" ->
537537
UpdateServersStats()
538+
| 2 | 3 when argv.[0] = "--print-transactions" ->
539+
// --print-transactions <btcAddress> [maxTransactionsCount]
540+
if argv.Length = 2 then
541+
UtxoCoin.BtcTransactionPrinting.PrintTransactions UInt32.MaxValue argv.[1]
542+
else
543+
UtxoCoin.BtcTransactionPrinting.PrintTransactions (uint32 argv.[2]) argv.[1]
538544
| _ ->
539545
failwith "Arguments not recognized"

0 commit comments

Comments
 (0)