diff --git a/src/Angor.Test/InvestmentOperationsTest.cs b/src/Angor.Test/InvestmentOperationsTest.cs index 4bb8cb6d9..190f3be9c 100644 --- a/src/Angor.Test/InvestmentOperationsTest.cs +++ b/src/Angor.Test/InvestmentOperationsTest.cs @@ -75,7 +75,7 @@ public void SpendFounderStage_Test() var funderReceiveCoinsKey = new Key(); var projectInvestmentInfo = new ProjectInfo(); - projectInvestmentInfo.TargetAmount = 3; + projectInvestmentInfo.TargetAmount = Money.Coins(3).Satoshi; projectInvestmentInfo.StartDate = DateTime.UtcNow; projectInvestmentInfo.ExpiryDate = DateTime.UtcNow.AddDays(5); projectInvestmentInfo.Stages = new List @@ -157,7 +157,7 @@ public void SpendFounderStage_Test() // create seeder1 investment transaction var seeder1InvTrx = operations.CreateInvestmentTransaction(network, seeder1Context, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); seeder1Context.TransactionHex = operations.SignInvestmentTransaction(network, seeder1Context.ChangeAddress, seeder1InvTrx, null, new List(), _expectedFeeEstimation) @@ -168,7 +168,7 @@ public void SpendFounderStage_Test() // create seeder2 investment transaction var seeder2InvTrx = operations.CreateInvestmentTransaction(network, seeder2Context, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); seeder2Context.TransactionHex = operations.SignInvestmentTransaction(network, seeder2Context.ChangeAddress, seeder2InvTrx, null, new List(), _expectedFeeEstimation) @@ -179,7 +179,7 @@ public void SpendFounderStage_Test() // create seeder3 investment transaction var seeder3InvTrx = operations.CreateInvestmentTransaction(network, seeder3Context, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); seeder3Context.TransactionHex = operations.SignInvestmentTransaction(network, seeder3Context.ChangeAddress, seeder3InvTrx, null, new List(), _expectedFeeEstimation) @@ -190,7 +190,7 @@ public void SpendFounderStage_Test() // create investor 1 investment transaction var investor1InvTrx = operations.CreateInvestmentTransaction(network, investor1Context, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); investor1Context.TransactionHex = operations.SignInvestmentTransaction(network, investor1Context.ChangeAddress, investor1InvTrx, null, new List(), @@ -202,7 +202,7 @@ public void SpendFounderStage_Test() // create investor 2 investment transaction var investor2InvTrx = operations.CreateInvestmentTransaction(network, investor2Context, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); investor2Context.TransactionHex = operations.SignInvestmentTransaction(network, investor2Context.ChangeAddress, investor2InvTrx, null, new List(), @@ -231,7 +231,7 @@ public void SeederTransaction_EndOfProject_Test() var words = new WalletWords { Words = new Mnemonic(Wordlist.English, WordCount.Twelve).ToString() }; var projectInvestmentInfo = new ProjectInfo(); - projectInvestmentInfo.TargetAmount = 3; + projectInvestmentInfo.TargetAmount = Money.Coins(3).Satoshi; projectInvestmentInfo.StartDate = DateTime.UtcNow; projectInvestmentInfo.ExpiryDate = DateTime.UtcNow.AddDays(5); projectInvestmentInfo.Stages = new List @@ -260,7 +260,7 @@ public void SeederTransaction_EndOfProject_Test() // create the investment transaction var seeder1InvTrx = operations.CreateInvestmentTransaction(network, seeder1Context, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); seeder1Context.TransactionHex = operations.SignInvestmentTransaction(network, seeder1Context.ChangeAddress, seeder1InvTrx, null, new List(), _expectedFeeEstimation) @@ -284,7 +284,7 @@ public void InvestorTransaction_EndOfProject_Test() var words = new WalletWords { Words = new Mnemonic(Wordlist.English, WordCount.Twelve).ToString() }; var projectInvestmentInfo = new ProjectInfo(); - projectInvestmentInfo.TargetAmount = 3; + projectInvestmentInfo.TargetAmount = Money.Coins(3).Satoshi; projectInvestmentInfo.StartDate = DateTime.UtcNow; projectInvestmentInfo.ExpiryDate = DateTime.UtcNow.AddDays(5); projectInvestmentInfo.Stages = new List @@ -312,7 +312,7 @@ public void InvestorTransaction_EndOfProject_Test() // create the investment transaction var seeder1InvTrx = operations.CreateInvestmentTransaction(network, seeder1Context, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); seeder1Context.TransactionHex = operations.SignInvestmentTransaction(network, seeder1Context.ChangeAddress, seeder1InvTrx, null, new List(), @@ -337,7 +337,7 @@ public void InvestorTransaction_WithSeederHashes_EndOfProject_Test() var words = new WalletWords { Words = new Mnemonic(Wordlist.English, WordCount.Twelve).ToString() }; var projectInvestmentInfo = new ProjectInfo(); - projectInvestmentInfo.TargetAmount = 3; + projectInvestmentInfo.TargetAmount = Money.Coins(3).Satoshi; projectInvestmentInfo.StartDate = DateTime.UtcNow; projectInvestmentInfo.ExpiryDate = DateTime.UtcNow.AddDays(5); projectInvestmentInfo.Stages = new List @@ -371,7 +371,7 @@ public void InvestorTransaction_WithSeederHashes_EndOfProject_Test() // create the investment transaction var seeder1InvTrx = operations.CreateInvestmentTransaction(network, seeder1Context, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); seeder1Context.TransactionHex = operations.SignInvestmentTransaction(network, seeder1Context.ChangeAddress, seeder1InvTrx, null, new List(), _expectedFeeEstimation) @@ -408,7 +408,7 @@ public void SpendInvestorRecoveryTest() { ProjectInfo = new ProjectInfo { - TargetAmount = 3, + TargetAmount = Money.Coins(3).Satoshi, StartDate = DateTime.UtcNow, ExpiryDate = DateTime.UtcNow.AddDays(5), Stages = new List @@ -430,7 +430,7 @@ public void SpendInvestorRecoveryTest() // create the investment transaction var investmentTransaction = operations.CreateInvestmentTransaction(network, investorContext, - Money.Coins(investorContext.ProjectInfo.TargetAmount).Satoshi); + investorContext.ProjectInfo.TargetAmount); investorContext.TransactionHex = investmentTransaction.ToHex(); @@ -508,7 +508,7 @@ public void SpendInvestorConsolidatedRecoveryTest() { ProjectInfo = new ProjectInfo { - TargetAmount = 3, + TargetAmount = Money.Coins(3).Satoshi, StartDate = DateTime.UtcNow, ExpiryDate = DateTime.UtcNow.AddDays(5), Stages = new List @@ -539,7 +539,7 @@ public void SpendInvestorConsolidatedRecoveryTest() // create the investment transaction var investmentTransaction = operations.CreateInvestmentTransaction(network, investorContext, - Money.Coins(investorContext.ProjectInfo.TargetAmount).Satoshi); + investorContext.ProjectInfo.TargetAmount); investorContext.TransactionHex = investmentTransaction.ToHex(); @@ -621,7 +621,7 @@ public void SpendSeederConsolidatedRecoveryTest() { ProjectInfo = new ProjectInfo { - TargetAmount = 3, + TargetAmount = Money.Coins(3).Satoshi, StartDate = DateTime.UtcNow, ExpiryDate = DateTime.UtcNow.AddDays(5), Stages = new List @@ -644,7 +644,7 @@ public void SpendSeederConsolidatedRecoveryTest() // create the investment transaction var investmentTransaction = operations.CreateInvestmentTransaction(network, seederContext, - Money.Coins(seederContext.ProjectInfo.TargetAmount).Satoshi); + seederContext.ProjectInfo.TargetAmount); seederContext.TransactionHex = investmentTransaction.ToHex(); @@ -711,7 +711,7 @@ public void InvestorTransaction_NoPenalty_Test() var words = new WalletWords { Words = new Mnemonic(Wordlist.English, WordCount.Twelve).ToString() }; var projectInvestmentInfo = new ProjectInfo(); - projectInvestmentInfo.TargetAmount = 3; + projectInvestmentInfo.TargetAmount = Money.Coins(3).Satoshi; projectInvestmentInfo.StartDate = DateTime.UtcNow; projectInvestmentInfo.ExpiryDate = DateTime.UtcNow.AddDays(5); projectInvestmentInfo.Stages = new List @@ -750,7 +750,7 @@ public void InvestorTransaction_NoPenalty_Test() // create the investment transaction var seeder1InvTrx = operations.CreateInvestmentTransaction(network, seeder1Context, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); seeder1Context.TransactionHex = operations.SignInvestmentTransaction(network, seeder1Context.ChangeAddress, seeder1InvTrx, null, new List(), _expectedFeeEstimation) diff --git a/src/Angor.Test/ProtocolNew/AngorTestData.cs b/src/Angor.Test/ProtocolNew/AngorTestData.cs index 2d6af3b2a..4a25b4063 100644 --- a/src/Angor.Test/ProtocolNew/AngorTestData.cs +++ b/src/Angor.Test/ProtocolNew/AngorTestData.cs @@ -1,6 +1,7 @@ using Angor.Shared; using Angor.Shared.Models; using Angor.Shared.Networks; +using Blockcore.NBitcoin; using Blockcore.NBitcoin.BIP39; using Microsoft.Extensions.Logging.Abstractions; using Moq; @@ -35,7 +36,7 @@ protected ProjectInfo GivenValidProjectInvestmentInfo( WalletWords? words = null startDate ??= DateTime.UtcNow; var projectInvestmentInfo = new ProjectInfo(); - projectInvestmentInfo.TargetAmount = 3; + projectInvestmentInfo.TargetAmount = Money.Coins(3).Satoshi; projectInvestmentInfo.StartDate = startDate.Value; projectInvestmentInfo.ExpiryDate = startDate.Value.AddDays(5); projectInvestmentInfo.PenaltyDays = 10; diff --git a/src/Angor.Test/ProtocolNew/FounderTransactionActionTest.cs b/src/Angor.Test/ProtocolNew/FounderTransactionActionTest.cs index 0d5bcdb66..54d0fbd41 100644 --- a/src/Angor.Test/ProtocolNew/FounderTransactionActionTest.cs +++ b/src/Angor.Test/ProtocolNew/FounderTransactionActionTest.cs @@ -72,7 +72,7 @@ private Transaction GivenASeederTransaction(ProjectInfo projectInvestmentInfo) projectInvestmentInfo.ProjectSeeders.SecretHashes.Add(seederContext.InvestorSecretHash); return operations.CreateInvestmentTransaction(network, seederContext, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); } private Transaction GivenAnInvestorTransaction(ProjectInfo projectInvestmentInfo) @@ -88,8 +88,7 @@ private Transaction GivenAnInvestorTransaction(ProjectInfo projectInvestmentInfo context.InvestorKey = Encoders.Hex.EncodeData(seederKey.PubKey.ToBytes()); context.ChangeAddress = seederChangeKey.PubKey.GetSegwitAddress(network).ToString(); - return operations.CreateInvestmentTransaction(network, context, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + return operations.CreateInvestmentTransaction(network, context, projectInvestmentInfo.TargetAmount); } /// diff --git a/src/Angor.Test/ProtocolNew/InvestmentIntegrationsTests.cs b/src/Angor.Test/ProtocolNew/InvestmentIntegrationsTests.cs index e25f5f69e..9086c10da 100644 --- a/src/Angor.Test/ProtocolNew/InvestmentIntegrationsTests.cs +++ b/src/Angor.Test/ProtocolNew/InvestmentIntegrationsTests.cs @@ -112,7 +112,7 @@ public void SpendFounderStage_Test() var funderReceiveCoinsKey = new Key(); var projectInvestmentInfo = new ProjectInfo(); - projectInvestmentInfo.TargetAmount = 3; + projectInvestmentInfo.TargetAmount = Money.Coins(3).Satoshi; projectInvestmentInfo.StartDate = DateTime.UtcNow; projectInvestmentInfo.ExpiryDate = DateTime.UtcNow.AddDays(5); projectInvestmentInfo.Stages = new List @@ -194,35 +194,35 @@ public void SpendFounderStage_Test() // create seeder1 investment transaction var seeder1InvTrx = _seederTransactionActions.CreateInvestmentTransaction(projectInvestmentInfo, - seeder1Context.InvestorKey, new uint256(seeder1Context.InvestorSecretHash), Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + seeder1Context.InvestorKey, new uint256(seeder1Context.InvestorSecretHash), projectInvestmentInfo.TargetAmount); founderContext.InvestmentTrasnactionsHex.Add(seeder1InvTrx.ToHex()); // create seeder2 investment transaction var seeder2InvTrx = _seederTransactionActions.CreateInvestmentTransaction(projectInvestmentInfo, - seeder2Context.InvestorKey, new uint256(seeder2Context.InvestorSecretHash), Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + seeder2Context.InvestorKey, new uint256(seeder2Context.InvestorSecretHash), projectInvestmentInfo.TargetAmount); founderContext.InvestmentTrasnactionsHex.Add(seeder2InvTrx.ToHex()); // create seeder3 investment transaction var seeder3InvTrx = _seederTransactionActions.CreateInvestmentTransaction(projectInvestmentInfo, - seeder3Context.InvestorKey, new uint256(seeder3Context.InvestorSecretHash), Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + seeder3Context.InvestorKey, new uint256(seeder3Context.InvestorSecretHash), projectInvestmentInfo.TargetAmount); founderContext.InvestmentTrasnactionsHex.Add(seeder3InvTrx.ToHex()); // create investor 1 investment transaction var investor1InvTrx = _investorTransactionActions.CreateInvestmentTransaction(projectInvestmentInfo, - investor1Context.InvestorKey, Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + investor1Context.InvestorKey, projectInvestmentInfo.TargetAmount); founderContext.InvestmentTrasnactionsHex.Add(investor1InvTrx.ToHex()); // create investor 2 investment transaction var investor2InvTrx = _investorTransactionActions.CreateInvestmentTransaction(projectInvestmentInfo, - investor2Context.InvestorKey, Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + investor2Context.InvestorKey, projectInvestmentInfo.TargetAmount); founderContext.InvestmentTrasnactionsHex.Add(investor2InvTrx.ToHex()); @@ -246,7 +246,7 @@ public void SeederTransaction_EndOfProject_Test() var words = new WalletWords { Words = new Mnemonic(Wordlist.English, WordCount.Twelve).ToString() }; var projectInvestmentInfo = new ProjectInfo(); - projectInvestmentInfo.TargetAmount = 3; + projectInvestmentInfo.TargetAmount = Money.Coins(3).Satoshi; projectInvestmentInfo.StartDate = DateTime.UtcNow; projectInvestmentInfo.ExpiryDate = DateTime.UtcNow.AddDays(5); projectInvestmentInfo.Stages = new List @@ -277,7 +277,7 @@ public void SeederTransaction_EndOfProject_Test() // create the investment transaction var seeder1InvTrx = _seederTransactionActions.CreateInvestmentTransaction(projectInvestmentInfo,seeder1Context.InvestorKey, - new uint256(seeder1Context.InvestorSecretHash), Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + new uint256(seeder1Context.InvestorSecretHash), projectInvestmentInfo.TargetAmount); var seeder1Expierytrx = _seederTransactionActions.RecoverEndOfProjectFunds(seeder1InvTrx.ToHex(), projectInvestmentInfo, 1, seeder1ReceiveCoinsKey.PubKey.ScriptPubKey.WitHash.GetAddress(network).ToString(), @@ -301,7 +301,7 @@ public void InvestorTransaction_EndOfProject_Test() var words = new WalletWords { Words = new Mnemonic(Wordlist.English, WordCount.Twelve).ToString() }; var projectInvestmentInfo = new ProjectInfo(); - projectInvestmentInfo.TargetAmount = 3; + projectInvestmentInfo.TargetAmount = Money.Coins(3).Satoshi; projectInvestmentInfo.StartDate = DateTime.UtcNow; projectInvestmentInfo.ExpiryDate = DateTime.UtcNow.AddDays(5); projectInvestmentInfo.Stages = new List @@ -329,7 +329,7 @@ public void InvestorTransaction_EndOfProject_Test() // create the investment transaction var investorInvTrx = _investorTransactionActions.CreateInvestmentTransaction(projectInvestmentInfo,seeder1Context.InvestorKey, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); var investor1Expierytrx = _investorTransactionActions.RecoverEndOfProjectFunds(investorInvTrx.ToHex(), projectInvestmentInfo, @@ -354,7 +354,7 @@ public void InvestorTransaction_WithSeederHashes_EndOfProject_Test() var words = new WalletWords { Words = new Mnemonic(Wordlist.English, WordCount.Twelve).ToString() }; var projectInvestmentInfo = new ProjectInfo(); - projectInvestmentInfo.TargetAmount = 3; + projectInvestmentInfo.TargetAmount = Money.Coins(3).Satoshi; projectInvestmentInfo.StartDate = DateTime.UtcNow; projectInvestmentInfo.ExpiryDate = DateTime.UtcNow.AddDays(5); projectInvestmentInfo.Stages = new List @@ -387,7 +387,7 @@ public void InvestorTransaction_WithSeederHashes_EndOfProject_Test() // create the investment transaction var investorInvTrx = _investorTransactionActions.CreateInvestmentTransaction(projectInvestmentInfo,investorPubKey, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); var investorExpierytrx = _investorTransactionActions.RecoverEndOfProjectFunds(investorInvTrx.ToHex(), projectInvestmentInfo, 1, investorReceiveCoinsKey.PubKey.ScriptPubKey.WitHash.GetAddress(network).ToString(), @@ -422,7 +422,7 @@ public void SpendSeederRecoveryTest() { ProjectInfo = new ProjectInfo { - TargetAmount = 3, + TargetAmount = Money.Coins(3).Satoshi, StartDate = DateTime.UtcNow, ExpiryDate = DateTime.UtcNow.AddDays(5), Stages = new List @@ -444,7 +444,7 @@ public void SpendSeederRecoveryTest() // create the investment transaction var investmentTransaction = _seederTransactionActions.CreateInvestmentTransaction(investorContext.ProjectInfo,investorContext.InvestorKey, - Hashes.Hash256(seederSecret.ToBytes()),Money.Coins(investorContext.ProjectInfo.TargetAmount).Satoshi); + Hashes.Hash256(seederSecret.ToBytes()), investorContext.ProjectInfo.TargetAmount); investorContext.TransactionHex = investmentTransaction.ToHex(); @@ -491,7 +491,7 @@ public void SpendInvestorRecoveryTest() { ProjectInfo = new ProjectInfo { - TargetAmount = 3, + TargetAmount = Money.Coins(3).Satoshi, StartDate = DateTime.UtcNow, ExpiryDate = DateTime.UtcNow.AddDays(5), PenaltyDays = 5, @@ -513,7 +513,7 @@ public void SpendInvestorRecoveryTest() // create the investment transaction var investmentTransaction = _investorTransactionActions.CreateInvestmentTransaction(investorContext.ProjectInfo,investorContext.InvestorKey, - Money.Coins(investorContext.ProjectInfo.TargetAmount).Satoshi); + investorContext.ProjectInfo.TargetAmount); investorContext.TransactionHex = investmentTransaction.ToHex(); @@ -578,7 +578,7 @@ public void InvestorTransaction_NoPenalty_Test(int stageIndex) var projectInvestmentInfo = new ProjectInfo { - TargetAmount = 3, + TargetAmount = Money.Coins(3).Satoshi, StartDate = DateTime.UtcNow, ExpiryDate = DateTime.UtcNow.AddDays(5), Stages = new List @@ -608,7 +608,7 @@ public void InvestorTransaction_NoPenalty_Test(int stageIndex) var investorInvTrx = _investorTransactionActions.CreateInvestmentTransaction(projectInvestmentInfo, Encoders.Hex.EncodeData(investorKey.PubKey.ToBytes()), - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); var secrets = new List { @@ -621,7 +621,6 @@ public void InvestorTransaction_NoPenalty_Test(int stageIndex) { var partSecrets = secrets.Where((_, index) => index != i) .ToList(); - var investorRecoverFundsNoPenalty = _investorTransactionActions.RecoverRemainingFundsWithOutPenalty( investorInvTrx.ToHex(), projectInvestmentInfo, stageIndex, @@ -635,5 +634,83 @@ public void InvestorTransaction_NoPenalty_Test(int stageIndex) investorInvTrx.Outputs.AsCoins().Where(c => c.Amount > 0)); } } + + [Fact] + public void SpendInvestorReleaseTest() + { + var network = Networks.Bitcoin.Testnet(); + + var words = new WalletWords { Words = new Mnemonic(Wordlist.English, WordCount.Twelve).ToString() }; + + // Create the investor params + var investorKey = new Key(); + var investorChangeKey = new Key(); + + var funderKey = _derivationOperations.DeriveFounderKey(words, 1); + var angorKey = _derivationOperations.DeriveAngorKey(funderKey, angorRootKey); + var founderRecoveryKey = _derivationOperations.DeriveFounderRecoveryKey(words, 1); + var funderPrivateKey = _derivationOperations.DeriveFounderPrivateKey(words, 1); + var founderRecoveryPrivateKey = _derivationOperations.DeriveFounderRecoveryPrivateKey(words, 1); + + var investorContext = new InvestorContext + { + ProjectInfo = new ProjectInfo + { + TargetAmount = Money.Coins(3).Satoshi, + StartDate = DateTime.UtcNow, + ExpiryDate = DateTime.UtcNow.AddDays(5), + PenaltyDays = 5, + Stages = new List + { + new() { AmountToRelease = 1, ReleaseDate = DateTime.UtcNow.AddDays(1) }, + new() { AmountToRelease = 1, ReleaseDate = DateTime.UtcNow.AddDays(2) }, + new() { AmountToRelease = 1, ReleaseDate = DateTime.UtcNow.AddDays(3) } + }, + FounderKey = funderKey, + FounderRecoveryKey = founderRecoveryKey, + ProjectIdentifier = angorKey, + ProjectSeeders = new ProjectSeeders() + }, + InvestorKey = Encoders.Hex.EncodeData(investorKey.PubKey.ToBytes()), + ChangeAddress = investorChangeKey.PubKey.GetSegwitAddress(network).ToString() + }; + + var investorReleaseKey = new Key(); + var investorReleasePubKey = Encoders.Hex.EncodeData(investorReleaseKey.PubKey.ToBytes()); + + // Create the investment transaction + var investmentTransaction = _investorTransactionActions.CreateInvestmentTransaction(investorContext.ProjectInfo, investorContext.InvestorKey, + investorContext.ProjectInfo.TargetAmount); + + investorContext.TransactionHex = investmentTransaction.ToHex(); + + // Build the release transaction + var releaseTransaction = _investorTransactionActions.BuildUnfundedReleaseInvestorFundsTransaction(investorContext.ProjectInfo, investmentTransaction, investorReleasePubKey); + + // Sign the release transaction + var founderSignatures = _founderTransactionActions.SignInvestorRecoveryTransactions(investorContext.ProjectInfo, + investmentTransaction.ToHex(), releaseTransaction, + Encoders.Hex.EncodeData(founderRecoveryPrivateKey.ToBytes())); + + var signedReleaseTransaction = _investorTransactionActions.AddSignaturesToUnfundedReleaseFundsTransaction(investorContext.ProjectInfo, + investmentTransaction, founderSignatures, Encoders.Hex.EncodeData(investorKey.ToBytes()), investorReleasePubKey); + + // Validate the signatures + var sigCheckResult = _investorTransactionActions.CheckInvestorUnfundedReleaseSignatures(investorContext.ProjectInfo, investmentTransaction, founderSignatures, investorReleasePubKey); + Assert.True(sigCheckResult, "Failed to validate the founder's signatures"); + + List coins = new(); + foreach (var indexedTxOut in investmentTransaction.Outputs.AsIndexedOutputs().Where(w => !w.TxOut.ScriptPubKey.IsUnspendable)) + { + coins.Add(new Blockcore.NBitcoin.Coin(indexedTxOut)); + coins.Add(new Blockcore.NBitcoin.Coin(Blockcore.NBitcoin.uint256.Zero, 0, new Blockcore.NBitcoin.Money(1000), + new Script("4a8a3d6bb78a5ec5bf2c599eeb1ea522677c4b10132e554d78abecd7561e4b42"))); // Adding fee inputs + } + + signedReleaseTransaction.Inputs.Add(new Blockcore.Consensus.TransactionInfo.TxIn( + new Blockcore.Consensus.TransactionInfo.OutPoint(Blockcore.NBitcoin.uint256.Zero, 0), null)); // Add fee to the transaction + + TransactionValidation.ThanTheTransactionHasNoErrors(signedReleaseTransaction, coins); + } } } \ No newline at end of file diff --git a/src/Angor.Test/ProtocolNew/SeederTransactionActionsTest.cs b/src/Angor.Test/ProtocolNew/SeederTransactionActionsTest.cs index 8dc7ed344..fbcc5e337 100644 --- a/src/Angor.Test/ProtocolNew/SeederTransactionActionsTest.cs +++ b/src/Angor.Test/ProtocolNew/SeederTransactionActionsTest.cs @@ -62,7 +62,7 @@ public void SeederInvestmentTransactionCreation_CallsBuildWithSeederOpReturn() .Returns(expectedTransaction); var seederInvestmentTransaction = _sut.CreateInvestmentTransaction(projectInvestmentInfo, investorKey, investorSecret, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); Assert.Same(expectedTransaction,seederInvestmentTransaction); } @@ -93,7 +93,7 @@ public void SeederInvestmentTransactionCreation_CallsBuildWithSeederScriptStages .Returns(expectedTransaction); var seederInvestmentTransaction = _sut.CreateInvestmentTransaction(projectInvestmentInfo, investorKey, investorSecret, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); Assert.Same(expectedTransaction,seederInvestmentTransaction); } diff --git a/src/Angor.Test/ProtocolNew/TransactionBuilders/InvestmentTransactionBuilderTest.cs b/src/Angor.Test/ProtocolNew/TransactionBuilders/InvestmentTransactionBuilderTest.cs index a355759bc..ebd24ff6d 100644 --- a/src/Angor.Test/ProtocolNew/TransactionBuilders/InvestmentTransactionBuilderTest.cs +++ b/src/Angor.Test/ProtocolNew/TransactionBuilders/InvestmentTransactionBuilderTest.cs @@ -16,7 +16,6 @@ public class InvestmentTransactionBuilderTest : AngorTestData private readonly Mock _projectScriptsBuilder; private readonly Mock _investmentScriptBuilder; - public InvestmentTransactionBuilderTest() { _projectScriptsBuilder = new Mock(); @@ -38,28 +37,27 @@ private Script GivenTheAngorFeeScript(ProjectInfo projectInvestmentInfo) return expectedScript; } - [Fact] public void SeederInvestmentTransactionCreation_addsAngorKeyScript() - { - var words = new WalletWords { Words = new Mnemonic(Wordlist.English, WordCount.Twelve).ToString() }; + { + var words = new WalletWords { Words = new Mnemonic(Wordlist.English, WordCount.Twelve).ToString() }; - var projectInvestmentInfo = GivenValidProjectInvestmentInfo(words); - - var expectedScript = GivenTheAngorFeeScript(projectInvestmentInfo); + var projectInvestmentInfo = GivenValidProjectInvestmentInfo(words); - var opReturnScript = new Key().ScriptPubKey; - - var seederInvestmentTransaction = _sut.BuildInvestmentTransaction(projectInvestmentInfo,opReturnScript, - new List(){}, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + var expectedScript = GivenTheAngorFeeScript(projectInvestmentInfo); + + var opReturnScript = new Key().ScriptPubKey; + + var seederInvestmentTransaction = _sut.BuildInvestmentTransaction(projectInvestmentInfo, opReturnScript, + new List() { }, + projectInvestmentInfo.TargetAmount); + + var expectedoutput = seederInvestmentTransaction.Outputs.First(); + + Assert.True(expectedoutput.ScriptPubKey.Equals(expectedScript)); + Assert.Equal(projectInvestmentInfo.TargetAmount / 100, expectedoutput.Value.Satoshi); + } - var expectedoutput = seederInvestmentTransaction.Outputs.First(); - - Assert.True(expectedoutput.ScriptPubKey.Equals(expectedScript)); - Assert.Equal(projectInvestmentInfo.TargetAmount / 100,expectedoutput.Value.ToDecimal(MoneyUnit.BTC)); - } - [Fact] public void SeederInvestmentTransactionCreation_addsOpReturnWithProjectData() { @@ -73,7 +71,7 @@ public void SeederInvestmentTransactionCreation_addsOpReturnWithProjectData() var seederInvestmentTransaction = _sut.BuildInvestmentTransaction(projectInvestmentInfo, opReturnScript, new List(){}, - Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + projectInvestmentInfo.TargetAmount); var expectedoutput = seederInvestmentTransaction.Outputs[1]; @@ -100,7 +98,7 @@ public void SeederInvestmentTransactionCreation_addsScriptForEachStage() }; var seederInvestmentTransaction = _sut.BuildInvestmentTransaction(projectInvestmentInfo, opReturnScript, - stageScripts, Money.Coins(projectInvestmentInfo.TargetAmount).Satoshi); + stageScripts, projectInvestmentInfo.TargetAmount); for (int i = 0; i < seederInvestmentTransaction.Outputs.Count(); i++) { diff --git a/src/Angor.Test/Services/TestSignService.cs b/src/Angor.Test/Services/TestSignService.cs new file mode 100644 index 000000000..5a0969a76 --- /dev/null +++ b/src/Angor.Test/Services/TestSignService.cs @@ -0,0 +1,66 @@ +using Angor.Client.Services; +using Angor.Shared; +using Angor.Shared.Models; +using Angor.Shared.Networks; +using Angor.Shared.Services; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Nostr.Client.Client; + +namespace Angor.Test.Services +{ + public class TestSignService + { + private readonly SignService _signService; + + public TestSignService() + { + var mockNetworkConfiguration = new Mock(); + var mockNetworkStorage = new Mock(); + + mockNetworkConfiguration.Setup(nc => nc.GetAngorKey()).Returns("dummyAngorKey"); + mockNetworkConfiguration.Setup(nc => nc.GetNetwork()).Returns(Networks.Bitcoin.Testnet); + + mockNetworkStorage.Setup(ns => ns.GetSettings()).Returns(new SettingsInfo + { + Relays = new List + { + new() { Name = "", Url = "wss://relay.angor.io", IsPrimary = true }, + new() { Name = "", Url = "wss://relay2.angor.io", IsPrimary = true }, + }, + }); + + var communicationFactory = new NostrCommunicationFactory(new NullLogger(), new NullLogger()); + var networkService = new NetworkService(mockNetworkStorage.Object, new HttpClient { BaseAddress = new Uri("https://angor.io") }, new NullLogger(), mockNetworkConfiguration.Object); + var subscriptionsHanding = new RelaySubscriptionsHandling(new NullLogger(), communicationFactory, networkService); + + _signService = new SignService(communicationFactory, networkService, subscriptionsHanding); + } + + //[Fact] // uncomment to test + public async Task TestLookupInvestmentRequestApprovals() + { + string nostrPubKey = "5a05cc7a38e3875ee3242e5f068304a36c9609c4c15f5baaf7d75e8fcdfe36c5"; // Replace with actual public key + + var tcs = new TaskCompletionSource(); + + bool failed = false; + _signService.LookupSignedReleaseSigs(nostrPubKey, item => + { + if(item.NostrEvent.Tags.FindFirstTagValue("subject") != "Release transaction signatures") + { + failed = true; + tcs.SetResult(false); + } + }, + () => + { + tcs.SetResult(true); + }); + + await tcs.Task; + + Assert.False(failed); + } + } +} diff --git a/src/Angor.Test/WalletOperationsTest.cs b/src/Angor.Test/WalletOperationsTest.cs index 609419a79..1a1d218c8 100644 --- a/src/Angor.Test/WalletOperationsTest.cs +++ b/src/Angor.Test/WalletOperationsTest.cs @@ -122,7 +122,7 @@ public void AddFeeAndSignTransaction_test() recoveryTransaction.Outputs.RemoveAt(0); recoveryTransaction.Inputs.RemoveAt(0); - var recoveryTransactions = _sut.AddFeeAndSignTransaction(changeAddress, recoveryTransaction, words, accountInfo, 3000300); + var recoveryTransactions = _sut.AddFeeAndSignTransaction(changeAddress, recoveryTransaction, words, accountInfo, 3000); // add the inputs of the investment trx List coins = new(); @@ -153,7 +153,7 @@ public void AddInputsAndSignTransaction() var network = _networkConfiguration.Object.GetNetwork(); var projectInfo = new ProjectInfo(); - projectInfo.TargetAmount = 3; + projectInfo.TargetAmount = Money.Coins(3).Satoshi; projectInfo.StartDate = DateTime.UtcNow; projectInfo.ExpiryDate = DateTime.UtcNow.AddDays(5); projectInfo.PenaltyDays = 10; @@ -182,7 +182,8 @@ public void AddInputsAndSignTransaction() var unsignedRecoveryTransaction = _investorTransactionActions.BuildRecoverInvestorFundsTransaction(projectInfo, strippedInvestmentTransaction); var recoverySigs = _founderTransactionActions.SignInvestorRecoveryTransactions(projectInfo, strippedInvestmentTransaction.ToHex(), unsignedRecoveryTransaction, Encoders.Hex.EncodeData(founderRecoveryPrivateKey.ToBytes())); - _investorTransactionActions.CheckInvestorRecoverySignatures(projectInfo, signedInvestmentTransaction.Transaction, recoverySigs); + var sigCheckResult = _investorTransactionActions.CheckInvestorRecoverySignatures(projectInfo, signedInvestmentTransaction.Transaction, recoverySigs); + Assert.True(sigCheckResult, "failed to validate the founders signatures"); var recoveryTransaction = _investorTransactionActions.AddSignaturesToRecoverSeederFundsTransaction(projectInfo, signedInvestmentTransaction.Transaction, recoverySigs, Encoders.Hex.EncodeData(investorPrivateKey.ToBytes())); @@ -299,25 +300,25 @@ public async Task TransactionSucceeds_WithSufficientFundsWallet() SendToAddress = "tb1qw4vvm955kq5vrnx48m3x6kq8rlpgcauzzx63sr", ChangeAddress = "tb1qw4vvm955kq5vrnx48m3x6kq8rlpgcauzzx63sr", SendAmount = Money.Coins(0.01m).Satoshi, + FeeRate = Money.Satoshis(3000).Satoshi, SendUtxos = new Dictionary - { { - "key", new UtxoDataWithPath { - UtxoData = new UtxoData + "key", new UtxoDataWithPath { - value = 1500000, // Sufficient to cover the send amount and estimated fees - address = address, - scriptHex = scriptHex, - outpoint = new Outpoint("0000000000000000000000000000000000000000000000000000000000000000", 0), - blockIndex = 1, - PendingSpent = false - }, - HdPath = "m/0/0" + UtxoData = new UtxoData + { + value = 1500000, // Sufficient to cover the send amount and estimated fees + address = address, + scriptHex = scriptHex, + outpoint = new Outpoint("0000000000000000000000000000000000000000000000000000000000000000", 0), + blockIndex = 1, + PendingSpent = false + }, + HdPath = "m/0/0" + } } - } - }, - FeeRate = Money.Coins(3000).Satoshi, + }, }; // Act @@ -385,32 +386,32 @@ public void CalculateTransactionFee_WithMultipleScenarios() var address = "tb1qeu7wvxjg7ft4fzngsdxmv0pphdux2uthq4z679"; var scriptHex = "0014b7d165bb8b25f567f05c57d3b484159582ac2827"; var accountInfo = new AccountInfo(); - long feeRate = Money.Coins(10).Satoshi; + long feeRate = Money.Satoshis(10).Satoshi; // Scenario 1: Sufficient funds var sendInfoSufficientFunds = new SendInfo { SendToAddress = "tb1qw4vvm955kq5vrnx48m3x6kq8rlpgcauzzx63sr", ChangeAddress = "tb1qw4vvm955kq5vrnx48m3x6kq8rlpgcauzzx63sr", - SendAmount = Money.Coins(0.0001m).Satoshi, // Lower amount for successful fee calculation + SendAmount = Money.Coins(0.0001m).Satoshi, // Lower amount for successful fee calculation SendUtxos = new Dictionary - { { - "key", new UtxoDataWithPath { - UtxoData = new UtxoData + "key", new UtxoDataWithPath { - value = 150000000, // Sufficient to cover the send amount and estimated fees - address = address, - scriptHex = scriptHex, - outpoint = new Outpoint("0000000000000000000000000000000000000000000000000000000000000000", 0), - blockIndex = 1, - PendingSpent = false - }, - HdPath = "m/0/0" + UtxoData = new UtxoData + { + value = 150000000, // Sufficient to cover the send amount and estimated fees + address = address, + scriptHex = scriptHex, + outpoint = new Outpoint("0000000000000000000000000000000000000000000000000000000000000000", 0), + blockIndex = 1, + PendingSpent = false + }, + HdPath = "m/0/0" + } } } - } }; // Act & Assert for sufficient funds diff --git a/src/Angor/Client/Models/FounderProject.cs b/src/Angor/Client/Models/FounderProject.cs index 0d4a09abe..d58ddd46a 100644 --- a/src/Angor/Client/Models/FounderProject.cs +++ b/src/Angor/Client/Models/FounderProject.cs @@ -9,7 +9,12 @@ public class FounderProject : Project public string ProjectInfoEventId { get; set; } public bool NostrProfileCreated { get; set; } - + + public bool ProjectHasStarted() + { + return DateTime.UtcNow > ProjectInfo.StartDate; + } + public bool NostrMetadataCreated() { return !string.IsNullOrEmpty(Metadata?.Name); diff --git a/src/Angor/Client/Models/InvestmentState.cs b/src/Angor/Client/Models/InvestmentState.cs index 311ffb1c8..c7ac4191c 100644 --- a/src/Angor/Client/Models/InvestmentState.cs +++ b/src/Angor/Client/Models/InvestmentState.cs @@ -5,4 +5,6 @@ public class InvestmentState public string ProjectIdentifier { get; set; } public string InvestmentTransactionHash { get; set; } public string investorPubKey { get; set; } + public string ReleaseAddress { get; set; } + } \ No newline at end of file diff --git a/src/Angor/Client/Models/InvestorProject.cs b/src/Angor/Client/Models/InvestorProject.cs index 590e14c77..1dab8e197 100644 --- a/src/Angor/Client/Models/InvestorProject.cs +++ b/src/Angor/Client/Models/InvestorProject.cs @@ -18,6 +18,17 @@ public class InvestorProject : Project public string InvestorPublicKey { get; set; } public string InvestorNPub { get; set; } + /// + /// The address to release the funds to if the project did not reach the target. + /// This will be used by the founder when signing the release outputs + /// + public string UnfundedReleaseAddress { get; set; } + + /// + /// The trxid of an unfunded project that the investor has released the funds without a penalty + /// + public string UnfundedReleaseTransactionId { get; set; } + public bool WaitingForFounderResponse() { return ReceivedFounderSignatures() == false && SignaturesInfo?.TimeOfSignatureRequest != null; diff --git a/src/Angor/Client/Pages/CheckTransactionCode.razor b/src/Angor/Client/Pages/CheckTransactionCode.razor index 9b48cb6a4..360eab014 100644 --- a/src/Angor/Client/Pages/CheckTransactionCode.razor +++ b/src/Angor/Client/Pages/CheckTransactionCode.razor @@ -181,7 +181,7 @@ else FounderKey = Encoders.Hex.EncodeData(privateFounderKey.Neuter().PubKey.ToBytes()), PenaltyDays = 100, StartDate = DateTime.Now, - TargetAmount = 10, + TargetAmount = Money.Coins(10).Satoshi, ProjectIdentifier = derivationOperations.DeriveAngorKey(Encoders.Hex.EncodeData(privateFounderKey.Neuter().PubKey.ToBytes()),angorRootKey), ProjectSeeders = new ProjectSeeders() } diff --git a/src/Angor/Client/Pages/Create.razor b/src/Angor/Client/Pages/Create.razor index 9551a2471..8b61a1f4e 100644 --- a/src/Angor/Client/Pages/Create.razor +++ b/src/Angor/Client/Pages/Create.razor @@ -5,6 +5,7 @@ @using Angor.Shared.Models @using Angor.Shared.ProtocolNew @using Angor.Shared.Services +@using Angor.Shared.Utilities @using Blockcore.Consensus.TransactionInfo @using Blockcore.NBitcoin @using Blockcore.Networks @@ -474,7 +475,7 @@

