Skip to content

Commit

Permalink
Notify investor signature signed (#129)
Browse files Browse the repository at this point in the history
* notify investor of refresh

* add nostr pubkey to storage

* Update Invest.razor

* fix for merge

* fix tests, pr go-over

* Update Investor.razor

* Some fixes to the Investor page

---------

Co-authored-by: dangershony <[email protected]>
  • Loading branch information
itailiors and dangershony authored Aug 3, 2024
1 parent c7381a2 commit 1c56f0d
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 81 deletions.
46 changes: 23 additions & 23 deletions src/Angor.Test/DerivationOperationsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,36 +51,36 @@ public ScriptTDerivationOperationsTestest()
_logger = mockLogger.Object;
}

//[Fact]
//public void BuildKeys()
//{
[Fact]
public void BuildKeys()
{

// var derivationOperations = new DerivationOperations(new HdOperations(), null, _networkConfiguration.Object);
// var rootKey = CreateAngorRootKey();
// var mnemonic = new Mnemonic(Wordlist.English, WordCount.Twelve);
var derivationOperations = new DerivationOperations(new HdOperations(), _logger, _networkConfiguration.Object);
var rootKey = CreateAngorRootKey();
var mnemonic = new Mnemonic(Wordlist.English, WordCount.Twelve);


// var founderKey = derivationOperations.DeriveFounderKey(new WalletWords { Words = mnemonic.ToString() }, 1);
// var projectId = derivationOperations.DeriveProjectId(founderKey);
// var angorKey = derivationOperations.DeriveAngorKey(founderKey, rootKey);
// var script = derivationOperations.AngorKeyToScript(angorKey);
//}
var founderKey = derivationOperations.DeriveFounderKey(new WalletWords { Words = mnemonic.ToString() }, 1);
var projectId = derivationOperations.DeriveProjectId(founderKey);
var angorKey = derivationOperations.DeriveAngorKey(founderKey, rootKey);
var script = derivationOperations.AngorKeyToScript(angorKey);
}

//[Fact]
//public void BuildKeysFromExisitData()
//{
// var derivationOperations = new DerivationOperations(new HdOperations(), null, _networkConfiguration.Object);
// var rootKey = CreateAngorRootKey("area frost rapid guitar salon tower bless fly where inmate trouble daughter");
[Fact]
public void BuildKeysFromExisitData()
{
var derivationOperations = new DerivationOperations(new HdOperations(), _logger, _networkConfiguration.Object);
var rootKey = CreateAngorRootKey("area frost rapid guitar salon tower bless fly where inmate trouble daughter");


// var words = "gospel awkward uphold orchard spike elite inform danger sheriff lens power monitor";
// var founderKey = derivationOperations.DeriveFounderKey(new WalletWords { Words = words }, 1);
// var founderRecoveryKey = derivationOperations.DeriveFounderRecoveryKey(new WalletWords { Words = words }, 1);
// var projectId = derivationOperations.DeriveProjectId(founderKey);
// var angorKey = derivationOperations.DeriveAngorKey(founderKey, rootKey);
// var script = derivationOperations.AngorKeyToScript(angorKey);
var words = "gospel awkward uphold orchard spike elite inform danger sheriff lens power monitor";
var founderKey = derivationOperations.DeriveFounderKey(new WalletWords { Words = words }, 1);
var founderRecoveryKey = derivationOperations.DeriveFounderRecoveryKey(new WalletWords { Words = words }, 1);
var projectId = derivationOperations.DeriveProjectId(founderKey);
var angorKey = derivationOperations.DeriveAngorKey(founderKey, rootKey);
var script = derivationOperations.AngorKeyToScript(angorKey);

//}
}

[Fact]
public void DeriveFounderKey_InvalidMnemonicWords_ThrowsException()
Expand Down
11 changes: 9 additions & 2 deletions src/Angor/Client/Pages/Invest.razor
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@
<div class="container py-3">
<h1 class="mb-4">Investment Page</h1>
<p>Here is a small explanation of the project. You can <a href="view/@ProjectId">view more details about the project here</a>.</p>

<p>To invest in this project, the founder must sign a recovery agreement.
<br>
This agreement ensures that in the event the project does not succeed, you will be able to recover your funds.
<br>
This provides a safety net for your investment, giving you peace of mind that your financial contribution is protected.</p>


