Skip to content

Commit

Permalink
NOSTR calls to relay for signing and browsing (#12)
Browse files Browse the repository at this point in the history
* Changed the relay service to connect to NOSTR (#9)

* Added the nostr id to the create project transaction

* Started converting the relay service to communication with the relay

* Tests communicating the test relay

* Work on communication with the nostr relay

* More work on nostr, changed the subscription handling to be injected from the UI.

* Some code refactoring

* Refactoring and started work on nostr client factory

---------

Co-authored-by: TheDude <david@vsodir.onmicrosoft.com>

* Cherry picked changes to nostr service

* Changed the nostr pub key derivation and refactored the code to be DRY

* Added creating a NOSTR profile before the creation of the project

* Added the missing property on account info

* Added sending the encrypted transaction to the relay for the founder to sign

* Changing the flow to send DM to founder with request to sign the investment trx

* Added guards for empty results

* Revert "Cherry picked changes to nostr service"

This reverts commit 0c6ae62.

* Added TODO comment on the test class

---------

Co-authored-by: TheDude <david@vsodir.onmicrosoft.com>
  • Loading branch information
DavidGershony and TheDude authored Nov 15, 2023
1 parent 96e9522 commit f005464
Show file tree
Hide file tree
Showing 21 changed files with 749 additions and 261 deletions.
1 change: 1 addition & 0 deletions src/Angor.Test/Angor.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.JSInterop" Version="7.0.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="xunit" Version="2.4.2" />
Expand Down
4 changes: 3 additions & 1 deletion src/Angor/Client/Pages/Browse.razor
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
{
searchInProgress = true;
var blockchainProjects = await _IndexerService.GetProjectsAsync();

blockchainProjects = blockchainProjects.Where(_ => _.NostrPubKey != null).ToList();

var projectCreators = SessionStorage.GetProjectSubscribedList();

Expand All @@ -85,7 +87,7 @@
.Select(_ => _.NostrPubKey)
.Where(nostrPubKey => !projectCreators.Contains(nostrPubKey)));

await _RelayService.RequestProjectDataAsync<ProjectInfo>(_ =>
await _RelayService.LookupProjectsInfoByPubKeysAsync<ProjectInfo>(_ =>
{
if (!SessionStorage.IsProjectInStorageById(_.ProjectIdentifier))
SessionStorage.StoreProjectInfo(_);
Expand Down
154 changes: 139 additions & 15 deletions src/Angor/Client/Pages/Create.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
@using Angor.Shared.Services
@using Blockcore.NBitcoin
@using Blockcore.NBitcoin.DataEncoders
@using Nostr.Client.Json
@using Nostr.Client.Messages
@using Nostr.Client.Messages.Metadata

@implements IDisposable
@inherits BaseComponent
@inject IDerivationOperations _derivationOperations
@inject IWalletStorage _walletStorage;
@inject IClientStorage storage;
@inject NavigationManager NavigationManager
@inject IWalletOperations _WalletOperations
@inject INetworkConfiguration _NetworkConfiguration
@inject IRelayService _RelayService

@inject ISignService _SignService
Expand All @@ -33,6 +36,53 @@

<NotificationComponent @ref="notificationComponent"/>

@if (!nostrProfileCreated)
{
<EditForm Model="NostrMetadata" OnValidSubmit="CreatNostrProfile">
<div class="mb-3">
<label for="NostrMetadataName" class="form-label">Project name</label>
<InputText id="NostrMetadataName" @bind-Value="NostrMetadata.Name" class="form-control" placeholder="Enter the project name"/>
</div>

<div class="mb-3">
<label for="NostrMetadataAbout" class="form-label">About</label>
<InputTextArea id="NostrMetadataAbout" @bind-Value="NostrMetadata.About" class="form-control" placeholder="Enter details about the project"/>
</div>

<div class="mb-3">
<label for="NostrMetadataName" class="form-label">Project website</label>
<InputText id="NostrMetadataName" @bind-Value="NostrMetadata.Website" class="form-control" placeholder="Enter the project name"/>
</div>

<div class="mb-3">
<label for="NostrMetadataBanner" class="form-label">Banner</label>
<InputText id="NostrMetadataBanner" @bind-Value="NostrMetadata.Banner" class="form-control"/>
</div>

<div class="mb-3">
<label for="NostrMetadataNip05" class="form-label">Nip 05</label>
<InputText id="NostrMetadataNip05" @bind-Value="NostrMetadata.Nip05" class="form-control"/>
</div>

<div class="mb-3">
<label for="NostrMetadataNip57" class="form-label">Nip 57 (zaps)</label>
<InputText id="NostrMetadataNip57" @bind-Value="NostrMetadata.Nip57" class="form-control"/>
</div>

<div class="mb-3">
<label for="NostrMetadataPicture" class="form-label">Image</label>
<InputText id="NostrMetadataPicture" @bind-Value="NostrMetadata.Picture" class="form-control" placeholder="Select a project picture"/>
</div>

<!-- Save & Publish Buttons -->
<div class="mb-3">
<button type="submit" class="btn btn-success">Next</button>
</div>

</EditForm>
}
else
{
<EditForm Model="project" OnValidSubmit="CreatProject">

<!-- Angor Fee Key -->
Expand Down Expand Up @@ -91,7 +141,7 @@
<button type="submit" class="btn btn-success">Next</button>
</div>
</EditForm>

}
@if (showCreateModal)
{
<!-- Confirmation Modal -->
Expand Down Expand Up @@ -166,15 +216,31 @@
PenaltyDate = DateTime.UtcNow.AddDays(100),
ExpiryDate = DateTime.UtcNow.AddDays(50),
TargetAmount = 100,
TransactionId = "unknowen",
CreationTransactionId = "unknowen",
Stages = new List<Stage>
{
new Stage { AmountToRelease = 10, ReleaseDate = DateTime.UtcNow }, //.AddDays(10) }, during testing we often need to spend a stage immediately
new Stage { AmountToRelease = 30, ReleaseDate = DateTime.UtcNow.AddDays(20) },
new Stage { AmountToRelease = 60, ReleaseDate = DateTime.UtcNow.AddDays(30) },
new () { AmountToRelease = 10, ReleaseDate = DateTime.UtcNow }, //.AddDays(10) }, during testing we often need to spend a stage immediately
new () { AmountToRelease = 30, ReleaseDate = DateTime.UtcNow.AddDays(20) },
new () { AmountToRelease = 60, ReleaseDate = DateTime.UtcNow.AddDays(30) },
}
};

private class NostrMetadataCollection
{
public string Name { get; set; }
public string Website { get; set; }
public string About { get; set; }
public string Picture { get; set; }
public string Nip05 { get; set; }
public string Lud16 { get; set; }
public string Banner { get; set; }
public string Nip57 { get; set; }
}

NostrMetadataCollection NostrMetadata = new ();
bool nostrProfileCreated;
bool applicationDataOnNostr;

protected override async Task OnInitializedAsync()
{
hasWallet = _walletStorage.HasWallet();
Expand All @@ -194,6 +260,53 @@
}

await _RelayService.ConnectToRelaysAsync();

await _RelayService.RequestProjectEventsoByPubKeyAsync(project.NostrPubKey, _ =>
{
nostrProfileCreated = _.Kind == NostrKind.Metadata;

if (_.Kind == NostrKind.ApplicationSpecificData) //In case of a crashed application in the middle of the call
{
var nostrProject = Newtonsoft.Json.JsonConvert.DeserializeObject<ProjectInfo>(_.Content!, NostrSerializer.Settings);
var findProject = storage.GetFounderProjects().FirstOrDefault(p => p.ProjectIdentifier == nostrProject!.ProjectIdentifier);
if (findProject == null)
storage.AddFounderProject(nostrProject!);
NavigationManager.NavigateTo($"/view/{nostrProject.ProjectIdentifier}");
}

StateHasChanged();
});
}

private async Task CreatNostrProfile()
{
var nostrKey = _derivationOperations.DeriveProjectNostrPrivateKey(_walletStorage.GetWallet(), project.ProjectIndex);

var resultId = await _RelayService.CreateNostrProfileAsync(new NostrMetadata
{
About = NostrMetadata.About,
Banner = NostrMetadata.Banner,
Lud16 = NostrMetadata.Lud16,
Name = NostrMetadata.Name,
Nip05 = NostrMetadata.Nip05,
Nip57 = NostrMetadata.Nip57,
Picture = NostrMetadata.Picture,
AdditionalData = new Dictionary<string, object>
{
{"website",NostrMetadata.Website},
{"display_name",NostrMetadata.Name}
}
}, NBitcoin.DataEncoders.Encoders.Hex.EncodeData((byte[])nostrKey.ToBytes()));

_RelayService.RegisterOKMessageHandler(resultId, _ =>
{
if (_.EventId != resultId)
return;
if (!_.Accepted)
notificationComponent.ShowErrorMessage("Failed to store the project information on the relay!!!"); //TODO add export project info
nostrProfileCreated = true;
StateHasChanged();
});
}

private async Task CreatProject()
Expand Down Expand Up @@ -238,7 +351,7 @@

signedTransaction = _WalletOperations.AddInputsAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unsignedTransaction, _walletStorage.GetWallet(), accountInfo, feeData.SelectedFeeEstimation);

project.TransactionId = signedTransaction.GetHash().ToString();
project.CreationTransactionId = signedTransaction.GetHash().ToString();

return new OperationResult { Success = true };

Expand Down Expand Up @@ -281,18 +394,24 @@
var operationResult = await notificationComponent.LongOperation(async () =>
{
showCreateModal = false;

var nostrKey = _derivationOperations.DeriveProjectNostrPrivateKey(_walletStorage.GetWallet(), project.ProjectIndex);

var nostrKeyHex = NBitcoin.DataEncoders.Encoders.Hex.EncodeData(nostrKey.ToBytes());

var resultId = await _RelayService.AddProjectAsync(project, nostrKeyHex);

var response = await _WalletOperations.PublishTransactionAsync(network, signedTransaction);

if (!response.Success)
{
await _RelayService.DeleteProjectAsync(resultId, nostrKeyHex);
//storage.AddFounderProject(project); TODO remove from storage
return response;

storage.AddFounderProject(project);
}

var nostrKey = _derivationOperations.DeriveProjectNostrPrivateKey(_walletStorage.GetWallet(), project.ProjectIndex);
storage.AddFounderProject(project);

var resultId = await _RelayService.AddProjectAsync(project, NBitcoin.DataEncoders.Encoders.Hex.EncodeData(nostrKey.ToBytes()));

_RelayService.RegisterOKMessageHandler(resultId, _ =>
{
if (_.EventId != resultId)
Expand All @@ -301,11 +420,10 @@
notificationComponent.ShowErrorMessage("Failed to store the project information on the relay!!!"); //TODO add export project info
});

// todo this code must be reviewed again as we send the recovery private key to the signing server
// todo this code must be reviewed again as we send the recovery private key to the signing server
var key = _derivationOperations.DeriveFounderRecoveryPrivateKey(_walletStorage.GetWallet(), project.ProjectIndex);

await _SignService.AddSignKeyAsync(project, Encoders.Hex.EncodeData(key.ToBytes()));
await _SignService.AddSignKeyAsync(project, Encoders.Hex.EncodeData(key.ToBytes()), nostrKeyHex);

return new OperationResult { Success = response.Success, Message = response.Message };
});
Expand Down Expand Up @@ -333,4 +451,10 @@
{
project.Stages.Remove(stage);
}

public void Dispose()
{
_RelayService.CloseConnection();
}

}
Loading

0 comments on commit f005464

Please sign in to comment.