Founder Key: @project.ProjectInfo.FounderKey.Substring(0, 10)...

- Target amount: @project.ProjectInfo.TargetAmount @network.CoinTicker + Target amount: @project.ProjectInfo.TargetAmount.ToUnitBtc() @network.CoinTicker

Start date: @project.ProjectInfo.StartDate.ToString("dd/MM/yyyy") in @((project.ProjectInfo.StartDate - DateTime.Now).Days) days

@@ -516,7 +517,7 @@ @if (publishProjectSpinner) { - Confirm... + Confirming... } else { @@ -552,8 +553,6 @@ } - - @@ -585,10 +584,9 @@ private decimal targetAmountInBTC { - get => Money.Coins(project.ProjectInfo.TargetAmount).ToUnit(MoneyUnit.BTC); - set => project.ProjectInfo.TargetAmount = (long)Money.Satoshis(value); + get => Money.Satoshis(project.ProjectInfo.TargetAmount).ToUnit(MoneyUnit.BTC); + set => project.ProjectInfo.TargetAmount = Money.Coins(value).Satoshi; } - private int activeTab = 1; @@ -1237,7 +1235,7 @@ StartDate = DateTime.UtcNow.AddMinutes(2), // to allow testing and spending immediately PenaltyDays = 90, ExpiryDate = DateTime.UtcNow.AddDays(120), - TargetAmount = 50 + TargetAmount = Money.Coins(50).Satoshi } }; } diff --git a/src/Angor/Client/Pages/Invest.razor b/src/Angor/Client/Pages/Invest.razor index cf91be93a..56c95b18b 100644 --- a/src/Angor/Client/Pages/Invest.razor +++ b/src/Angor/Client/Pages/Invest.razor @@ -14,6 +14,7 @@ @using Angor.Client.Models @using Blockcore.Networks @using System.Text.Json +@using Angor.Shared.Utilities @inherits BaseComponent @@ -80,7 +81,7 @@
Target Amount
-