<p>ProjectId: @ProjectId</p>
<p>Target amount: @project.ProjectInfo.TargetAmount BTC</p>
<p>Starting in: @project.ProjectInfo.StartDate.ToString("dd/MM/yyyy")</p>
Expand Down Expand Up @@ -230,7 +236,7 @@
<button type="button" class="btn btn-danger" @onclick="CancelInvestment">Cancel</button>
</div>
<div class="card-body">
<p class="modal-title">The founder has signed the recovery transaction, you may now invest.</p>
<p class="modal-title">The founder has signed a transaction agreement ensuring that you can recover your funds if the project does not succeed, you may now invest.</p>
<br/>
<button type="button" class="btn btn-success" @onclick="PublishSignedTransactionAsync" disabled="@publishSpinner">
@if (publishSpinner)
Expand Down Expand Up @@ -613,6 +619,7 @@
investorProject.SignaturesInfo!.SignatureRequestEventId = investmentSigsRequest.eventId;

storage.AddInvestmentProject(investorProject);
storage.SetNostrPublicKeyPerProject(project.ProjectInfo.ProjectIdentifier, nostrPrivateKey.PubKey.ToHex()[2..]);



Expand Down
165 changes: 110 additions & 55 deletions src/Angor/Client/Pages/Investor.razor
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
@inject IRelayService _RelayService
@inject ISignService _SignService
@inject ISerializer serializer
@inject ISignService _SignService
@inject IEncryptionService _encryptionService

@inject IJSRuntime JS
Expand Down Expand Up @@ -136,61 +137,75 @@
</div>
</div>

<div class="table-responsive form-control mt-4">
<table class="table align-items-center mb-0">
<thead>


<div class="card-body">
<div class="table-responsive form-control">
<table class="table align-items-center mb-0">
<thead>
<tr>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Name</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Funding Target (@network.CoinTicker)</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Raised (@network.CoinTicker)</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Raised (% Target)</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Project Status</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">My Investment (@network.CoinTicker)</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Spent by Founder</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Available to Founder</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">In Recovery</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Founder Approval</th>
</tr>
</thead>
<tbody>

@foreach (var project in projects)
{
Stats.TryGetValue(project.ProjectInfo.ProjectIdentifier, out var stats);
var nostrPubKey = project.ProjectInfo.NostrPubKey;
investmentRequestsMap.TryGetValue(nostrPubKey, out bool hasInvestmentRequests);

<tr>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Name</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Funding Target (@network.CoinTicker)</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Raised (@network.CoinTicker)</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Raised (% Target)</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Project Status</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">My Investment (@network.CoinTicker)</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Spent by Founder</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">Available to Founder</th>
<th class="text-uppercase text-xxs font-weight-bolder opacity-7">In Recovery</th>
<td>
<a href=@($"/view/{project.ProjectInfo.ProjectIdentifier}")>@project.Metadata?.Name</a>
</td>
<td>@project.ProjectInfo.TargetAmount @network.CoinTicker</td>
<td>@Money.Satoshis(stats?.AmountInvested ?? 0).ToUnit(MoneyUnit.BTC) @network.CoinTicker </td>
<td>@((stats?.AmountInvested ?? 0) * 100 / Money.Coins(project.ProjectInfo.TargetAmount).Satoshi) %</td>
<td>
@if (project.ProjectInfo.StartDate < DateTime.UtcNow)
{
<p class="text-info">Funding</p>
}
else
{
<p class="text-success">Live</p>
}
</td>
<td>
@Money.Satoshis(project.AmountInvested ?? 0).ToUnit(MoneyUnit.BTC) @network.CoinTicker
@if (!project.SignaturesInfo?.Signatures.Any() ?? false)
{
<a href=@($"/invest/{project.ProjectInfo.ProjectIdentifier}") class="btn btn-link" data-toggle="tooltip" title="Pending"> <i class="oi oi-clock"></i></a>
}
</td>
<td>-</td>
<td>-</td>
<td>@Money.Satoshis(project.AmountInRecovery ?? 0).ToUnit(MoneyUnit.BTC) @network.CoinTicker</td>
<td>
@if (hasInvestmentRequests)
{
<a href="@($"/invest/{project.ProjectInfo.ProjectIdentifier}")" class="text-info">Approved</a>
}

</td>
</tr>
</thead>
<tbody>

@foreach (var project in projects)
{
Stats.TryGetValue(project.ProjectInfo.ProjectIdentifier, out var stats);

<tr>
<td>
<a href=@($"/view/{project.ProjectInfo.ProjectIdentifier}")>@project.Metadata?.Name</a>
</td>
<td>@project.ProjectInfo.TargetAmount @network.CoinTicker</td>
<td>@Money.Satoshis(stats?.AmountInvested ?? 0).ToUnit(MoneyUnit.BTC) @network.CoinTicker </td>
<td>@((stats?.AmountInvested ?? 0) * 100 / Money.Coins(project.ProjectInfo.TargetAmount).Satoshi) %</td>
<td>
@if (project.ProjectInfo.StartDate < DateTime.UtcNow)
{
<span class="text-info">Funding</span>
}
else
{
<span class="text-success">Live</span>
}
</td>
<td>
@Money.Satoshis(project.AmountInvested ?? 0).ToUnit(MoneyUnit.BTC) @network.CoinTicker
@if (!project.SignaturesInfo?.Signatures.Any() ?? false)
{
<a href=@($"/invest/{project.ProjectInfo.ProjectIdentifier}") class="btn btn-link" data-toggle="tooltip" title="Pending"> <i class="oi oi-clock"></i></a>
}
</td>
<td>-</td>
<td>-</td>
<td>@Money.Satoshis(project.AmountInRecovery ?? 0).ToUnit(MoneyUnit.BTC) @network.CoinTicker</td>
</tr>
}
</tbody>
</table>
</div>
}
</tbody>
</table>
</div>

