Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NOSTR calls to relay for signing and browsing #12

Merged
merged 10 commits into from
Nov 15, 2023
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();

DavidGershony marked this conversation as resolved.
Show resolved Hide resolved
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
Loading