@project.ProjectInfo.TargetAmount @network.CoinTicker

+

@project.ProjectInfo.TargetAmount.ToUnitBtc() @network.CoinTicker

@@ -192,33 +193,23 @@
- - -
- - - - - + @network.CoinTicker
- -
+ min="0.001" max="@project.ProjectInfo.TargetAmount.ToUnitBtc()" step="0.001" + @bind="Investment.InvestmentAmountBtc" + @oninput="UpdateStagesBreakdown" />
0.001 @network.CoinTicker - @project.ProjectInfo.TargetAmount @network.CoinTicker + @project.ProjectInfo.TargetAmount.ToUnitBtc() @network.CoinTicker
@@ -249,7 +240,7 @@
Stage @stage.StageNumber
- @stage.Amount @network.CoinTicker + @stage.AmountBtc @network.CoinTicker

@@ -307,7 +298,7 @@

Amount to Invest
- @(Money.Satoshis(Investment.InvestmentAmount).ToUnit(MoneyUnit.BTC)) @network.CoinTicker + @Investment.InvestmentAmountBtc @network.CoinTicker
@@ -362,7 +353,7 @@
Stage @(++index)
-
@StagesBreakdown[index - 1].Amount @network.CoinTicker
+
@StagesBreakdown[index - 1].AmountBtc @network.CoinTicker
Release Date
@@ -407,7 +398,6 @@
} - } else { @@ -503,8 +493,6 @@ else } } - - @code { [Parameter] @@ -659,10 +647,6 @@ else return Task.CompletedTask; } - - - - private void UpdateStagesBreakdown(ChangeEventArgs e) { if (decimal.TryParse(e.Value.ToString(), out decimal amountBtc)) @@ -677,7 +661,7 @@ else var insert = new StageBreakdown { StageNumber = index++, - Amount = Investment.InvestmentAmountBtc * (stage.AmountToRelease / 100), + AmountBtc = Investment.InvestmentAmountBtc * (stage.AmountToRelease / 100), StageDateTime = stage.ReleaseDate, DaysFromStartDate = (stage.ReleaseDate - project.ProjectInfo.StartDate).Days }; @@ -706,13 +690,15 @@ else bool isTestnet = network.NetworkType == NetworkType.Testnet; long maxInvestmentAmount = isTestnet ? long.MaxValue : Money.Satoshis(0.1m) ; // 0.1 BTC for mainnet, unlimited for testnet long minInvestmentAmount = Money.Satoshis(0.001m) ; // Minimum investment for all networks - if (Investment.InvestmentAmount < minInvestmentAmount) + long investmentAmount = Investment.InvestmentAmountBtc.ToUnitSatoshi(); + + if (investmentAmount < minInvestmentAmount) { notificationComponent.ShowErrorMessage($"Seeder minimum investment amount of {minInvestmentAmount} BTC was not reached"); return; } - if (Investment.InvestmentAmount > maxInvestmentAmount) + if (investmentAmount > maxInvestmentAmount) { notificationComponent.ShowErrorMessage($"Maximum investment amount is {maxInvestmentAmount} BTC on the mainnet"); return; @@ -720,7 +706,9 @@ else } else { - if (Investment.InvestmentAmount < Money.Satoshis(0.001m)) + long investmentAmount = Investment.InvestmentAmountBtc.ToUnitSatoshi(); + + if (investmentAmount < Money.Satoshis(0.001m)) { notificationComponent.ShowErrorMessage($"Investor minimum investment amount is {0.001} BTC"); return; @@ -761,7 +749,7 @@ else var seederHash = _derivationOperations.DeriveSeederSecretHash(words, project.ProjectInfo.FounderKey); } - unSignedTransaction = _InvestorTransactionActions.CreateInvestmentTransaction(project.ProjectInfo, InvestorPubKey, Money.Satoshis(Investment.InvestmentAmount)); + unSignedTransaction = _InvestorTransactionActions.CreateInvestmentTransaction(project.ProjectInfo, InvestorPubKey, Money.Coins(Investment.InvestmentAmountBtc).Satoshi); signedTransaction = _WalletOperations.AddInputsAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unSignedTransaction, words, accountInfo, feeData.SelectedFeeEstimation.FeeRate); @@ -834,7 +822,7 @@ else Metadata = project.Metadata, SignedTransactionHex = signedTransaction!.Transaction!.ToHex(), CreationTransactionId = project.CreationTransactionId, - AmountInvested = Investment.InvestmentAmount, + AmountInvested = Investment.InvestmentAmountBtc.ToUnitSatoshi(), InvestorPublicKey = InvestorPubKey ?? throw new ArgumentNullException("The investor pub key is not populated") }; @@ -853,25 +841,30 @@ else var words = await passwordComponent.GetWalletAsync(); - var nostrPrivateKey = _derivationOperations.DeriveProjectNostrInvestorPrivateKey(words, project.ProjectInfo.ProjectIdentifier); + var investorNostrPrivateKey = _derivationOperations.DeriveProjectNostrInvestorPrivateKey(words, project.ProjectInfo.ProjectIdentifier); + var nostrPrivateKeyHex = Encoders.Hex.EncodeData(investorNostrPrivateKey.ToBytes()); + + var releaseAddress = accountInfo.GetNextReceiveAddress(); + + SignRecoveryRequest signRecoveryRequest = new() + { + ProjectIdentifier = investorProject.ProjectInfo.ProjectIdentifier, + InvestmentTransactionHex = strippedInvestmentTransaction.ToHex(network.Consensus.ConsensusFactory), + UnfundedReleaseAddress = releaseAddress + }; - var nostrPrivateKeyHex = Encoders.Hex.EncodeData(nostrPrivateKey.ToBytes()); + var sigJson = serializer.Serialize(signRecoveryRequest); var encryptedContent = await encryption.EncryptNostrContentAsync( nostrPrivateKeyHex, investorProject.ProjectInfo.NostrPubKey, - strippedInvestmentTransaction.ToHex(network.Consensus.ConsensusFactory)); + sigJson); - var investmentSigsRequest = _SignService.RequestInvestmentSigs(new SignRecoveryRequest - { - ProjectIdentifier = investorProject.ProjectInfo.ProjectIdentifier, - EncryptedContent = encryptedContent, - NostrPubKey = investorProject.ProjectInfo.NostrPubKey, - InvestorNostrPrivateKey = nostrPrivateKeyHex - }); + var investmentSigsRequest = _SignService.RequestInvestmentSigs(encryptedContent, nostrPrivateKeyHex, investorProject.ProjectInfo.NostrPubKey); investorProject.SignaturesInfo!.TimeOfSignatureRequest = investmentSigsRequest.eventTime; investorProject.SignaturesInfo!.SignatureRequestEventId = investmentSigsRequest.eventId; investorProject.InvestorNPub = NostrPrivateKey.FromHex(nostrPrivateKeyHex).DerivePublicKey().Hex; + investorProject.UnfundedReleaseAddress = releaseAddress; storage.AddInvestmentProject(investorProject); @@ -1030,7 +1023,14 @@ else { ProjectIdentifiers = storage.GetInvestmentProjects() .Where(x => x.InvestedInProject()) - .Select(x => new InvestmentState { ProjectIdentifier = x.ProjectInfo.ProjectIdentifier, investorPubKey = x.InvestorPublicKey, InvestmentTransactionHash = x.TransactionId }) + .Select(x => new InvestmentState + { + ProjectIdentifier = x.ProjectInfo.ProjectIdentifier, + investorPubKey = x.InvestorPublicKey, + InvestmentTransactionHash = x.TransactionId, + ReleaseAddress = x.UnfundedReleaseAddress, + + }) .ToList() }; @@ -1048,11 +1048,6 @@ else public class InvestmentModel { - /// - /// Amount in satoshis converted on demand. - /// - public long InvestmentAmount => Money.Coins(InvestmentAmountBtc).Satoshi; - /// /// Amount in BTC, this is a parameter that is bind to the users input, /// so it makes sense to keep it in btc format (instead of satoshis) @@ -1065,7 +1060,7 @@ else public class StageBreakdown { public int StageNumber { get; set; } - public decimal Amount { get; set; } + public decimal AmountBtc { get; set; } public DateTime StageDateTime { get; set; } public int DaysFromStartDate { get; set; } } @@ -1090,7 +1085,7 @@ else Stages = StagesBreakdown.Select(stage => new { StageNumber = stage.StageNumber, - Amount = stage.Amount, + Amount = stage.AmountBtc, ReleaseDate = stage.StageDateTime.ToString("dd/MM/yyyy"), DaysFromStart = stage.DaysFromStartDate, AmountToRelease = project.ProjectInfo.Stages[stage.StageNumber - 1].AmountToRelease diff --git a/src/Angor/Client/Pages/Investor.razor b/src/Angor/Client/Pages/Investor.razor index f465b9b1c..4aa9b3130 100644 --- a/src/Angor/Client/Pages/Investor.razor +++ b/src/Angor/Client/Pages/Investor.razor @@ -143,11 +143,10 @@ Stats.TryGetValue(project.ProjectInfo.ProjectIdentifier, out var stats); var nostrPubKey = project.ProjectInfo.NostrPubKey; investmentRequestsMap.TryGetValue(nostrPubKey, out bool hasInvestmentRequests); + releaseRequestsMap.TryGetValue(nostrPubKey, out bool hasInvestmentReleaseRequests);
- -
@@ -162,7 +161,7 @@
Funding Target: - @project.ProjectInfo.TargetAmount @network.CoinTicker + @Money.Satoshis(project.ProjectInfo.TargetAmount).ToUnit(MoneyUnit.BTC) @network.CoinTicker
@@ -172,7 +171,7 @@
Raised (% Target): - @((stats?.AmountInvested ?? 0) * 100 / Money.Coins(project.ProjectInfo.TargetAmount).Satoshi) % + @((stats?.AmountInvested ?? 0) * 100 / project.ProjectInfo.TargetAmount) %
@@ -230,6 +229,24 @@ Pending }
+ +
+ Founder Released Funds + @if (hasInvestmentReleaseRequests) + { + @if (project.UnfundedReleaseTransactionId == null) + { + + Release funds + + } + else + { + Funds released + } + } +
+