From 4c33d1d10736581185ba60e37fa3d8c82794e212 Mon Sep 17 00:00:00 2001 From: dangershony Date: Wed, 18 Dec 2024 23:43:22 +0000 Subject: [PATCH 01/16] Put a limit to founder if amount was not reached --- src/Angor/Client/Pages/Spend.razor | 310 +++++++++++++++-------------- src/Angor/Client/Pages/View.razor | 5 +- 2 files changed, 168 insertions(+), 147 deletions(-) diff --git a/src/Angor/Client/Pages/Spend.razor b/src/Angor/Client/Pages/Spend.razor index d33848c0..2c730ad5 100644 --- a/src/Angor/Client/Pages/Spend.razor +++ b/src/Angor/Client/Pages/Spend.razor @@ -64,184 +64,191 @@ +@if (firstTimeRefreshSpinner && refreshSpinner) +{ +
+
+
+} - @if (firstTimeRefreshSpinner && refreshSpinner) - { -
-
-
- } - +@if (!targetInvestmentReached && projectDateStarted) +{ + +} - @foreach (var stage in stageDatas) - { - bool stageisActive = stage.Stage.ReleaseDate < DateTime.UtcNow; - var investedCount = stage.Items.Count(c => c.IsSpent == false); - var investedAmount = stage.Items.Where(c => c.IsSpent == false).Sum(c => c.Amount); - bool noCoinsToClaim = investedCount == 0 || stage.StageSpinner == true; +@foreach (var stage in stageDatas) +{ + bool stageisActive = stage.Stage.ReleaseDate < DateTime.UtcNow; + var investedCount = stage.Items.Count(c => c.IsSpent == false); + var investedAmount = stage.Items.Where(c => c.IsSpent == false).Sum(c => c.Amount); + bool noCoinsToClaim = investedCount == 0 || stage.StageSpinner == true; -
-
-
-
Stage @stage.StageIndex (@stage.Stage.AmountToRelease %) - @investedAmount @network.CoinTicker (@investedCount trx)
- @if (stageisActive) - { +
+
+
+
Stage @stage.StageIndex (@stage.Stage.AmountToRelease %) - @investedAmount @network.CoinTicker (@investedCount trx)
+ @if (stageisActive) + { - - } - else - { - - } + } + + } + else + { + + } -
-

Estimated completion time: @stage.Stage.ReleaseDate.ToString("dd/MM/yyyy")

+
+

Estimated completion time: @stage.Stage.ReleaseDate.ToString("dd/MM/yyyy")

- + - @if (stage.StageIndex == expandedStageId) - { -
- @foreach (var transaction in stage.Items) - { - bool isTicked = IsUtxoSelected(transaction.Trxid, transaction.Outputindex); - string statusClass = transaction.IsSpent ? "bg-warning text-dark" : "bg-success text-light"; - string statusText = transaction.IsSpent ? "Spent" : "Unspent"; - -
- - -
- } + @if (stage.StageIndex == expandedStageId) + { +
+ @foreach (var transaction in stage.Items) + { + bool isTicked = IsUtxoSelected(transaction.Trxid, transaction.Outputindex); + string statusClass = transaction.IsSpent ? "bg-warning text-dark" : "bg-success text-light"; + string statusText = transaction.IsSpent ? "Spent" : "Unspent"; + +
+ + +
+ } -
- } -
+
+ }
- } +
+} - @if (showCreateModal) - { - - +
+ + } } @@ -138,7 +274,7 @@ else public SignatureInfo? InvestorReleaseSigInfo; - private List signaturesRequests = new(); + private List signaturesReleaseItems = new(); private FeeData feeData = new(); @@ -146,13 +282,16 @@ else bool messagesReceived; bool scanedForApprovals; + bool scanedReleaseApprovals; bool releaseCoinsSpinner = false; bool prepareToReleaseCoinsConfirmSpinner = false; private Transaction? unsignedReleaseTransaction; private TransactionInfo? releaseTransaction; - private bool CanApproveAllSignatures => signaturesRequests != null && signaturesRequests.Any(s => s.SignRecoveryRequest?.InvestmentTransactionHex != null && s.AmountToInvest != null && s.TimeApproved == null); + ProjectStats? projectStats; + + private bool CanApproveAllSignatures => signaturesReleaseItems != null && signaturesReleaseItems.Any(s => s.TimeApproved == null); private string ApproveButtonClass => CanApproveAllSignatures ? "btn-border-success" : "btn-border"; @@ -183,14 +322,7 @@ else } } - if (IsFounder) - { - await FetchPendingSignatures(FounderProject); - } - else - { - await FetchInvestorCheckPassword(); - } + projectStats = await _IndexerService.GetProjectStatsAsync(ProjectIdentifier); } } @@ -198,7 +330,10 @@ else { if (IsFounder) { - await FetchSignaturesCheckPassword(); + if (FounderProject!.ProjectDateStarted()) + { + await RefreshSignaturesInternal(); + } } else { @@ -206,20 +341,27 @@ else } } + public bool TargetInvestmentReached() + { + if (projectStats != null) + { + return projectStats?.AmountInvested >= Money.Coins(FounderProject.ProjectInfo.TargetAmount).Satoshi; + } + + return FounderProject.TargetInvestmentReached(); + } + // ==== code for the investor ==== protected async Task FetchInvestorCheckPassword() { - if (signaturesRequests.Any(x => x.AmountToInvest == null)) + if (passwordComponent.HasPassword()) { - if (passwordComponent.HasPassword()) - { - await FetchInvestorReleaseSignatures(); - } - else - { - passwordComponent.ShowPassword(FetchInvestorReleaseSignatures); - } + await FetchInvestorReleaseSignatures(); + } + else + { + passwordComponent.ShowPassword(FetchInvestorReleaseSignatures); } } @@ -230,15 +372,15 @@ else InvestorProject.ProjectInfo.NostrPubKey, null, InvestorProject.SignaturesInfo.SignatureRequestEventId, - signatureContent => HandleReleaseSignatureReceivedAsync(InvestorProject.ProjectInfo.NostrPubKey, signatureContent) + signatureContent => HandleReleaseInvestorSignatureReceivedAsync(InvestorProject.ProjectInfo.NostrPubKey, signatureContent) ); } - private async Task HandleReleaseSignatureReceivedAsync(string nostrPubKey, string signatureContent) + private async Task HandleReleaseInvestorSignatureReceivedAsync(string nostrPubKey, string signatureContent) { if (!passwordComponent.HasPassword()) { - passwordComponent.ShowPassword(() => HandleReleaseSignatureReceivedAsync(nostrPubKey, signatureContent)); + passwordComponent.ShowPassword(() => HandleReleaseInvestorSignatureReceivedAsync(nostrPubKey, signatureContent)); return; } @@ -333,7 +475,7 @@ else } } - private async Task ReleaseFeeRangeChanged(ChangeEventArgs e) + private async Task ReleaseInvestorFeeRangeChanged(ChangeEventArgs e) { var selected = e.Value?.ToString(); @@ -364,7 +506,7 @@ else } } - private async Task ReleaseCoins() + private async Task ReleaseInvestorCoins() { prepareToReleaseCoinsConfirmSpinner = true; StateHasChanged(); @@ -406,21 +548,6 @@ else // ==== code for the founder ==== - protected async Task FetchSignaturesCheckPassword() - { - if (signaturesRequests.Any(x => x.AmountToInvest == null)) - { - if (passwordComponent.HasPassword()) - { - await FetchOrDecryptSignaturesRequest(); - } - else - { - passwordComponent.ShowPassword(FetchOrDecryptSignaturesRequest); - } - } - } - private async Task RefreshSignaturesInternal() { if (passwordComponent.HasPassword()) @@ -441,10 +568,18 @@ else refreshButtonSpinner = true; StateHasChanged(); + if (!passwordComponent.HasPassword()) + { + notificationComponent.ShowErrorMessage("no wallet password"); + return; + } + try { - await FetchPendingSignatures(FounderProject); - await FetchOrDecryptSignaturesRequest(); + await FetchAndDecryptSignatureRequests(); + + projectStats = await _IndexerService.GetProjectStatsAsync(ProjectIdentifier); + await Task.Delay(1000); } catch (Exception e) @@ -459,82 +594,18 @@ else } } - protected async Task FetchOrDecryptSignaturesRequest() + private async Task FetchAndDecryptSignatureRequests() { - Logger.LogDebug($"handled = {signaturesRequests.Count(x => x.AmountToInvest.HasValue)}, total = {signaturesRequests.Count}"); - - if (signaturesRequests.Any(x => x.AmountToInvest == null)) - { - if (!scanedForApprovals) - { - FetchFounderApprovalsSignatures(FounderProject); - return; - } - - if (!passwordComponent.HasPassword()) - { - notificationComponent.ShowErrorMessage("no wallet password"); - return; - } - - var words = await passwordComponent.GetWalletAsync(); - - var nostrPrivateKey = await DerivationOperations.DeriveProjectNostrPrivateKeyAsync(words, FounderProject.ProjectIndex); - - var nostrPrivateKeyHex = Encoders.Hex.EncodeData(nostrPrivateKey.ToBytes()); - - foreach (var pendingSignature in signaturesRequests.Where(_ => _.EncryptedMessage != null && _.AmountToInvest == null)) - { - try - { - var sigResJson = await encryption.DecryptNostrContentAsync( - nostrPrivateKeyHex, pendingSignature.investorNostrPubKey, pendingSignature.EncryptedMessage); - - pendingSignature.SignRecoveryRequest = serializer.Deserialize(sigResJson); - - if (pendingSignature.SignRecoveryRequest is null) - { - throw new Exception("Error deserializing signature request"); - } - - var investorTrx = _networkConfiguration.GetNetwork().CreateTransaction(pendingSignature.SignRecoveryRequest.InvestmentTransactionHex); - - pendingSignature.AmountToInvest = investorTrx.Outputs.AsIndexedOutputs().Skip(2).Take(investorTrx.Outputs.Count - 3) //Todo get the actual outputs with taproot type - .Sum(_ => _.TxOut.Value.ToUnit(MoneyUnit.BTC)); - } - catch (FormatException fe) - { - Logger.LogError("Format error decrypting transaction hex: {TransactionHex}, Exception: {ExceptionMessage}", pendingSignature?.SignRecoveryRequest?.InvestmentTransactionHex, fe.Message); - pendingSignature.SignRecoveryRequest = null; - } - catch (CryptographicException ce) - { - Logger.LogError("Cryptographic error decrypting transaction hex: {TransactionHex}, Exception: {ExceptionMessage}", pendingSignature?.SignRecoveryRequest?.InvestmentTransactionHex, ce.Message); - pendingSignature.SignRecoveryRequest = null; - } - catch (Exception e) - { - Logger.LogError("Error decrypting transaction hex: {TransactionHex}, Exception: {ExceptionMessage}", pendingSignature?.SignRecoveryRequest?.InvestmentTransactionHex, e.Message); - pendingSignature.SignRecoveryRequest = null; - } - } - Logger.LogDebug($"Calling StateHasChanged in OnAfterRenderAsync"); - messagesReceived = false; - StateHasChanged(); - } - - Logger.LogDebug("OnAfterRenderAsync Completed"); - Logger.LogDebug($"Signatures retrieved: {signaturesRequests.Count}"); - } + var words = await passwordComponent.GetWalletAsync(); + var nostrPrivateKey = DerivationOperations.DeriveProjectNostrPrivateKeyAsync(words, FounderProject.ProjectIndex).Result; + var nostrPrivateKeyHex = Encoders.Hex.EncodeData(nostrPrivateKey.ToBytes()); - private async Task FetchPendingSignatures(FounderProject project) - { - await SignService.LookupInvestmentRequestsAsync(project.ProjectInfo.NostrPubKey, null, null,// project.LastRequestForSignaturesTime , async + await SignService.LookupInvestmentRequestsAsync(FounderProject.ProjectInfo.NostrPubKey, null, null, (eventId, investorNostrPubKey, encryptedMessage, timeArrived) => { Logger.LogDebug($"Sig request event received investorNostrPubKey: {investorNostrPubKey} - timeArrived: {timeArrived}"); - var sigReq = signaturesRequests.FirstOrDefault(_ => _.investorNostrPubKey == investorNostrPubKey); + var sigReq = signaturesReleaseItems.FirstOrDefault(_ => _.investorNostrPubKey == investorNostrPubKey); if (sigReq != null) { @@ -546,23 +617,43 @@ else Logger.LogDebug($"Sig request event received is replaced"); // this is a newer sig request so replace it - signaturesRequests.Remove(sigReq); + signaturesReleaseItems.Remove(sigReq); } Logger.LogDebug($"Sig request event received is new"); messagesReceived = true; - var signatureRequest = new SignatureReleaseItem + var signatureReleaseItem = new SignatureReleaseItem { investorNostrPubKey = investorNostrPubKey, TimeArrived = timeArrived, - EncryptedMessage = encryptedMessage, //To be encrypted after js interop is loaded + EncryptedSignRecoveryMessage = encryptedMessage, //To be encrypted after js interop is loaded EventId = eventId }; - signaturesRequests.Add(signatureRequest); + signaturesReleaseItems.Add(signatureReleaseItem); Logger.LogDebug($"Added to pendingSignatures"); + + // decrypt the message + + try + { + var sigResJson = encryption.DecryptNostrContentAsync( + nostrPrivateKeyHex, signatureReleaseItem.investorNostrPubKey, signatureReleaseItem.EncryptedSignRecoveryMessage).Result; + + signatureReleaseItem.SignRecoveryRequest = serializer.Deserialize(sigResJson); + + if (signatureReleaseItem.SignRecoveryRequest is null) + { + throw new Exception("Error deserializing signature request"); + } + } + catch (Exception e) + { + Logger.LogError("Error decrypting transaction hex: {TransactionHex}, Exception: {ExceptionMessage}", signatureReleaseItem?.SignRecoveryRequest?.InvestmentTransactionHex, e.Message); + signatureReleaseItem.SignRecoveryRequest = null; + } }, () => { @@ -571,21 +662,25 @@ else if (!messagesReceived) return; + // fetch all the approvals + FetchFounderApprovalsSignatures(); + FetchFounderReleaseSignatures(); + Logger.LogDebug($"Calling StateHasChanged in EOSE"); StateHasChanged(); }); } - private void FetchFounderApprovalsSignatures(FounderProject project) + private void FetchFounderApprovalsSignatures() { - SignService.LookupInvestmentRequestApprovals(project.ProjectInfo.NostrPubKey, + SignService.LookupInvestmentRequestApprovals(FounderProject.ProjectInfo.NostrPubKey, (investorNostrPubKey, timeApproved, reqEventId) => { Logger.LogDebug($"Sig response event received investorNostrPubKey: {investorNostrPubKey} - timeApproved: {timeApproved} - reqEventId: {reqEventId}"); - var signatureRequest = signaturesRequests.FirstOrDefault(_ => _.investorNostrPubKey == investorNostrPubKey); + var signatureRequest = signaturesReleaseItems.FirstOrDefault(_ => _.investorNostrPubKey == investorNostrPubKey); - if (signatureRequest is null || signatureRequest.TimeApproved != null) + if (signatureRequest is null) return; //multiple relays could mean the same massage multiple times if (signatureRequest.TimeArrived > timeApproved) @@ -610,23 +705,51 @@ else { scanedForApprovals = true; - if (signaturesRequests.Any(_ => _.TimeApproved != null)) + Logger.LogDebug($"Calling StateHasChanged in EOSE"); + StateHasChanged(); + Logger.LogDebug($"End of messages on EOSE"); + }); + } + + protected void FetchFounderReleaseSignatures() + { + SignService.LookupSignedReleaseSigs( + InvestorProject.InvestorNPub, + InvestorProject.ProjectInfo.NostrPubKey, + null, + InvestorProject.SignaturesInfo.SignatureRequestEventId, (investorNostrPubKey, timeReleased, reqEventId) => + { + Logger.LogDebug($"Sig release response event received investorNostrPubKey: {investorNostrPubKey} - timeReleased: {timeReleased} - reqEventId: {reqEventId}"); + + var signatureRequest = signaturesReleaseItems.FirstOrDefault(_ => _.investorNostrPubKey == investorNostrPubKey); + + if (signatureRequest is null) + return; //multiple relays could mean the same massage multiple times + + if (signatureRequest.TimeReleased > timeReleased) { - var latestApprovedTime = signaturesRequests - .Where(x => x.TimeApproved != null) - .Max(x => x.TimeArrived); + Logger.LogDebug($"The event received is replaced by time"); + return; // sig of an old request + } - if (FounderProject.LastRequestForSignaturesTime is null || FounderProject.LastRequestForSignaturesTime < latestApprovedTime) - { - FounderProject.LastRequestForSignaturesTime = latestApprovedTime; - Storage.UpdateFounderProject(FounderProject); - } + if (reqEventId != null && signatureRequest.EventId != reqEventId) + { + Logger.LogDebug($"The event received is replaced by eventid"); + return; // sig of an old request } - Logger.LogDebug($"Calling StateHasChanged in EOSE"); + Logger.LogDebug($"The event received is new"); + + signatureRequest.TimeReleased = timeReleased; + + Logger.LogDebug($"Added to pendingSignatures"); + }, () => + { + scanedReleaseApprovals = true; + StateHasChanged(); - Logger.LogDebug($"End of messages on EOSE"); }); + } protected async Task ApproveReleaseSignatureCheckPassword(SignatureReleaseItem signature) @@ -694,7 +817,7 @@ else try { - var releaseSignatures = signaturesRequests.Where(s => s.SignRecoveryRequest?.InvestmentTransactionHex != null && s.AmountToInvest != null && s.TimeApproved == null).ToList(); + var releaseSignatures = signaturesReleaseItems.Where(s => s.TimeReleased == null).ToList(); numOfSignatureToSign = releaseSignatures.Count; numOfSignaturesSigned = 0; @@ -767,14 +890,17 @@ else { public string investorNostrPubKey { get; set; } - public decimal? AmountToInvest { get; set; } - public DateTime TimeArrived { get; set; } public DateTime? TimeApproved { get; set; } + public DateTime? TimeReleased { get; set; } public SignRecoveryRequest? SignRecoveryRequest { get; set; } - public string? EncryptedMessage { get; set; } + public SignatureInfo? InvestorReleaseSigInfo { get; set; } + + public string? EncryptedSignRecoveryMessage { get; set; } + + public string? EncryptedSignReleaseMessage { get; set; } public string EventId { get; set; } } diff --git a/src/Angor/Client/Pages/Signatures.razor b/src/Angor/Client/Pages/Signatures.razor index d3060bc0..36203944 100644 --- a/src/Angor/Client/Pages/Signatures.razor +++ b/src/Angor/Client/Pages/Signatures.razor @@ -2,17 +2,13 @@ @using Angor.Shared @using Angor.Client.Storage @using Angor.Shared.Models -@using Angor.Client.Services @using Angor.Shared.ProtocolNew @using Angor.Client.Models @using Blockcore.NBitcoin @using Blockcore.NBitcoin.DataEncoders -@using System.Text.Json @using Angor.Shared.Services @using System.Security.Cryptography -@inject IJSRuntime JS - @inject ILogger Logger @inject IDerivationOperations DerivationOperations @inject IClientStorage Storage; @@ -95,11 +91,6 @@
Signatures
- @if (!FounderProject.TargetInvestmentReached() && FounderProject.ProjectDateStarted()) - { -
@FounderProject.ReleaseSignaturesTime.HasValue ? "release at " + FounderProject.ReleaseSignaturesTime : "not released yet"
- - }
+ } + else + { + @if (!refreshSpinner) { - - } + @if (StageInfo.CanRelease) + { +

Total funds in penalty = @StageInfo.TotalInPenalty @network.CoinTicker

+ } + + @if (StageInfo.CanRecover && !StageInfo.EndOfProject) + { + + } - @if (StageInfo.CanRelease) - { - - } + @if (StageInfo.CanRelease) + { + + } - @if (StageInfo.EndOfProject && StageInfo.TotalSpendable > 0) - { - + @if (StageInfo.EndOfProject && StageInfo.TotalSpendable > 0) + { + + } } }
@@ -364,6 +371,8 @@ private TransactionInfo? releaseRecoveryTransaction; private TransactionInfo? endOfProjectTransaction; + private bool releaseSigsfound = false; + StageData StageInfo = new(); string explorerLink; @@ -423,6 +432,8 @@ try { + CheckReleaseSignatures(); + await FindInvestments(); await CheckSpentFund(); @@ -441,6 +452,21 @@ await Task.Delay(10); } + protected void CheckReleaseSignatures() + { + SignService.LookupReleaseSigs( + project.InvestorNPub, + project.ProjectInfo.NostrPubKey, + null, + project.SignaturesInfo?.SignatureRequestEventId!, + _ => + { + releaseSigsfound = true; + StateHasChanged(); + return Task.CompletedTask; + }); + } + private async Task FindInvestments() { if (StageInfo.TransactionInfo != null) @@ -1003,4 +1029,10 @@ passwordComponent.ClearPassword(); } } + + private void NavigatetoReleaseSigs() + { + NavigationManager.NavigateTo($"/release/{ProjectId}"); + } + } diff --git a/src/Angor/Client/Pages/Release.razor b/src/Angor/Client/Pages/Release.razor index a05e128b..658ea25b 100644 --- a/src/Angor/Client/Pages/Release.razor +++ b/src/Angor/Client/Pages/Release.razor @@ -125,141 +125,114 @@ else { // handle founder - @if (!signaturesReleaseItems.Any()) - { -
-
-
- - - - No pending signatures yet... -
-
-
- } - else - { - -
-
+
+
+ + @if (!FounderProject.ProjectDateStarted()) + { +
+
- @if (!FounderProject.ProjectDateStarted()) +
Project didn't start yet
+
+
+ } + else + { + @if (TargetInvestmentReached()) {
-
Project didn't start yet
+
Target reached
} else { - @if (TargetInvestmentReached()) - { -
-
- -
Target reached
-
-
- } - else - { - @if (messagesReceived || isLoading) - { -
-
- @if (isLoading) - { -
Creating Release Signatures... @numOfSignaturesSigned / @numOfSignatureToSign
- } -
+
+
+
+
+
+
Signatures
+
+
+ + +
- } - else - { -
-
-
-
-
-
Signatures
-
-
- @if (!FounderProject.TargetInvestmentReached() && FounderProject.ProjectDateStarted()) +
+ + +
+ + + + + + + + + + @foreach (var signature in signaturesReleaseItems.Where(_ => _.SignRecoveryRequest?.InvestmentTransactionHex != null)) + { + + + + @if (signature.TimeReleased is null) { -
@FounderProject.ReleaseSignaturesTime.HasValue ? "release at " + FounderProject.ReleaseSignaturesTime : "not released yet"
- + } - - - - -
- - -
-
Time ArrivedTime ApprovedTime Released
@signature.TimeArrived.ToString("g")@signature.TimeApproved?.ToString("g") + +
- - - - - - - - - @foreach (var signature in signaturesReleaseItems.Where(_ => _ is { SignRecoveryRequest.InvestmentTransactionHex: not null })) + else { - - - @if (signature.TimeApproved is null) - { - - } - else - { - - } - + } - -
Investment amountReceived atStatus
@signature.TimeArrived.ToString("g") - - Approved on - @signature.TimeApproved.ToString()
@signature.TimeReleased.ToString()
-
-
+ + } + +
- - } - } +
+
} -
+ }
- - } +
} @@ -278,9 +251,8 @@ else private FeeData feeData = new(); - private Dictionary signaturesRequestsApproving = new(); + private Dictionary signaturesReleaseApproving = new(); - bool messagesReceived; bool scanedForApprovals; bool scanedReleaseApprovals; @@ -291,16 +263,24 @@ else ProjectStats? projectStats; - private bool CanApproveAllSignatures => signaturesReleaseItems != null && signaturesReleaseItems.Any(s => s.TimeApproved == null); + private bool CanApproveAllSignatures => signaturesReleaseItems?.Any(s => s.TimeReleased == null) ?? false; private string ApproveButtonClass => CanApproveAllSignatures ? "btn-border-success" : "btn-border"; private bool ApproveButtonDisabled => !CanApproveAllSignatures; + private bool releasingAll = false; private bool showReleaseModal = false; - private bool isLoading = false; + private bool receivingMessages = false; private bool refreshButtonSpinner = false; - private int numOfSignatureToSign = 0; - private int numOfSignaturesSigned = 0; + + private bool isRefreshing + { + get + { + return refreshButtonSpinner == true || receivingMessages == true; + + } + } protected override async Task OnInitializedAsync() { @@ -323,31 +303,45 @@ else } projectStats = await _IndexerService.GetProjectStatsAsync(ProjectIdentifier); + + Logger.LogInformation($"Fetching project stats stats AmountInvested = {projectStats?.AmountInvested} InvestorCount = {projectStats?.InvestorCount}"); } } protected override async Task OnAfterRenderAsync(bool firstRender) { - if (IsFounder) + if (firstRender) { - if (FounderProject!.ProjectDateStarted()) + if (hasWallet) { - await RefreshSignaturesInternal(); + if (IsFounder) + { + if (FounderProject!.ProjectDateStarted()) + { + await RefreshSignaturesInternal(); + } + } + else + { + await FetchInvestorCheckPassword(); + } } } - else - { - await FetchInvestorCheckPassword(); - } } public bool TargetInvestmentReached() { if (projectStats != null) { - return projectStats?.AmountInvested >= Money.Coins(FounderProject.ProjectInfo.TargetAmount).Satoshi; + var targetReached = projectStats?.AmountInvested >= Money.Coins(FounderProject.ProjectInfo.TargetAmount).Satoshi; + + Logger.LogInformation($"Indexer stats for target reached = {targetReached} AmountInvested = {projectStats?.AmountInvested} TargetAmount = {Money.Coins(FounderProject.ProjectInfo.TargetAmount).Satoshi}"); + + return targetReached; } + Logger.LogInformation($"None indexer stats for target reached = {FounderProject.TargetInvestmentReached()} AmountInvested = {FounderProject.TotalAvailableInvestedAmount} TargetAmount = {Money.Coins(FounderProject.ProjectInfo.TargetAmount).Satoshi}"); + return FounderProject.TargetInvestmentReached(); } @@ -366,45 +360,46 @@ else } protected async Task FetchInvestorReleaseSignatures() - { - SignService.LookupReleaseSigs( - InvestorProject.InvestorNPub, - InvestorProject.ProjectInfo.NostrPubKey, - null, - InvestorProject.SignaturesInfo.SignatureRequestEventId, - signatureContent => HandleReleaseInvestorSignatureReceivedAsync(InvestorProject.ProjectInfo.NostrPubKey, signatureContent) - ); - } - - private async Task HandleReleaseInvestorSignatureReceivedAsync(string nostrPubKey, string signatureContent) { if (!passwordComponent.HasPassword()) { - passwordComponent.ShowPassword(() => HandleReleaseInvestorSignatureReceivedAsync(nostrPubKey, signatureContent)); + notificationComponent.ShowErrorMessage("no wallet password"); return; } var words = await passwordComponent.GetWalletAsync(); - var nostrPrivateKey = await DerivationOperations.DeriveProjectNostrPrivateKeyAsync(words, FounderProject.ProjectIndex); - var nostrPrivateKeyHex = Encoders.Hex.EncodeData(nostrPrivateKey.ToBytes()); - var signatureJson = await encryption.DecryptNostrContentAsync( - nostrPrivateKeyHex, InvestorProject.ProjectInfo.NostrPubKey, signatureContent); + SignService.LookupReleaseSigs( + InvestorProject.InvestorNPub, + InvestorProject.ProjectInfo.NostrPubKey, + null, + InvestorProject.SignaturesInfo.SignatureRequestEventId, + signatureContent => + { + InvokeAsync(async () => + { + var signatureJson = await encryption.DecryptNostrContentAsync( + nostrPrivateKeyHex, InvestorProject.ProjectInfo.NostrPubKey, signatureContent); + + Logger.LogInformation("signature : " + signatureJson); - Logger.LogInformation("signature : " + signatureJson); + InvestorReleaseSigInfo = serializer.Deserialize(signatureJson); - InvestorReleaseSigInfo = serializer.Deserialize(signatureJson); + if (InvestorReleaseSigInfo?.SignatureType != "release") + { + notificationComponent.ShowErrorMessage("Incorrect signatures received"); + Logger.LogError("Incorrect signatures received"); + InvestorReleaseSigInfo = null; + } - if (InvestorReleaseSigInfo?.SignatureType != "release") - { - notificationComponent.ShowErrorMessage("Incorrect signatures received"); - Logger.LogError("Incorrect signatures received"); - InvestorReleaseSigInfo = null; - } + StateHasChanged(); - StateHasChanged(); + }); + + return Task.CompletedTask; + }); } private async Task ClaimInvestorCoinsPasswordAsync() @@ -576,10 +571,12 @@ else try { - await FetchAndDecryptSignatureRequests(); + await FetchSignatureRequests(); projectStats = await _IndexerService.GetProjectStatsAsync(ProjectIdentifier); + Logger.LogInformation($"Fetching project stats stats AmountInvested = {projectStats?.AmountInvested} InvestorCount = {projectStats?.InvestorCount}"); + await Task.Delay(1000); } catch (Exception e) @@ -594,12 +591,14 @@ else } } - private async Task FetchAndDecryptSignatureRequests() + private async Task FetchSignatureRequests() { var words = await passwordComponent.GetWalletAsync(); - var nostrPrivateKey = DerivationOperations.DeriveProjectNostrPrivateKeyAsync(words, FounderProject.ProjectIndex).Result; + var nostrPrivateKey = await DerivationOperations.DeriveProjectNostrPrivateKeyAsync(words, FounderProject.ProjectIndex); var nostrPrivateKeyHex = Encoders.Hex.EncodeData(nostrPrivateKey.ToBytes()); + receivingMessages = true; + await SignService.LookupInvestmentRequestsAsync(FounderProject.ProjectInfo.NostrPubKey, null, null, (eventId, investorNostrPubKey, encryptedMessage, timeArrived) => { @@ -622,8 +621,6 @@ else Logger.LogDebug($"Sig request event received is new"); - messagesReceived = true; - var signatureReleaseItem = new SignatureReleaseItem { investorNostrPubKey = investorNostrPubKey, @@ -634,56 +631,70 @@ else signaturesReleaseItems.Add(signatureReleaseItem); Logger.LogDebug($"Added to pendingSignatures"); - - // decrypt the message - - try - { - var sigResJson = encryption.DecryptNostrContentAsync( - nostrPrivateKeyHex, signatureReleaseItem.investorNostrPubKey, signatureReleaseItem.EncryptedSignRecoveryMessage).Result; - - signatureReleaseItem.SignRecoveryRequest = serializer.Deserialize(sigResJson); - - if (signatureReleaseItem.SignRecoveryRequest is null) - { - throw new Exception("Error deserializing signature request"); - } - } - catch (Exception e) - { - Logger.LogError("Error decrypting transaction hex: {TransactionHex}, Exception: {ExceptionMessage}", signatureReleaseItem?.SignRecoveryRequest?.InvestmentTransactionHex, e.Message); - signatureReleaseItem.SignRecoveryRequest = null; - } }, () => { Logger.LogDebug($"End of messages"); - if (!messagesReceived) - return; + // we cannot directly call a method that needs to be awaited + // so we do it inside the InvokeAsync delegate. + + // decrypt the message + InvokeAsync(async () => { await DecryptMessages(nostrPrivateKeyHex); }); + + receivingMessages = false; // fetch all the approvals - FetchFounderApprovalsSignatures(); - FetchFounderReleaseSignatures(); + FetchFounderApprovalsSignatures(nostrPrivateKeyHex); + FetchFounderReleaseSignatures(nostrPrivateKeyHex); Logger.LogDebug($"Calling StateHasChanged in EOSE"); StateHasChanged(); }); } - private void FetchFounderApprovalsSignatures() + private async Task DecryptMessages(string nostrPrivateKeyHex) + { + foreach (var signatureReleaseItem in signaturesReleaseItems) + { + try + { + Logger.LogInformation($"Decrypting recovery request for key {signatureReleaseItem.investorNostrPubKey}"); + + var sigResJson = await encryption.DecryptNostrContentAsync(nostrPrivateKeyHex, signatureReleaseItem.investorNostrPubKey, signatureReleaseItem.EncryptedSignRecoveryMessage); + + signatureReleaseItem.SignRecoveryRequest = serializer.Deserialize(sigResJson); + + Logger.LogInformation($"Decrypted recovery request for key {signatureReleaseItem.investorNostrPubKey} content = {sigResJson}"); + + if (signatureReleaseItem.SignRecoveryRequest is null) + { + throw new Exception("Error deserializing signature request"); + } + } + catch (Exception e) + { + Logger.LogError("Error decrypting transaction hex: {TransactionHex}, Exception: {ExceptionMessage}", signatureReleaseItem?.SignRecoveryRequest?.InvestmentTransactionHex, e.Message); + signatureReleaseItem.SignRecoveryRequest = null; + } + } + + StateHasChanged(); + } + + private void FetchFounderApprovalsSignatures(string nostrPrivateKeyHex) { SignService.LookupInvestmentRequestApprovals(FounderProject.ProjectInfo.NostrPubKey, - (investorNostrPubKey, timeApproved, reqEventId) => + (investorNostrPubKey, timeEventCreated, reqEventId) => { - Logger.LogDebug($"Sig response event received investorNostrPubKey: {investorNostrPubKey} - timeApproved: {timeApproved} - reqEventId: {reqEventId}"); + Logger.LogDebug($"Sig response event received investorNostrPubKey: {investorNostrPubKey} - timeApproved: {timeEventCreated} - reqEventId: {reqEventId}"); var signatureRequest = signaturesReleaseItems.FirstOrDefault(_ => _.investorNostrPubKey == investorNostrPubKey); if (signatureRequest is null) - return; //multiple relays could mean the same massage multiple times + return; // ignore it could be a fake message - if (signatureRequest.TimeArrived > timeApproved) + if (signatureRequest.TimeArrived > timeEventCreated) { Logger.LogDebug($"The event received is replaced by time"); return; // sig of an old request @@ -697,7 +708,7 @@ else Logger.LogDebug($"The event received is new"); - signatureRequest.TimeApproved = timeApproved; + signatureRequest.TimeApproved = timeEventCreated; Logger.LogDebug($"Added to pendingSignatures"); }, @@ -711,28 +722,25 @@ else }); } - protected void FetchFounderReleaseSignatures() + protected void FetchFounderReleaseSignatures(string nostrPrivateKeyHex) { - SignService.LookupSignedReleaseSigs( - InvestorProject.InvestorNPub, - InvestorProject.ProjectInfo.NostrPubKey, - null, - InvestorProject.SignaturesInfo.SignatureRequestEventId, (investorNostrPubKey, timeReleased, reqEventId) => + SignService.LookupSignedReleaseSigs(FounderProject.ProjectInfo.NostrPubKey, + (item) => { - Logger.LogDebug($"Sig release response event received investorNostrPubKey: {investorNostrPubKey} - timeReleased: {timeReleased} - reqEventId: {reqEventId}"); + Logger.LogDebug($"Sig release response event received investorNostrPubKey: {item.ProfileIdentifier} - timeReleased: {item.EventCreatedAt} - reqEventId: {item.EventIdentifier}"); - var signatureRequest = signaturesReleaseItems.FirstOrDefault(_ => _.investorNostrPubKey == investorNostrPubKey); + var signatureRequest = signaturesReleaseItems.FirstOrDefault(_ => _.investorNostrPubKey == item.ProfileIdentifier); if (signatureRequest is null) - return; //multiple relays could mean the same massage multiple times + return; // ignore it could be a message we do not recognize - if (signatureRequest.TimeReleased > timeReleased) + if (signatureRequest.TimeReleased > item.EventCreatedAt) { Logger.LogDebug($"The event received is replaced by time"); return; // sig of an old request } - if (reqEventId != null && signatureRequest.EventId != reqEventId) + if (item.EventIdentifier != null && signatureRequest.EventId != item.EventIdentifier) { Logger.LogDebug($"The event received is replaced by eventid"); return; // sig of an old request @@ -740,16 +748,15 @@ else Logger.LogDebug($"The event received is new"); - signatureRequest.TimeReleased = timeReleased; + signatureRequest.TimeReleased = item.EventCreatedAt; - Logger.LogDebug($"Added to pendingSignatures"); + Logger.LogDebug($"signatureRequest.TimeReleased was updated to {signatureRequest.TimeReleased}"); }, () => { scanedReleaseApprovals = true; StateHasChanged(); }); - } protected async Task ApproveReleaseSignatureCheckPassword(SignatureReleaseItem signature) @@ -769,7 +776,7 @@ else private async Task ApproveReleaseSignature(SignatureReleaseItem signature) { - signaturesRequestsApproving.Add(signature, string.Empty); + signaturesReleaseApproving.Add(signature, string.Empty); StateHasChanged(); try @@ -789,7 +796,7 @@ else } finally { - signaturesRequestsApproving.Remove(signature); + signaturesReleaseApproving.Remove(signature); } StateHasChanged(); @@ -812,21 +819,18 @@ else private async Task ProcessReleaseSignatures() { - isLoading = true; + releasingAll = true; StateHasChanged(); try { var releaseSignatures = signaturesReleaseItems.Where(s => s.TimeReleased == null).ToList(); - numOfSignatureToSign = releaseSignatures.Count; - numOfSignaturesSigned = 0; var words = await passwordComponent.GetWalletAsync(); foreach (var signature in releaseSignatures) { await PerformReleaseSignature(signature, words); - numOfSignaturesSigned++; StateHasChanged(); } } @@ -836,7 +840,7 @@ else } finally { - isLoading = false; + releasingAll = false; passwordComponent.ClearPassword(); } @@ -858,9 +862,7 @@ else var encryptedContent = await encryption.EncryptNostrContentAsync( nostrPrivateKeyHex, signature.investorNostrPubKey, sigJson); - FounderProject.ReleaseSignaturesTime = SignService.SendReleaseSigsToInvestor(encryptedContent, nostrPrivateKeyHex, signature.investorNostrPubKey, signature.EventId); - - Storage.UpdateFounderProject(FounderProject); + signature.TimeReleased = SignService.SendReleaseSigsToInvestor(encryptedContent, nostrPrivateKeyHex, signature.investorNostrPubKey, signature.EventId); return new OperationResult { Success = true }; } @@ -896,6 +898,8 @@ else public SignRecoveryRequest? SignRecoveryRequest { get; set; } + public SignatureInfo? InvestorRecoverySigInfo { get; set; } + public SignatureInfo? InvestorReleaseSigInfo { get; set; } public string? EncryptedSignRecoveryMessage { get; set; } diff --git a/src/Angor/Client/Pages/Spend.razor b/src/Angor/Client/Pages/Spend.razor index 23335bf6..214d3173 100644 --- a/src/Angor/Client/Pages/Spend.razor +++ b/src/Angor/Client/Pages/Spend.razor @@ -990,6 +990,6 @@ private void ReleaseFundsToInvestors(MouseEventArgs e) { - NavigationManager.NavigateTo($"/signatures/{ProjectId}"); + NavigationManager.NavigateTo($"/release/{ProjectId}"); } } \ No newline at end of file diff --git a/src/Angor/Shared/Services/ISignService.cs b/src/Angor/Shared/Services/ISignService.cs index 17ea67d7..66b4ee80 100644 --- a/src/Angor/Shared/Services/ISignService.cs +++ b/src/Angor/Shared/Services/ISignService.cs @@ -20,5 +20,5 @@ DateTime SendSignaturesToInvestor(string encryptedSignatureInfo, string nostrPri void LookupReleaseSigs(string investorNostrPubKey, string projectNostrPubKey, DateTime? releaseRequestSentTime, string releaseRequestEventId, Func action); - void LookupSignedReleaseSigs(string investorNostrPubKey, string projectNostrPubKey, DateTime? releaseRequestSentTime, string releaseRequestEventId, Action action, Action onAllMessagesReceived); + void LookupSignedReleaseSigs(string projectNostrPubKey, Action action, Action onAllMessagesReceived); } \ No newline at end of file diff --git a/src/Angor/Shared/Services/NostrCommunicationFactory.cs b/src/Angor/Shared/Services/NostrCommunicationFactory.cs index 3e3be8be..96037620 100644 --- a/src/Angor/Shared/Services/NostrCommunicationFactory.cs +++ b/src/Angor/Shared/Services/NostrCommunicationFactory.cs @@ -45,7 +45,7 @@ public INostrClient GetOrCreateClient(INetworkService networkService) _serviceSubscriptions.Add(_nostrMultiWebsocketClient.Streams.EventStream .Where(_ => _.Event?.AdditionalData?.Any() ?? false).Subscribe(_ => - _logger.LogInformation( + _logger.LogDebug( $"EventStream {_.Subscription} {_.Event?.Id} {_.Event?.AdditionalData}"))); _serviceSubscriptions.Add(_nostrMultiWebsocketClient.Streams.NoticeStream.Subscribe(_ => @@ -68,21 +68,21 @@ private void ConnectToAllRelaysInTheSettings(INetworkService networkService) _serviceSubscriptions.Add(client.Streams.EoseStream.Subscribe(x => { - _logger.LogInformation($"{x.CommunicatorName} EOSE {x.Subscription}"); + _logger.LogDebug($"{x.CommunicatorName} EOSE {x.Subscription}"); if (_eoseCalledOnSubscriptionClients.TryGetValue(x.Subscription ?? string.Empty, out var clientsReceivedList)) { - _logger.LogInformation($"EOSE {x.Subscription} adding {x.CommunicatorName}"); + _logger.LogDebug($"EOSE {x.Subscription} adding {x.CommunicatorName}"); clientsReceivedList.Add(x.CommunicatorName); } })); _serviceSubscriptions.Add(client.Streams.OkStream.Subscribe(x => { - _logger.LogInformation($"{x.CommunicatorName} OK {x.EventId} {x.Accepted}"); + _logger.LogDebug($"{x.CommunicatorName} OK {x.EventId} {x.Accepted}"); if (_okCalledOnSubscriptionClients.TryGetValue(x.EventId ?? string.Empty, out var clientsReceivedList)) { - _logger.LogInformation($"OK {x.EventId} adding {x.CommunicatorName}"); + _logger.LogDebug($"OK {x.EventId} adding {x.CommunicatorName}"); clientsReceivedList.Add(x.CommunicatorName); } })); @@ -103,26 +103,26 @@ public bool EoseEventReceivedOnAllRelays(string subscription) if (!_eoseCalledOnSubscriptionClients.ContainsKey(subscription)) return true; //If not monitoring than no need to block - _logger.LogInformation($"Checking for all Eose on monitored subscription {subscription}"); + _logger.LogDebug($"Checking for all Eose on monitored subscription {subscription}"); bool response = _nostrMultiWebsocketClient?.Clients .All(x => _eoseCalledOnSubscriptionClients[subscription].Contains(x.Communicator.Name)) ?? false; - _logger.LogInformation($"Eose on monitored subscription {subscription} received from all clients - {response}"); + _logger.LogDebug($"Eose on monitored subscription {subscription} received from all clients - {response}"); return response; } public bool MonitoringEoseReceivedOnSubscription(string subscription) { - _logger.LogInformation($"Started monitoring subscription {subscription}"); + _logger.LogDebug($"Started monitoring subscription {subscription}"); return _eoseCalledOnSubscriptionClients.TryAdd(subscription, new List()); } public void ClearEoseReceivedOnSubscriptionMonitoring(string subscription) { - _logger.LogInformation($"Stopped monitoring subscription {subscription}"); + _logger.LogDebug($"Stopped monitoring subscription {subscription}"); _eoseCalledOnSubscriptionClients.Remove(subscription); } @@ -131,26 +131,26 @@ public bool OkEventReceivedOnAllRelays(string eventId) if (!_okCalledOnSubscriptionClients.ContainsKey(eventId)) return true; //If not monitoring than no need to block - _logger.LogInformation($"Checking for all Ok on monitored subscription {eventId}"); + _logger.LogDebug($"Checking for all Ok on monitored subscription {eventId}"); bool response = _nostrMultiWebsocketClient?.Clients .All(x => _okCalledOnSubscriptionClients[eventId].Contains(x.Communicator.Name)) ?? false; - _logger.LogInformation($"Eose on monitored subscription {eventId} received from all clients - {response}"); + _logger.LogDebug($"Eose on monitored subscription {eventId} received from all clients - {response}"); return response; } public void MonitoringOkReceivedOnSubscription(string eventId) { - _logger.LogInformation($"Started monitoring event id {eventId}"); + _logger.LogDebug($"Started monitoring event id {eventId}"); _okCalledOnSubscriptionClients.Add(eventId, new List()); } public void ClearOkReceivedOnSubscriptionMonitoring(string eventId) { - _logger.LogInformation($"Started monitoring event id {eventId}"); + _logger.LogDebug($"Started monitoring event id {eventId}"); _okCalledOnSubscriptionClients.Remove(eventId); } @@ -174,7 +174,7 @@ public INostrCommunicator CreateCommunicator(string uri, string relayName) "Relay {relayName} disconnected, type: {Type}, reason: {CloseStatusDescription}", relayName, e.Type, e.CloseStatusDescription); else - _logger.LogInformation( + _logger.LogDebug( "Relay {relayName} disconnected, type: {Type}, reason: {CloseStatusDescription}", relayName, e.Type, e.CloseStatusDescription); })); @@ -183,7 +183,7 @@ public INostrCommunicator CreateCommunicator(string uri, string relayName) { _serviceSubscriptions.Add(nostrCommunicator.MessageReceived.Subscribe(e => { - _logger.LogInformation( + _logger.LogDebug( "message received on communicator {relayName} - {Text} Relay message received, type: {MessageType}", relayName, e.Text, e.MessageType); })); diff --git a/src/Angor/Shared/Services/RelaySubscriptionsHandling.cs b/src/Angor/Shared/Services/RelaySubscriptionsHandling.cs index ddd7e7de..3b6522d4 100644 --- a/src/Angor/Shared/Services/RelaySubscriptionsHandling.cs +++ b/src/Angor/Shared/Services/RelaySubscriptionsHandling.cs @@ -50,7 +50,7 @@ public bool TryAddOKAction(string eventId, Action action) public void HandleOkMessages(NostrOkResponse okResponse) { - _logger.LogInformation($"OkStream {okResponse.Accepted} message - {okResponse.Message}"); + _logger.LogDebug($"OkStream {okResponse.Accepted} message - {okResponse.Message}"); if (!OkVerificationActions.TryGetValue(okResponse?.EventId ?? string.Empty, out var action)) return; @@ -69,21 +69,21 @@ public bool TryAddEoseAction(string subscriptionName, Action action) var add = _communicationFactory.MonitoringEoseReceivedOnSubscription(subscriptionName); if (!add) - _logger.LogWarning($"Subscription {subscriptionName} is already being monitored"); + _logger.LogDebug($"Subscription {subscriptionName} is already being monitored"); return userEoseActions.TryAdd(subscriptionName,action); } public void HandleEoseMessages(NostrEoseResponse _) { - _logger.LogInformation($"EoseStream {_.Subscription} message - {_.AdditionalData}"); + _logger.LogDebug($"EoseStream {_.Subscription} message - {_.AdditionalData}"); if (!_communicationFactory.EoseEventReceivedOnAllRelays(_.Subscription)) return; if (userEoseActions.TryGetValue(_.Subscription, out var action)) { - _logger.LogInformation($"Invoking action on EOSE - {_.Subscription}"); + _logger.LogDebug($"Invoking action on EOSE - {_.Subscription}"); try { action.Invoke(); @@ -94,7 +94,7 @@ public void HandleEoseMessages(NostrEoseResponse _) } userEoseActions.Remove(_.Subscription); - _logger.LogInformation($"Removed action on EOSE for subscription - {_.Subscription}"); + _logger.LogDebug($"Removed action on EOSE for subscription - {_.Subscription}"); } _communicationFactory.ClearEoseReceivedOnSubscriptionMonitoring(_.Subscription); @@ -102,7 +102,7 @@ public void HandleEoseMessages(NostrEoseResponse _) if (!relaySubscriptions.ContainsKey(_.Subscription)) return; - _logger.LogInformation($"Disposing of subscription - {_.Subscription}"); + _logger.LogDebug($"Disposing of subscription - {_.Subscription}"); _communicationFactory .GetOrCreateClient(_networkService) @@ -110,7 +110,7 @@ public void HandleEoseMessages(NostrEoseResponse _) relaySubscriptions[_.Subscription].Dispose(); relaySubscriptions.Remove(_.Subscription); - _logger.LogInformation($"subscription disposed - {_.Subscription}"); + _logger.LogDebug($"subscription disposed - {_.Subscription}"); } public bool RelaySubscriptionAdded(string subscriptionKey) diff --git a/src/Angor/Shared/Services/SignService.cs b/src/Angor/Shared/Services/SignService.cs index eb9c8cb3..1a9cd9a3 100644 --- a/src/Angor/Shared/Services/SignService.cs +++ b/src/Angor/Shared/Services/SignService.cs @@ -190,7 +190,7 @@ public DateTime SendReleaseSigsToInvestor(string encryptedReleaseSigInfo, string public void LookupReleaseSigs(string investorNostrPubKey, string projectNostrPubKey, DateTime? releaseRequestSentTime, string releaseRequestEventId, Func action) { var nostrClient = _communicationFactory.GetOrCreateClient(_networkService); - var subscriptionKey = projectNostrPubKey + "release_sigs"; + var subscriptionKey = projectNostrPubKey.Substring(0, 20) + "rel_sigs"; if (!_subscriptionsHanding.RelaySubscriptionAdded(subscriptionKey)) { @@ -214,10 +214,10 @@ public void LookupReleaseSigs(string investorNostrPubKey, string projectNostrPub })); } - public void LookupSignedReleaseSigs(string investorNostrPubKey, string projectNostrPubKey, DateTime? releaseRequestSentTime, string releaseRequestEventId, Action action, Action onAllMessagesReceived) + public void LookupSignedReleaseSigs(string projectNostrPubKey, Action action, Action onAllMessagesReceived) { var nostrClient = _communicationFactory.GetOrCreateClient(_networkService); - var subscriptionKey = projectNostrPubKey + "release_approved_sigs"; + var subscriptionKey = projectNostrPubKey.Substring(0, 20) + "sing_sigs"; if (!_subscriptionsHanding.RelaySubscriptionAdded(subscriptionKey)) { @@ -228,7 +228,13 @@ public void LookupSignedReleaseSigs(string investorNostrPubKey, string projectNo .Select(_ => _.Event) .Subscribe(nostrEvent => { - action.Invoke(nostrEvent.Tags.FindFirstTagValue(NostrEventTag.ProfileIdentifier), nostrEvent.CreatedAt.Value, nostrEvent.Tags.FindFirstTagValue(NostrEventTag.EventIdentifier)); + action.Invoke(new SignServiceLookupItem + { + NostrEvent = nostrEvent, + ProfileIdentifier = nostrEvent.Tags.FindFirstTagValue(NostrEventTag.ProfileIdentifier), + EventCreatedAt = nostrEvent.CreatedAt.Value, + EventIdentifier = nostrEvent.Tags.FindFirstTagValue(NostrEventTag.EventIdentifier) + }); }); _subscriptionsHanding.TryAddRelaySubscription(subscriptionKey, subscription); @@ -248,4 +254,15 @@ public void CloseConnection() _subscriptionsHanding.Dispose(); } } + + public class SignServiceLookupItem + { + public DateTime EventCreatedAt { get; set; } + + public string ProfileIdentifier { get; set; } + + public string EventIdentifier { get; set; } + + public NostrEvent NostrEvent { get; set; } + } } From 487131e7ad840e3cc28f8776f5e483622294c6e1 Mon Sep 17 00:00:00 2001 From: dangershony Date: Wed, 29 Jan 2025 12:55:08 +0000 Subject: [PATCH 15/16] check if a message was already decrypted --- src/Angor/Client/Pages/Release.razor | 30 ++++++++++++++++------------ 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Angor/Client/Pages/Release.razor b/src/Angor/Client/Pages/Release.razor index 658ea25b..539b559e 100644 --- a/src/Angor/Client/Pages/Release.razor +++ b/src/Angor/Client/Pages/Release.razor @@ -378,26 +378,30 @@ else InvestorProject.SignaturesInfo.SignatureRequestEventId, signatureContent => { - InvokeAsync(async () => + if (InvestorReleaseSigInfo != null) { - var signatureJson = await encryption.DecryptNostrContentAsync( - nostrPrivateKeyHex, InvestorProject.ProjectInfo.NostrPubKey, signatureContent); + // if we already decrypted the message from another relay just ignore it - Logger.LogInformation("signature : " + signatureJson); + InvokeAsync(async () => + { + var signatureJson = await encryption.DecryptNostrContentAsync( + nostrPrivateKeyHex, InvestorProject.ProjectInfo.NostrPubKey, signatureContent); - InvestorReleaseSigInfo = serializer.Deserialize(signatureJson); + Logger.LogInformation("signature : " + signatureJson); - if (InvestorReleaseSigInfo?.SignatureType != "release") - { - notificationComponent.ShowErrorMessage("Incorrect signatures received"); - Logger.LogError("Incorrect signatures received"); - InvestorReleaseSigInfo = null; - } + InvestorReleaseSigInfo = serializer.Deserialize(signatureJson); - StateHasChanged(); + if (InvestorReleaseSigInfo?.SignatureType != "release") + { + notificationComponent.ShowErrorMessage("Incorrect signatures received"); + Logger.LogError("Incorrect signatures received"); + InvestorReleaseSigInfo = null; + } - }); + StateHasChanged(); + }); + } return Task.CompletedTask; }); } From 3fb58a1afeaef57f173836901b74ada13d923794 Mon Sep 17 00:00:00 2001 From: dangershony Date: Fri, 31 Jan 2025 18:34:10 +0000 Subject: [PATCH 16/16] flow now works end to end --- src/Angor/Client/Models/InvestorProject.cs | 10 +- src/Angor/Client/Pages/Investor.razor | 30 +- src/Angor/Client/Pages/Recover.razor | 3 +- src/Angor/Client/Pages/Release.razor | 731 ++++----------------- src/Angor/Client/Pages/Spend.razor | 2 +- src/Angor/Client/Pages/Unfunded.razor | 634 ++++++++++++++++++ src/Angor/Shared/Services/ISignService.cs | 2 +- src/Angor/Shared/Services/SignService.cs | 10 +- 8 files changed, 818 insertions(+), 604 deletions(-) create mode 100644 src/Angor/Client/Pages/Unfunded.razor diff --git a/src/Angor/Client/Models/InvestorProject.cs b/src/Angor/Client/Models/InvestorProject.cs index d78a5fc3..c1e1894e 100644 --- a/src/Angor/Client/Models/InvestorProject.cs +++ b/src/Angor/Client/Models/InvestorProject.cs @@ -18,8 +18,16 @@ 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 ReleaseAddress { get; set; } - public string ReleaseTransactionId { get; set; } + + /// + /// The trxid of an unfunded project that the investor has released the funds without a penalty + /// + public string ReleaseTransactionId { get; set; } public bool WaitingForFounderResponse() { diff --git a/src/Angor/Client/Pages/Investor.razor b/src/Angor/Client/Pages/Investor.razor index 13973368..2f7cee7e 100644 --- a/src/Angor/Client/Pages/Investor.razor +++ b/src/Angor/Client/Pages/Investor.razor @@ -229,12 +229,24 @@ Pending }
- @if (hasInvestmentReleaseRequests) - { -
- Founder Released Funds -
- } + +
+ Founder Released Funds + @if (hasInvestmentReleaseRequests) + { + @if (project.ReleaseTransactionId == null) + { + + Release funds + + } + else + { + Funds released + } + } +
+
-@if (!IsFounder) -{ - // handle investor +
+
+

Waiting for the founder to approve

+
+ +
+
+ + @if (InvestorProject.ReleaseTransactionId != null) + { +

+ Transaction ID: @InvestorProject.ReleaseTransactionId | + View on explorer +

- @if (InvestorReleaseSigInfo != null) + + } + else { - +
+ + } } +
- @if (showReleaseModal) - { -