</div>
</div>

</div>

</div>
Expand All @@ -209,6 +224,8 @@
long TotalWallet = 0;
int TotalFundedProjects = 0;
long TotalInRecovery = 0;

private Dictionary<string, bool> investmentRequestsMap = new Dictionary<string, bool>();

public Dictionary<string, ProjectStats> Stats = new();

Expand All @@ -234,6 +251,7 @@
TotalInRecovery = projects.Sum(s => s.AmountInRecovery ?? 0);

await RefreshBalance();
await checkSignatureFromFounder();
}
}

Expand Down Expand Up @@ -277,9 +295,38 @@
}

}

private async Task HandleSignatureReceivedAsync(string nostrPubKey, string signatureContent)
{
if (investmentRequestsMap.ContainsKey(nostrPubKey))
{
investmentRequestsMap[nostrPubKey] = true;
StateHasChanged();
}
}

private async Task checkSignatureFromFounder()
{
foreach (var project in projects)
{
investmentRequestsMap[project.ProjectInfo.NostrPubKey] = false;

var investorNostrPubKey = storage.GetNostrPublicKeyPerProject(project.ProjectInfo.ProjectIdentifier);

if (!string.IsNullOrEmpty(investorNostrPubKey))
{
_SignService.LookupSignatureForInvestmentRequest(
investorNostrPubKey,
project.ProjectInfo.NostrPubKey,
project.SignaturesInfo.TimeOfSignatureRequest.Value,
project.SignaturesInfo.SignatureRequestEventId,
async signatureContent => await HandleSignatureReceivedAsync(project.ProjectInfo.NostrPubKey, signatureContent)
);
}
}
}


private async Task RefreshBalance()
{
try
Expand Down Expand Up @@ -311,11 +358,15 @@
RefreshBalanceTriggered = false;
return;
}


RefreshBalanceTriggered = true;
var words = await passwordComponent.GetWalletAsync();
var NostrDMPrivateKey = await _DerivationOperations.DeriveProjectNostrPrivateKeyAsync(words, 1);
var NostrDMPrivateKeyHex = Encoders.Hex.EncodeData(NostrDMPrivateKey.ToBytes());

var NostrDMPubkey = _DerivationOperations.DeriveNostrPubKey(words, 1);

await checkSignatureFromFounder();

var rootNostrPubKeyHex = _DerivationOperations.DeriveNostrPubKey(words, 0);


Expand Down Expand Up @@ -431,6 +482,10 @@
{
projects.Add(investorProject);
storage.AddInvestmentProject(investorProject);

// todo: David to check this, not sure this is correct.
// storage.SetNostrPublicKeyPerProject(investorProject.ProjectInfo.ProjectIdentifier, investorNostrPrivateKey.PubKey.ToHex()[2..]);
RefreshBalanceTriggered = false;
StateHasChanged();
}
Expand Down
10 changes: 10 additions & 0 deletions src/Angor/Client/Storage/ClientStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,14 @@ public string GetNetwork()
{
return _storage.GetItem<string>("network");
}

public void SetNostrPublicKeyPerProject(string projectId,string nostrPubKey)
{
_storage.SetItem($"project:{projectId}:nostrKey", nostrPubKey);
}

public string GetNostrPublicKeyPerProject(string projectId)
{
return _storage.GetItem<string>($"project:{projectId}:nostrKey");
}
}
3 changes: 2 additions & 1 deletion src/Angor/Client/Storage/IClientStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public interface IClientStorage
// void RemoveSignatures(SignatureInfo signatureInfo);
// List<SignatureInfo> GetSignatures();
// void DeleteSignatures();

void SetNostrPublicKeyPerProject(string projectId, string nostrPubKey);
string GetNostrPublicKeyPerProject(string projectId);

SettingsInfo GetSettingsInfo();
void SetSettingsInfo(SettingsInfo settingsInfo);
Expand Down

0 comments on commit 1c56f0d

Please sign in to comment.