diff --git a/src/GWallet.Backend.Tests/Deserialization.fs b/src/GWallet.Backend.Tests/Deserialization.fs index e3cd27d0e..bf3a1697b 100644 --- a/src/GWallet.Backend.Tests/Deserialization.fs +++ b/src/GWallet.Backend.Tests/Deserialization.fs @@ -95,83 +95,71 @@ type Deserialization() = [] member __.``signed btc transaction import``() = - let deserializedSignedTrans: SignedTransaction = + let deserializedSignedTrans: SignedTransaction = Account.ImportSignedTransactionFromJson MarshallingData.SignedBtcTransactionExampleInJson + // Validate deserialized SignedTranasction Assert.That(deserializedSignedTrans, Is.Not.Null) + Assert.That(deserializedSignedTrans.Currency, Is.EqualTo Currency.BTC) + Assert.That(deserializedSignedTrans.FeeCurrency, Is.EqualTo Currency.BTC) Assert.That(deserializedSignedTrans.RawTransaction, - Is.EqualTo "0200000000010111b6e0460bb810b05744f8d38262f95fbab02b168b070598a6f31fad438fced4000000001716001427c106013c0042da165c082b3870c31fb3ab4683feffffff0200ca9a3b0000000017a914d8b6fcc85a383261df05423ddf068a8987bf0287873067a3fa0100000017a914d5df0b9ca6c0e1ba60a9ff29359d2600d9c6659d870247304402203b85cb05b43cc68df72e2e54c6cb508aa324a5de0c53f1bbfe997cbd7509774d022041e1b1823bdaddcd6581d7cde6e6a4c4dbef483e42e59e04dbacbaf537c3e3e8012103fbbdb3b3fc3abbbd983b20a557445fb041d6f21cc5977d2121971cb1ce5298978c000000") - - Assert.That(deserializedSignedTrans.TransactionInfo, Is.Not.Null) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal, Is.Not.Null) - Assert.That(deserializedSignedTrans.TransactionInfo.Cache, Is.Not.Null) - Assert.That(deserializedSignedTrans.TransactionInfo.Metadata, Is.Not.Null) - - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.Amount.ValueToSend, - Is.EqualTo(10.01m)) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.Amount.BalanceAtTheMomentOfSending, - Is.EqualTo 12.02m) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.Amount.Currency, - Is.EqualTo(Currency.BTC)) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.DestinationAddress, - Is.EqualTo("13jxHQDxGto46QhjFiMb78dZdys9ZD8vW5")) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginAddress, - Is.EqualTo("16pKBjGGZkUXo1afyBNf5ttFvV9hauS1kR")) - - let btcTxMetadata = deserializedSignedTrans.TransactionInfo.Metadata :?> UtxoCoin.TransactionMetadata - Assert.That(btcTxMetadata.Fee.EstimatedFeeInSatoshis, Is.EqualTo 10) - Assert.That(btcTxMetadata.Inputs.Length, Is.EqualTo 1) - Assert.That(deserializedSignedTrans.TransactionInfo.Metadata.FeeEstimationTime, - Is.EqualTo MarshallingData.SomeDate) - - Assert.That(deserializedSignedTrans.TransactionInfo.Cache.Balances.Count, - Is.EqualTo 5) - Assert.That(deserializedSignedTrans.TransactionInfo.Cache.UsdPrice.Count, - Is.EqualTo 5) - + Is.EqualTo "01000000000102cd9e4c06746721fe5d0ecdeabe29a0f05cc22bd7013ff76132efe476d9346bdc0000000017160014618869483590d6c1afe51160f244982e055d213ffdffffffef2763e4690975dc9415d36c06361ddee8393e6d9d86edd748ca21f10788fbc30100000017160014618869483590d6c1afe51160f244982e055d213ffdffffff01ba89000000000000220020574712746ca1942b8f0e3d52e4c1fd9406c3e1b602b328a2a77a57c233fed4640247304402206e9359074007c597a8243d4e5bbfb18ccfd83c0206fcbd1fafc02eb4946852f90220566e0d719b48d11f193d5d6d80eccbaaf44ee1771bf9ea7fd3810d41c5cb429f012102b7326aff8f2e56a341c31fbf50d0ce1a641859d837daffd7bf03f1f80a8c5eaa0247304402202fdbb2ea123c1150b26835ecd54cd59a22bca6a47f32167b35f355fbfcc12d22022011b8314e51b229d6d5a5ee216c9e038b5e05d1b5123485b935a1f823f2bf2279012102b7326aff8f2e56a341c31fbf50d0ce1a641859d837daffd7bf03f1f80a8c5eaa00000000") + + // Can't validate proposal because of "unknown origin account" error + + let btcTxMetadata = + Account.GetTransactionMetadata deserializedSignedTrans + |> Async.RunSynchronously + :?> UtxoCoin.TransactionMetadata + + Assert.That(btcTxMetadata.Fee.EstimatedFeeInSatoshis, Is.EqualTo 980) + Assert.That(btcTxMetadata.Inputs.Length, Is.EqualTo 2) + [] member __.``signed ether transaction import``() = - let deserializedSignedTrans: SignedTransaction = + let deserializedSignedTrans: SignedTransaction = Account.ImportSignedTransactionFromJson MarshallingData.SignedEtherTransactionExampleInJson + // Validate deserialized SignedTransaction Assert.That(deserializedSignedTrans, Is.Not.Null) Assert.That(deserializedSignedTrans.RawTransaction, - Is.EqualTo("doijfsoifjdosisdjfomirmjosmi")) - - Assert.That(deserializedSignedTrans.TransactionInfo, Is.Not.Null) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal, Is.Not.Null) - Assert.That(deserializedSignedTrans.TransactionInfo.Cache, Is.Not.Null) - Assert.That(deserializedSignedTrans.TransactionInfo.Metadata, Is.Not.Null) + Is.EqualTo("f86b0185019d334a3482520894d2fdfa29d5ccbb8168ba248d59ded7a25396f84e87022a8ad81f98768026a06bb7c1f8f2b40ed2bc3a3b572cdde7fddb42a8d43c561c60580183b0ed8c2d9fa035183359feab8789642135a253371f80781f4a870f0cae8a7368c5d7e102a688")) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.Amount.ValueToSend, - Is.EqualTo(10.01m)) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.Amount.BalanceAtTheMomentOfSending, - Is.EqualTo 12.02m) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.Amount.Currency, - Is.EqualTo(Currency.ETC)) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.DestinationAddress, - Is.EqualTo("0xf3j4m0rjxdddud9403j")) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginAddress, - Is.EqualTo("0xf3j4m0rjx94sushh03j")) + Assert.That(deserializedSignedTrans.Currency, + Is.EqualTo(Currency.ETH)) + + Assert.That(deserializedSignedTrans.FeeCurrency, + Is.EqualTo(Currency.ETH)) - let etherTxMetadata = deserializedSignedTrans.TransactionInfo.Metadata :?> Ether.TransactionMetadata - Assert.That(etherTxMetadata.TransactionCount, Is.EqualTo(69)) + + // Validate generated proposal + let proposal = Account.GetTransactionProposal deserializedSignedTrans + + Assert.That(proposal.Amount.ValueToSend, + Is.EqualTo(0.000609725773224054m)) + Assert.That(proposal.Amount.Currency, + Is.EqualTo(Currency.ETH)) + Assert.That(proposal.DestinationAddress, + Is.EqualTo("0xd2FDFA29D5ccbb8168Ba248D59dED7a25396f84E")) + Assert.That(proposal.OriginAddress, + Is.EqualTo("0xc295DDB9B89AFb7B0b23cFb76cb34ce33bc854D5")) + + // Validate generated metadata + let etherTxMetadata = + Account.GetTransactionMetadata deserializedSignedTrans + |> Async.RunSynchronously + :?> Ether.TransactionMetadata + + Assert.That(etherTxMetadata.TransactionCount, Is.EqualTo(1)) Assert.That(etherTxMetadata.Fee.Currency, - Is.EqualTo(Currency.ETC)) + Is.EqualTo(Currency.ETH)) Assert.That(etherTxMetadata.Fee.GasPriceInWei, - Is.EqualTo(6969)) - Assert.That(deserializedSignedTrans.TransactionInfo.Metadata.FeeEstimationTime, - Is.EqualTo MarshallingData.SomeDate) - - Assert.That(deserializedSignedTrans.TransactionInfo.Cache.Balances.Count, - Is.EqualTo(2)) - Assert.That(deserializedSignedTrans.TransactionInfo.Cache.UsdPrice.Count, - Is.EqualTo(2)) + Is.EqualTo(6932351540UL)) [] member __.``unsigned SAI transaction import``() = @@ -204,46 +192,48 @@ type Deserialization() = Assert.That(deserializedUnsignedTrans.Cache.UsdPrice.Count, Is.EqualTo(5)) [] - member __.``signed SAI transaction import``() = + member __.``signed DAI transaction import``() = - let deserializedSignedTrans: SignedTransaction = + let deserializedSignedTrans: SignedTransaction = Account.ImportSignedTransactionFromJson - MarshallingData.SignedSaiTransactionExampleInJson + MarshallingData.SignedDaiTransactionExampleInJson + // Validate deserialized SignedTransaction Assert.That(deserializedSignedTrans, Is.Not.Null) Assert.That(deserializedSignedTrans.RawTransaction, - Is.EqualTo("f8a80784c74d93708291b29489d24a6b4ccb1b6faa2625fe562bdd9a2326035980b844a9059cbb000000000000000000000000db0381b1a380d8db2724a9ca2d33e0c6c044be3b0000000000000000000000000000000000000000000000000de0b6b3a764000026a072cdeb03affd5977c76366efbc1405fbb4fa997ce72c1e4554ba9ec5ef772ddca069d522ea304efebd2537330870bc1ca9e9a6fe3eb5f8d8f66c1b82d9fc27a4bf")) + Is.EqualTo("f8a90185016c653675828792946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb000000000000000000000000d2fdfa29d5ccbb8168ba248d59ded7a25396f84e0000000000000000000000000000000000000000000000000de0b6b3a764000026a0d5c49133f38f3b60aa41747a4b7cc300a6dac87803b82ba23af9a97fd5994c3ea03122864fd6b294a3da2f3827e70fa861838a168f6533e03587358a6bdc594235")) - Assert.That(deserializedSignedTrans.TransactionInfo, Is.Not.Null) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal, Is.Not.Null) - Assert.That(deserializedSignedTrans.TransactionInfo.Cache, Is.Not.Null) - Assert.That(deserializedSignedTrans.TransactionInfo.Metadata, Is.Not.Null) - - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.Amount.ValueToSend, - Is.EqualTo(1m)) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.Amount.BalanceAtTheMomentOfSending, - Is.EqualTo 7.08m) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.Amount.Currency, - Is.EqualTo Currency.SAI) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.DestinationAddress, - Is.EqualTo("0xDb0381B1a380d8db2724A9Ca2d33E0C6C044bE3b")) - Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginAddress, - Is.EqualTo("0xba766d6d13E2Cc921Bf6e896319D32502af9e37E")) - - let etherTxMetadata = deserializedSignedTrans.TransactionInfo.Metadata :?> Ether.TransactionMetadata - Assert.That(etherTxMetadata.TransactionCount, Is.EqualTo(7)) + Assert.That(deserializedSignedTrans.Currency, + Is.EqualTo Currency.DAI) + + Assert.That(deserializedSignedTrans.FeeCurrency, + Is.EqualTo(Currency.ETH)) + + // Validate generated proposal + let proposal = Account.GetTransactionProposal deserializedSignedTrans + + Assert.That(proposal.Amount.ValueToSend, + Is.EqualTo(1.0m)) + Assert.That(proposal.Amount.Currency, + Is.EqualTo(Currency.DAI)) + Assert.That(proposal.DestinationAddress, + Is.EqualTo("0xd2FDFA29D5ccbb8168Ba248D59dED7a25396f84E")) + Assert.That(proposal.OriginAddress, + Is.EqualTo("0xc295DDB9B89AFb7B0b23cFb76cb34ce33bc854D5")) + + // Validate generated metadata + let etherTxMetadata = + Account.GetTransactionMetadata deserializedSignedTrans + |> Async.RunSynchronously + :?> Ether.TransactionMetadata + + Assert.That(etherTxMetadata.TransactionCount, Is.EqualTo(1)) Assert.That(etherTxMetadata.Fee.Currency, Is.EqualTo(Currency.ETH)) Assert.That(etherTxMetadata.Fee.GasPriceInWei, - Is.EqualTo(3343750000L)) - Assert.That(deserializedSignedTrans.TransactionInfo.Metadata.FeeEstimationTime, - Is.EqualTo MarshallingData.SomeDate) + Is.EqualTo(6113539701UL)) - Assert.That(deserializedSignedTrans.TransactionInfo.Cache.Balances.Count, - Is.EqualTo 5) - Assert.That(deserializedSignedTrans.TransactionInfo.Cache.UsdPrice.Count, - Is.EqualTo(5)) [] member __.``can roundtrip currency``() = diff --git a/src/GWallet.Backend.Tests/GWallet.Backend.Tests-legacy.fsproj b/src/GWallet.Backend.Tests/GWallet.Backend.Tests-legacy.fsproj index f4f18f795..a4d57150b 100644 --- a/src/GWallet.Backend.Tests/GWallet.Backend.Tests-legacy.fsproj +++ b/src/GWallet.Backend.Tests/GWallet.Backend.Tests-legacy.fsproj @@ -67,7 +67,7 @@ - + diff --git a/src/GWallet.Backend.Tests/GWallet.Backend.Tests.fsproj b/src/GWallet.Backend.Tests/GWallet.Backend.Tests.fsproj index 7407d1b20..a36bf095f 100644 --- a/src/GWallet.Backend.Tests/GWallet.Backend.Tests.fsproj +++ b/src/GWallet.Backend.Tests/GWallet.Backend.Tests.fsproj @@ -51,7 +51,7 @@ - + diff --git a/src/GWallet.Backend.Tests/MarshallingData.fs b/src/GWallet.Backend.Tests/MarshallingData.fs index d39aaf5e9..5987d68c8 100644 --- a/src/GWallet.Backend.Tests/MarshallingData.fs +++ b/src/GWallet.Backend.Tests/MarshallingData.fs @@ -41,8 +41,8 @@ module MarshallingData = let UnsignedSaiTransactionExampleInJson = ReadEmbeddedResource "unsignedAndFormattedSaiTransaction.json" - let SignedSaiTransactionExampleInJson = - ReadEmbeddedResource "signedAndFormattedSaiTransaction.json" + let SignedDaiTransactionExampleInJson = + ReadEmbeddedResource "signedAndFormattedDaiTransaction.json" let BasicExceptionExampleInJson = ReadEmbeddedResource "basicException.json" @@ -257,8 +257,9 @@ module MarshallingData = let SignedBtcTransactionExample = { - TransactionInfo = UnsignedBtcTransactionExample; - RawTransaction = "0200000000010111b6e0460bb810b05744f8d38262f95fbab02b168b070598a6f31fad438fced4000000001716001427c106013c0042da165c082b3870c31fb3ab4683feffffff0200ca9a3b0000000017a914d8b6fcc85a383261df05423ddf068a8987bf0287873067a3fa0100000017a914d5df0b9ca6c0e1ba60a9ff29359d2600d9c6659d870247304402203b85cb05b43cc68df72e2e54c6cb508aa324a5de0c53f1bbfe997cbd7509774d022041e1b1823bdaddcd6581d7cde6e6a4c4dbef483e42e59e04dbacbaf537c3e3e8012103fbbdb3b3fc3abbbd983b20a557445fb041d6f21cc5977d2121971cb1ce5298978c000000"; + Currency = Currency.BTC + FeeCurrency = Currency.BTC + RawTransaction = "01000000000102cd9e4c06746721fe5d0ecdeabe29a0f05cc22bd7013ff76132efe476d9346bdc0000000017160014618869483590d6c1afe51160f244982e055d213ffdffffffef2763e4690975dc9415d36c06361ddee8393e6d9d86edd748ca21f10788fbc30100000017160014618869483590d6c1afe51160f244982e055d213ffdffffff01ba89000000000000220020574712746ca1942b8f0e3d52e4c1fd9406c3e1b602b328a2a77a57c233fed4640247304402206e9359074007c597a8243d4e5bbfb18ccfd83c0206fcbd1fafc02eb4946852f90220566e0d719b48d11f193d5d6d80eccbaaf44ee1771bf9ea7fd3810d41c5cb429f012102b7326aff8f2e56a341c31fbf50d0ce1a641859d837daffd7bf03f1f80a8c5eaa0247304402202fdbb2ea123c1150b26835ecd54cd59a22bca6a47f32167b35f355fbfcc12d22022011b8314e51b229d6d5a5ee216c9e038b5e05d1b5123485b935a1f823f2bf2279012102b7326aff8f2e56a341c31fbf50d0ce1a641859d837daffd7bf03f1f80a8c5eaa00000000"; } let SignedBtcTransactionExampleInJson = @@ -303,10 +304,11 @@ module MarshallingData = Cache = realCachingDataExample; Metadata = someSaiTxMetadata } - let SignedSaiTransactionExample = + let SignedDaiTransactionExample = { - TransactionInfo = someSaiTransactionInfo - RawTransaction = "f8a80784c74d93708291b29489d24a6b4ccb1b6faa2625fe562bdd9a2326035980b844a9059cbb000000000000000000000000db0381b1a380d8db2724a9ca2d33e0c6c044be3b0000000000000000000000000000000000000000000000000de0b6b3a764000026a072cdeb03affd5977c76366efbc1405fbb4fa997ce72c1e4554ba9ec5ef772ddca069d522ea304efebd2537330870bc1ca9e9a6fe3eb5f8d8f66c1b82d9fc27a4bf"; + Currency = Currency.DAI + FeeCurrency = Currency.ETH + RawTransaction = "f8a90185016c653675828792946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb000000000000000000000000d2fdfa29d5ccbb8168ba248d59ded7a25396f84e0000000000000000000000000000000000000000000000000de0b6b3a764000026a0d5c49133f38f3b60aa41747a4b7cc300a6dac87803b82ba23af9a97fd5994c3ea03122864fd6b294a3da2f3827e70fa861838a168f6533e03587358a6bdc594235"; } let someEtherTransactionInfo = @@ -317,8 +319,9 @@ module MarshallingData = } let SignedEtherTransactionExample = { - TransactionInfo = someEtherTransactionInfo; - RawTransaction = "doijfsoifjdosisdjfomirmjosmi"; + Currency = Currency.ETH + FeeCurrency = Currency.ETH + RawTransaction = "f86b0185019d334a3482520894d2fdfa29d5ccbb8168ba248d59ded7a25396f84e87022a8ad81f98768026a06bb7c1f8f2b40ed2bc3a3b572cdde7fddb42a8d43c561c60580183b0ed8c2d9fa035183359feab8789642135a253371f80781f4a870f0cae8a7368c5d7e102a688"; } let SignedEtherTransactionExampleInJson = ReadEmbeddedResource "signedAndFormattedEtherTransaction.json" diff --git a/src/GWallet.Backend.Tests/Serialization.fs b/src/GWallet.Backend.Tests/Serialization.fs index afc048db4..0bbbf4903 100644 --- a/src/GWallet.Backend.Tests/Serialization.fs +++ b/src/GWallet.Backend.Tests/Serialization.fs @@ -76,16 +76,16 @@ type Serialization() = [] member __.``signed SAI transaction export``() = - let json = Account.ExportUnsignedTransactionToJson MarshallingData.SignedSaiTransactionExample + let json = Account.ExportUnsignedTransactionToJson MarshallingData.SignedDaiTransactionExample Assert.That(json, Is.Not.Null) Assert.That(json, Is.Not.Empty) Assert.That(json|> MarshallingData.Sanitize, - Is.EqualTo MarshallingData.SignedSaiTransactionExampleInJson) + Is.EqualTo MarshallingData.SignedDaiTransactionExampleInJson) [] member __.``can serialize exceptions``() = - let json = Account.ExportUnsignedTransactionToJson MarshallingData.SignedSaiTransactionExample + let json = Account.ExportUnsignedTransactionToJson MarshallingData.SignedDaiTransactionExample Assert.That(json, Is.Not.Null) Assert.That(json, Is.Not.Empty) Assert.That(json|> MarshallingData.Sanitize, - Is.EqualTo MarshallingData.SignedSaiTransactionExampleInJson) + Is.EqualTo MarshallingData.SignedDaiTransactionExampleInJson) diff --git a/src/GWallet.Backend.Tests/data/signedAndFormattedBtcTransaction.json b/src/GWallet.Backend.Tests/data/signedAndFormattedBtcTransaction.json index de46d58d2..82c1696bc 100644 --- a/src/GWallet.Backend.Tests/data/signedAndFormattedBtcTransaction.json +++ b/src/GWallet.Backend.Tests/data/signedAndFormattedBtcTransaction.json @@ -1,66 +1,13 @@ { "Version": "{version}", - "TypeName": "GWallet.Backend.SignedTransaction`1[[GWallet.Backend.UtxoCoin.TransactionMetadata, GWallet.Backend, Version={version}, Culture=neutral, PublicKeyToken=null]]", + "TypeName": "GWallet.Backend.SignedTransaction", "Value": { - "TransactionInfo": { - "Proposal": { - "OriginAddress": "16pKBjGGZkUXo1afyBNf5ttFvV9hauS1kR", - "Amount": { - "ValueToSend": 10.01, - "BalanceAtTheMomentOfSending": 12.02, - "Currency": { - "Case": "BTC" - } - }, - "DestinationAddress": "13jxHQDxGto46QhjFiMb78dZdys9ZD8vW5" - }, - "Metadata": { - "Fee": { - "EstimatedFeeInSatoshis": 10, - "EstimationTime": "2018-06-14T16:50:09.133411Z", - "Currency": { - "Case": "BTC" - } - }, - "Inputs": [ - { - "TransactionHash": "4d129e98d87fab00a99ebc88688752b588ec7d38c2ba5dc86d3563a6bc4c691f", - "OutputIndex": 1, - "ValueInSatoshis": 1000, - "DestinationInHex": "a9145131075257d8b8de8298e7c52891eb4b87823b9387" - } - ] - }, - "Cache": { - "UsdPrice": { - "BTC": 9156.19, - "ETC": 19.8644, - "ETH": 691.52, - "LTC": 173.592, - "SAI": 1.00376 - }, - "Addresses": { - "0xba766d6d13E2Cc921Bf6e896319D32502af9e37E": [ - "ETH", - "SAI", - "ETC" - ], - "3Buz1evVsQeHtDfQAmwfAKQsUzAt3f4TuR": [ - "BTC" - ], - "MJ88KYLTpXVigiwJGevzyxfGogmKx7WiWm": [ - "LTC" - ] - }, - "Balances": { - "BTC": 0.0, - "ETC": 8.0, - "ETH": 7.08, - "LTC": 0.0, - "SAI": 1.0 - } - } + "FeeCurrency": { + "Case": "BTC" }, - "RawTransaction": "0200000000010111b6e0460bb810b05744f8d38262f95fbab02b168b070598a6f31fad438fced4000000001716001427c106013c0042da165c082b3870c31fb3ab4683feffffff0200ca9a3b0000000017a914d8b6fcc85a383261df05423ddf068a8987bf0287873067a3fa0100000017a914d5df0b9ca6c0e1ba60a9ff29359d2600d9c6659d870247304402203b85cb05b43cc68df72e2e54c6cb508aa324a5de0c53f1bbfe997cbd7509774d022041e1b1823bdaddcd6581d7cde6e6a4c4dbef483e42e59e04dbacbaf537c3e3e8012103fbbdb3b3fc3abbbd983b20a557445fb041d6f21cc5977d2121971cb1ce5298978c000000" + "Currency": { + "Case": "BTC" + }, + "RawTransaction": "01000000000102cd9e4c06746721fe5d0ecdeabe29a0f05cc22bd7013ff76132efe476d9346bdc0000000017160014618869483590d6c1afe51160f244982e055d213ffdffffffef2763e4690975dc9415d36c06361ddee8393e6d9d86edd748ca21f10788fbc30100000017160014618869483590d6c1afe51160f244982e055d213ffdffffff01ba89000000000000220020574712746ca1942b8f0e3d52e4c1fd9406c3e1b602b328a2a77a57c233fed4640247304402206e9359074007c597a8243d4e5bbfb18ccfd83c0206fcbd1fafc02eb4946852f90220566e0d719b48d11f193d5d6d80eccbaaf44ee1771bf9ea7fd3810d41c5cb429f012102b7326aff8f2e56a341c31fbf50d0ce1a641859d837daffd7bf03f1f80a8c5eaa0247304402202fdbb2ea123c1150b26835ecd54cd59a22bca6a47f32167b35f355fbfcc12d22022011b8314e51b229d6d5a5ee216c9e038b5e05d1b5123485b935a1f823f2bf2279012102b7326aff8f2e56a341c31fbf50d0ce1a641859d837daffd7bf03f1f80a8c5eaa00000000" } } \ No newline at end of file diff --git a/src/GWallet.Backend.Tests/data/signedAndFormattedDaiTransaction.json b/src/GWallet.Backend.Tests/data/signedAndFormattedDaiTransaction.json new file mode 100644 index 000000000..cf73e51dc --- /dev/null +++ b/src/GWallet.Backend.Tests/data/signedAndFormattedDaiTransaction.json @@ -0,0 +1,13 @@ +{ + "Version": "{version}", + "TypeName": "GWallet.Backend.SignedTransaction", + "Value": { + "FeeCurrency": { + "Case": "ETH" + }, + "Currency": { + "Case": "DAI" + }, + "RawTransaction": "f8a90185016c653675828792946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb000000000000000000000000d2fdfa29d5ccbb8168ba248d59ded7a25396f84e0000000000000000000000000000000000000000000000000de0b6b3a764000026a0d5c49133f38f3b60aa41747a4b7cc300a6dac87803b82ba23af9a97fd5994c3ea03122864fd6b294a3da2f3827e70fa861838a168f6533e03587358a6bdc594235" + } +} diff --git a/src/GWallet.Backend.Tests/data/signedAndFormattedEtherTransaction.json b/src/GWallet.Backend.Tests/data/signedAndFormattedEtherTransaction.json index b93428cc2..ad1894a13 100644 --- a/src/GWallet.Backend.Tests/data/signedAndFormattedEtherTransaction.json +++ b/src/GWallet.Backend.Tests/data/signedAndFormattedEtherTransaction.json @@ -1,49 +1,13 @@ { "Version": "{version}", - "TypeName": "GWallet.Backend.SignedTransaction`1[[GWallet.Backend.Ether.TransactionMetadata, GWallet.Backend, Version={version}, Culture=neutral, PublicKeyToken=null]]", + "TypeName": "GWallet.Backend.SignedTransaction", "Value": { - "TransactionInfo": { - "Proposal": { - "OriginAddress": "0xf3j4m0rjx94sushh03j", - "Amount": { - "ValueToSend": 10.01, - "BalanceAtTheMomentOfSending": 12.02, - "Currency": { - "Case": "ETC" - } - }, - "DestinationAddress": "0xf3j4m0rjxdddud9403j" - }, - "Metadata": { - "Fee": { - "GasLimit": 21000, - "GasPriceInWei": 6969, - "Currency": { - "Case": "ETC" - }, - "EstimationTime": "2018-06-14T16:50:09.133411Z" - }, - "TransactionCount": 69 - }, - "Cache": { - "UsdPrice": { - "ETC": 169.99999999, - "ETH": 161.796 - }, - "Addresses": { - "0xFOOBARBAZ": [ - "ETC" - ], - "1fooBarBaz": [ - "BTC" - ] - }, - "Balances": { - "BTC": 0.0, - "ETC": 123456789.12345678 - } - } + "FeeCurrency": { + "Case": "ETH" }, - "RawTransaction": "doijfsoifjdosisdjfomirmjosmi" + "Currency": { + "Case": "ETH" + }, + "RawTransaction": "f86b0185019d334a3482520894d2fdfa29d5ccbb8168ba248d59ded7a25396f84e87022a8ad81f98768026a06bb7c1f8f2b40ed2bc3a3b572cdde7fddb42a8d43c561c60580183b0ed8c2d9fa035183359feab8789642135a253371f80781f4a870f0cae8a7368c5d7e102a688" } } \ No newline at end of file diff --git a/src/GWallet.Backend.Tests/data/signedAndFormattedSaiTransaction.json b/src/GWallet.Backend.Tests/data/signedAndFormattedSaiTransaction.json deleted file mode 100644 index af562adaa..000000000 --- a/src/GWallet.Backend.Tests/data/signedAndFormattedSaiTransaction.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "Version": "{version}", - "TypeName": "GWallet.Backend.SignedTransaction`1[[GWallet.Backend.Ether.TransactionMetadata, GWallet.Backend, Version={version}, Culture=neutral, PublicKeyToken=null]]", - "Value": { - "TransactionInfo": { - "Proposal": { - "OriginAddress": "0xba766d6d13E2Cc921Bf6e896319D32502af9e37E", - "Amount": { - "ValueToSend": 1.0, - "BalanceAtTheMomentOfSending": 7.08, - "Currency": { - "Case": "SAI" - } - }, - "DestinationAddress": "0xDb0381B1a380d8db2724A9Ca2d33E0C6C044bE3b" - }, - "Metadata": { - "Fee": { - "GasLimit": 37298, - "GasPriceInWei": 3343750000, - "Currency": { - "Case": "ETH" - }, - "EstimationTime": "2018-06-14T16:50:09.133411Z" - }, - "TransactionCount": 7 - }, - "Cache": { - "UsdPrice": { - "BTC": 9156.19, - "ETC": 19.8644, - "ETH": 691.52, - "LTC": 173.592, - "SAI": 1.00376 - }, - "Addresses": { - "0xba766d6d13E2Cc921Bf6e896319D32502af9e37E": [ - "ETH", - "SAI", - "ETC" - ], - "3Buz1evVsQeHtDfQAmwfAKQsUzAt3f4TuR": [ - "BTC" - ], - "MJ88KYLTpXVigiwJGevzyxfGogmKx7WiWm": [ - "LTC" - ] - }, - "Balances": { - "BTC": 0.0, - "ETC": 8.0, - "ETH": 7.08, - "LTC": 0.0, - "SAI": 1.0 - } - } - }, - "RawTransaction": "f8a80784c74d93708291b29489d24a6b4ccb1b6faa2625fe562bdd9a2326035980b844a9059cbb000000000000000000000000db0381b1a380d8db2724a9ca2d33e0c6c044be3b0000000000000000000000000000000000000000000000000de0b6b3a764000026a072cdeb03affd5977c76366efbc1405fbb4fa997ce72c1e4554ba9ec5ef772ddca069d522ea304efebd2537330870bc1ca9e9a6fe3eb5f8d8f66c1b82d9fc27a4bf" - } -} \ No newline at end of file diff --git a/src/GWallet.Backend/Account.fs b/src/GWallet.Backend/Account.fs index 0b86f5d6a..5c69924ed 100644 --- a/src/GWallet.Backend/Account.fs +++ b/src/GWallet.Backend/Account.fs @@ -194,13 +194,32 @@ module Account = | _ -> () } + + let GetTransactionProposal (signedTransaction: SignedTransaction) = + if signedTransaction.Currency.IsUtxo() then + UtxoCoin.Account.GetSignedTransactionProposal signedTransaction + elif signedTransaction.Currency.IsEtherBased() then + Ether.Account.GetSignedTransactionProposal signedTransaction + else + failwith "Unknown currency type for parsing proposal from raw transaction" + + let GetTransactionMetadata (signedTransaction: SignedTransaction) = + if signedTransaction.Currency.IsUtxo() then + UtxoCoin.Account.GetTransactionFeeMetadata signedTransaction + elif signedTransaction.Currency.IsEtherBased() then + async { + return Ether.Account.GetTransactionFeeMetadata signedTransaction + } + else + failwith "Unknown currency type for parsing metadata from raw transaction" + // FIXME: broadcasting shouldn't just get N consistent replies from FaultTolerantClient, // but send it to as many as possible, otherwise it could happen that some server doesn't // broadcast it even if you sent it - let BroadcastTransaction (trans: SignedTransaction<_>): Async = + let BroadcastTransaction (trans: SignedTransaction): Async = async { - let currency = trans.TransactionInfo.Proposal.Amount.Currency + let currency = trans.Currency let! txId = if currency.IsEtherBased() then @@ -210,9 +229,11 @@ module Account = else failwith <| SPrintF1 "Unknown currency %A" currency - do! CheckIfOutOfGas trans.TransactionInfo.Metadata txId + let! txMetadata = GetTransactionMetadata trans + + do! CheckIfOutOfGas txMetadata txId - SaveOutgoingTransactionInCache trans.TransactionInfo.Proposal trans.TransactionInfo.Metadata txId + SaveOutgoingTransactionInCache (GetTransactionProposal trans) txMetadata txId let uri = BlockExplorer.GetTransaction currency txId return uri @@ -367,7 +388,7 @@ module Account = async { do! ValidateAddress currency destination - let! txId = + let! txId, txRawTransaction = match txMetadata with | :? UtxoCoin.TransactionMetadata as btcTxMetadata -> if not (currency.IsUtxo()) then @@ -386,6 +407,15 @@ module Account = | _ -> failwith "Unknown tx metadata type" + let signedTransaction = + { + SignedTransaction.FeeCurrency = txMetadata.Currency + Currency = currency + RawTransaction = txRawTransaction + } + + let! txMetadata = GetTransactionMetadata signedTransaction + do! CheckIfOutOfGas txMetadata txId let transactionProposal = @@ -410,40 +440,17 @@ module Account = unsignedTrans.Metadata password - { TransactionInfo = unsignedTrans; RawTransaction = rawTransaction } + { + FeeCurrency = unsignedTrans.Metadata.Currency + Currency = unsignedTrans.Proposal.Amount.Currency + RawTransaction = rawTransaction + } - let public ExportSignedTransaction (trans: SignedTransaction<_>) = + let public ExportSignedTransaction (trans: SignedTransaction) = Marshalling.Serialize trans - let SaveSignedTransaction (trans: SignedTransaction<_>) (filePath: string) = - - let json = - match trans.TransactionInfo.Metadata.GetType() with - | t when t = typeof -> - let unsignedEthTx = { - Metadata = box trans.TransactionInfo.Metadata :?> Ether.TransactionMetadata; - Proposal = trans.TransactionInfo.Proposal; - Cache = trans.TransactionInfo.Cache; - } - let signedEthTx = { - TransactionInfo = unsignedEthTx; - RawTransaction = trans.RawTransaction; - } - ExportSignedTransaction signedEthTx - | t when t = typeof -> - let unsignedBtcTx = { - Metadata = box trans.TransactionInfo.Metadata :?> UtxoCoin.TransactionMetadata; - Proposal = trans.TransactionInfo.Proposal; - Cache = trans.TransactionInfo.Cache; - } - let signedBtcTx = { - TransactionInfo = unsignedBtcTx; - RawTransaction = trans.RawTransaction; - } - ExportSignedTransaction signedBtcTx - | _ -> failwith "Unknown miner fee type" - - File.WriteAllText(filePath, json) + let SaveSignedTransaction (trans: SignedTransaction) (filePath: string) = + File.WriteAllText(filePath, ExportSignedTransaction trans) let CreateReadOnlyAccounts (watchWalletInfo: WatchWalletInfo): Async = async { for etherCurrency in Currency.GetAll().Where(fun currency -> currency.IsEtherBased()) do @@ -629,23 +636,17 @@ module Account = let deserializedBtcTransaction: UnsignedTransaction = Marshalling.Deserialize json deserializedBtcTransaction.ToAbstract() - | _ when transType.GetGenericTypeDefinition() = typedefof> -> + | _ when transType.GetGenericTypeDefinition() = typedefof -> raise TransactionAlreadySigned | unexpectedType -> raise <| Exception(SPrintF1 "Unknown unsignedTransaction subtype: %s" unexpectedType.FullName) - let public ImportSignedTransactionFromJson (json: string): SignedTransaction = + let public ImportSignedTransactionFromJson (json: string): SignedTransaction = let transType = Marshalling.ExtractType json match transType with - | _ when transType = typeof> -> - let deserializedBtcTransaction: SignedTransaction = - Marshalling.Deserialize json - deserializedBtcTransaction.ToAbstract() - | _ when transType = typeof> -> - let deserializedBtcTransaction: SignedTransaction = - Marshalling.Deserialize json - deserializedBtcTransaction.ToAbstract() + | _ when transType = typeof -> + Marshalling.Deserialize json | _ when transType.GetGenericTypeDefinition() = typedefof> -> raise TransactionNotSignedYet | unexpectedType -> @@ -661,16 +662,14 @@ module Account = ImportUnsignedTransactionFromJson unsignedTransInJson - let GetSignedTransactionDetails<'T when 'T :> IBlockchainFeeInfo> (signedTransaction: SignedTransaction<'T>) + let GetSignedTransactionDetails (signedTransaction: SignedTransaction) : ITransactionDetails = - let currency = signedTransaction.TransactionInfo.Proposal.Amount.Currency + let currency = signedTransaction.Currency if currency.IsUtxo () then UtxoCoin.Account.GetSignedTransactionDetails - signedTransaction.RawTransaction - currency + signedTransaction elif currency.IsEtherBased () then Ether.Account.GetSignedTransactionDetails signedTransaction else failwith <| (SPrintF1 "Unknown currency: %A" currency) - diff --git a/src/GWallet.Backend/Ether/EtherAccount.fs b/src/GWallet.Backend/Ether/EtherAccount.fs index 6442265cb..c3f9345d2 100644 --- a/src/GWallet.Backend/Ether/EtherAccount.fs +++ b/src/GWallet.Backend/Ether/EtherAccount.fs @@ -222,9 +222,9 @@ module internal Account = let private BroadcastRawTransaction (currency: Currency) trans = Ether.Server.BroadcastTransaction currency ("0x" + trans) - let BroadcastTransaction (trans: SignedTransaction<_>) = + let BroadcastTransaction (trans: SignedTransaction) = BroadcastRawTransaction - trans.TransactionInfo.Proposal.Amount.Currency + trans.Currency trans.RawTransaction let internal GetPrivateKey (account: NormalAccount) password = @@ -365,7 +365,7 @@ module internal Account = (txMetadata: TransactionMetadata) (destination: string) (amount: TransferAmount) - (password: string) = + (password: string) = async { let baseAccount = account :> IAccount if (baseAccount.PublicAddress.Equals(destination, StringComparison.InvariantCultureIgnoreCase)) then raise DestinationEqualToOrigin @@ -374,8 +374,11 @@ module internal Account = let trans = SignTransaction account txMetadata destination amount password - BroadcastRawTransaction currency trans - + let! txId = BroadcastRawTransaction currency trans + + return txId, trans + } + let private CreateInternal (password: string) (seed: array): FileRepresentation = let privateKey = EthECKey(seed, true) let publicAddress = privateKey.GetPublicAddress() @@ -414,42 +417,43 @@ module internal Account = } ExportUnsignedTransactionToJson unsignedTransaction + let GetTransactionFeeMetadata (signedTransaction: SignedTransaction): IBlockchainFeeInfo = + let intDecoder = IntTypeDecoder() + let parsedTx = TransactionFactory.CreateTransaction signedTransaction.RawTransaction :?> SignedLegacyTransaction + let gasLimitInWei = intDecoder.DecodeBigInteger parsedTx.GasLimit |> int64 + let gasPriceInWei = intDecoder.DecodeBigInteger parsedTx.GasPrice |> int64 + let nonce = intDecoder.DecodeBigInteger parsedTx.Nonce |> int64 - let GetSignedTransactionDetails (signedTransaction: SignedTransaction<'T>): ITransactionDetails = - - match signedTransaction.TransactionInfo.Proposal.Amount.Currency with - | SAI | DAI -> - // FIXME: derive the transaction details from the raw transaction so that we can remove the proposal from - // the SignedTransaction type (as it's redundant); and when we remove it, we can also rename - // IBlockchainFeeInfo's Currency to "FeeCurrency", or change "Metadata" members whose type is - // IBlockchainFeeInfo to have "fee" in the name too, otherwise is to easy to use ETH instead of DAI - signedTransaction.TransactionInfo.Proposal :> ITransactionDetails - - | _ -> - let getTransactionChainId (tx: SignedLegacyTransactionBase) = - // the chain id can be deconstructed like so - - // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md - // into one of the following - - // https://chainid.network/ - // NOTE: according to the SO discussion, the following alrogithm is adequate - - // https://stackoverflow.com/questions/68023440/how-do-i-use-nethereum-to-extract-chain-id-from-a-raw-transaction - let v = IntTypeDecoder().DecodeBigInteger tx.Signature.V - let chainId = (v - BigInteger 35) / BigInteger 2 - chainId - - let getTransactionCurrency (tx: SignedLegacyTransactionBase) = - match int (getTransactionChainId tx) with - | chainId when chainId = int Config.EthNet -> ETH - | chainId when chainId = int Config.EtcNet -> ETC - | other -> failwith <| SPrintF1 "Could not infer currency from transaction where chainId = %i." other - - let tx = TransactionFactory.CreateTransaction signedTransaction.RawTransaction :?> SignedLegacyTransaction - - // HACK: I prefix 12 elements to the address due to AddressTypeDecoder expecting some sort of header... - let address = AddressTypeDecoder().Decode (Array.append (Array.zeroCreate 12) tx.ReceiveAddress) - - let destAddress = address.ConvertToEthereumChecksumAddress() - + { + TransactionMetadata.Fee = + MinerFee(gasLimitInWei, gasPriceInWei, DateTime.Now, signedTransaction.FeeCurrency) + TransactionCount = nonce + } :> IBlockchainFeeInfo + + let GetSignedTransactionDetails (signedTransaction: SignedTransaction): ITransactionDetails = + let getTransactionChainId (tx: SignedLegacyTransactionBase) = + // the chain id can be deconstructed like so - + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md + // into one of the following - + // https://chainid.network/ + // NOTE: according to the SO discussion, the following alrogithm is adequate - + // https://stackoverflow.com/questions/68023440/how-do-i-use-nethereum-to-extract-chain-id-from-a-raw-transaction + let v = IntTypeDecoder().DecodeBigInteger tx.Signature.V + let chainId = (v - BigInteger 35) / BigInteger 2 + chainId + + let getTransactionCurrency (tx: SignedLegacyTransactionBase) = + match int (getTransactionChainId tx) with + | chainId when chainId = int Config.EthNet -> ETH + | chainId when chainId = int Config.EtcNet -> ETC + | other -> failwith <| SPrintF1 "Could not infer currency from transaction where chainId = %i." other + + let tx = TransactionFactory.CreateTransaction signedTransaction.RawTransaction :?> SignedLegacyTransaction + + let destAddress = tx.ReceiveAddress.ConvertToEthereumChecksumAddress() + + match TokenManager.TryGetCurrencyByContractAddress destAddress with + | None -> let txDetails = { OriginAddress = TransactionVerificationAndRecovery.GetSenderAddress signedTransaction.RawTransaction @@ -458,3 +462,26 @@ module internal Account = DestinationAddress = destAddress } txDetails :> ITransactionDetails + | Some tokenCurrency -> + let tokenServiceWrapper = + TokenManager.OfflineTokenServiceWrapper tokenCurrency + + let destinationAddress, amountInWei = + tokenServiceWrapper.DecodeInputDataForTransferTransaction tx.Data + + let txDetails = + { + OriginAddress = TransactionVerificationAndRecovery.GetSenderAddress signedTransaction.RawTransaction + Amount = UnitConversion.Convert.FromWei amountInWei + Currency = tokenCurrency + DestinationAddress = destinationAddress + } + txDetails :> ITransactionDetails + let GetSignedTransactionProposal (signedTx: SignedTransaction): UnsignedTransactionProposal = + let txDetail = GetSignedTransactionDetails signedTx + { + UnsignedTransactionProposal.Amount = + TransferAmount(txDetail.Amount, txDetail.Amount, txDetail.Currency) + OriginAddress = txDetail.OriginAddress + DestinationAddress = txDetail.DestinationAddress + } \ No newline at end of file diff --git a/src/GWallet.Backend/Ether/TokenManager.fs b/src/GWallet.Backend/Ether/TokenManager.fs index 015c0b036..c627f5157 100644 --- a/src/GWallet.Backend/Ether/TokenManager.fs +++ b/src/GWallet.Backend/Ether/TokenManager.fs @@ -4,6 +4,7 @@ open System.Numerics open Nethereum.Web3 open Nethereum.Hex.HexTypes +open Nethereum.Hex.HexConvertors.Extensions open Nethereum.StandardTokenEIP20 open Nethereum.StandardTokenEIP20.ContractDefinition @@ -12,10 +13,20 @@ open GWallet.Backend.FSharpUtil.UwpHacks module TokenManager = + let ContractAddresses: Map = + Map [ + Currency.DAI, "0x6B175474E89094C44Da98b954EedeAC495271d0F" + Currency.SAI, "0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359" + ] + + let TryGetCurrencyByContractAddress addr = + ContractAddresses + |> Map.tryFindKey (fun _currency contractAddress -> contractAddress = addr) + let GetTokenContractAddress currency = match currency with - | Currency.DAI -> "0x6B175474E89094C44Da98b954EedeAC495271d0F" - | Currency.SAI -> "0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359" + | Currency.DAI + | Currency.SAI -> ContractAddresses.[currency] | _ -> raise <| invalidOp (SPrintF1 "%A has no contract address" currency) type TokenServiceWrapper(web3, currency: Currency) = @@ -45,6 +56,26 @@ module TokenManager = failwith "Assertion failed: transactionInput's VALUE property should be equal to passed tokenAmountInWei parameter" transactionInput.Data + member self.DecodeInputDataForTransferTransaction (data: byte[]) = + let transferFuncBuilder = self.ContractHandler.GetFunction() + let decodedInput = transferFuncBuilder.DecodeInput (data.ToHex true) + + let expectedParamsCount = 2 + + let transferDestination, transferAmountInWei = + try + if decodedInput.Count = expectedParamsCount then + decodedInput.[0].Result :?> string, + decodedInput.[1].Result :?> BigInteger + else + failwith <| SPrintF2 "Invalid transfer function parameters count, expected %i got %i" expectedParamsCount decodedInput.Count + with + | :? System.InvalidCastException -> + failwith "Invalid transfer function parameters type" + + transferDestination, transferAmountInWei + + // this is a dummy instance we need in order to pass it to base class of StandardTokenService, but not // really used online; FIXME: propose "Web3-less" overload to Nethereum let private dummyOfflineWeb3 = Web3() diff --git a/src/GWallet.Backend/Transaction.fs b/src/GWallet.Backend/Transaction.fs index c0ea030c6..441442c05 100644 --- a/src/GWallet.Backend/Transaction.fs +++ b/src/GWallet.Backend/Transaction.fs @@ -46,13 +46,9 @@ type UnsignedTransaction<'T when 'T:> IBlockchainFeeInfo> = Proposal = self.Proposal; } -type SignedTransaction<'T when 'T:> IBlockchainFeeInfo> = +type SignedTransaction = { - TransactionInfo: UnsignedTransaction<'T>; - RawTransaction: string; + FeeCurrency: Currency + Currency: Currency + RawTransaction: string } - member self.ToAbstract(): SignedTransaction = - { - TransactionInfo = self.TransactionInfo.ToAbstract(); - RawTransaction = self.RawTransaction; - } diff --git a/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs b/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs index ec56c1a2c..5609b0bc3 100644 --- a/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs +++ b/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs @@ -431,7 +431,7 @@ module Account = let job = ElectrumClient.BroadcastTransaction rawTx Server.Query currency QuerySettings.Broadcast job None - let internal BroadcastTransaction currency (transaction: SignedTransaction<_>) = + let internal BroadcastTransaction currency (transaction: SignedTransaction) = // FIXME: stop embedding TransactionInfo element in SignedTransaction // and show the info from the RawTx, using NBitcoin to extract it BroadcastRawTransaction currency transaction.RawTransaction @@ -441,13 +441,16 @@ module Account = (destination: string) (amount: TransferAmount) (password: string) - = + = async { let baseAccount = account :> IAccount if (baseAccount.PublicAddress.Equals(destination, StringComparison.InvariantCultureIgnoreCase)) then raise DestinationEqualToOrigin let finalTransaction = SignTransaction account txMetadata destination amount password - BroadcastRawTransaction baseAccount.Currency finalTransaction + let! txId = BroadcastRawTransaction baseAccount.Currency finalTransaction + + return txId, finalTransaction + } // TODO: maybe move this func to Backend.Account module, or simply inline it (simple enough) let public ExportUnsignedTransactionToJson trans = @@ -554,12 +557,58 @@ module Account = // TODO: propose to NBitcoin upstream to generate an NBitcoin exception instead | :? FormatException -> raise (AddressWithInvalidChecksum None) - - let GetSignedTransactionDetails<'T when 'T :> IBlockchainFeeInfo>(rawTransaction: string) - (currency: Currency) - : ITransactionDetails = - let network = GetNetwork currency - match Transaction.TryParse(rawTransaction, network) with + + let GetTransactionFeeMetadata (signedTx: SignedTransaction): Async = + async { + let network = GetNetwork signedTx.Currency + let txToValidate = Transaction.Parse (signedTx.RawTransaction, network) + + let totalOutputsAmount = txToValidate.TotalOut + + let getInputDetails (input: TxIn) = + async { + let job = ElectrumClient.GetBlockchainTransaction (input.PrevOut.Hash.ToString()) + let! inputOriginTxString = Server.Query signedTx.Currency (QuerySettings.Default ServerSelectionMode.Fast) job None + let inputOriginTx = Transaction.Parse (inputOriginTxString, network) + return + input.PrevOut.N, + inputOriginTx.Outputs.[input.PrevOut.N], + inputOriginTx.GetHash().ToString() + } + + let! inputs = + txToValidate.Inputs + |> Seq.map getInputDetails + |> Async.Parallel + + let totalInputsAmount = + inputs + |> Seq.sumBy (fun (_outputIndex, txOut, _transactionHash) -> txOut.Value) + + let minerFee = totalInputsAmount - totalOutputsAmount + + return + { + TransactionMetadata.Fee = + MinerFee(minerFee.Satoshi, DateTime.Now, signedTx.FeeCurrency) + // We don't need inputs since the metadata object gets casted to IBlockchainFeeInfo + Inputs = + inputs + |> Seq.map(fun (outputIndex, txOut, transactionHash) -> + { + TransactionHash = transactionHash + OutputIndex = int outputIndex + ValueInSatoshis = txOut.Value.Satoshi + DestinationInHex = txOut.ScriptPubKey.ToHex() + } + ) + |> Seq.toList + } :> IBlockchainFeeInfo + } + + let GetSignedTransactionDetails (signedTx: SignedTransaction) : ITransactionDetails = + let network = GetNetwork signedTx.Currency + match Transaction.TryParse(signedTx.RawTransaction, network) with | false, _ -> failwith "malformed transaction" | true, transaction -> @@ -588,10 +637,10 @@ module Account = let account = let accountOpt = - Config.GetAccountFiles [currency] AccountKind.ReadOnly + Config.GetAccountFiles [signedTx.Currency] AccountKind.ReadOnly |> Seq.map (fun accountFile -> - GetAccountFromFile accountFile currency AccountKind.ReadOnly + GetAccountFromFile accountFile signedTx.Currency AccountKind.ReadOnly :?> ReadOnlyUtxoAccount ) |> Seq.filter matchOriginToAccount @@ -624,6 +673,14 @@ module Account = OriginAddress = originAddress.ToString() DestinationAddress = destinationAddress.ToString() Amount = value.ToDecimal MoneyUnit.BTC - Currency = currency + Currency = signedTx.Currency } :> ITransactionDetails + let GetSignedTransactionProposal (signedTx: SignedTransaction): UnsignedTransactionProposal = + let txDetail = GetSignedTransactionDetails signedTx + { + UnsignedTransactionProposal.Amount = + TransferAmount(txDetail.Amount, txDetail.Amount + 1m, txDetail.Currency) + OriginAddress = txDetail.OriginAddress + DestinationAddress = txDetail.DestinationAddress + } \ No newline at end of file diff --git a/src/GWallet.Frontend.Console/App.config b/src/GWallet.Frontend.Console/App.config index 1778ee567..64c3a41ca 100644 --- a/src/GWallet.Frontend.Console/App.config +++ b/src/GWallet.Frontend.Console/App.config @@ -14,6 +14,10 @@ + + + + diff --git a/src/GWallet.Frontend.Console/Program.fs b/src/GWallet.Frontend.Console/Program.fs index 18abaa2e7..fb20e4a8e 100644 --- a/src/GWallet.Frontend.Console/Program.fs +++ b/src/GWallet.Frontend.Console/Program.fs @@ -63,9 +63,14 @@ let BroadcastPayment() = | Some signedTransaction -> let transactionDetails = Account.GetSignedTransactionDetails signedTransaction + let transactionMetadata = + signedTransaction + |> Account.GetTransactionMetadata + |> Async.RunSynchronously + Presentation.ShowTransactionData transactionDetails - signedTransaction.TransactionInfo.Metadata + transactionMetadata if UserInteraction.AskYesNo "Do you accept?" then try