Skip to content

Commit

Permalink
Handle unconfirmed trx in wallet (#24)
Browse files Browse the repository at this point in the history
* Handle unconfirmed trx in wallet

* Ignore spent outputs when looking for utxos

* Correctly show the days till penalty recovery

* hide text when waiting for sigs

* Add scollable to modal and make a hack for the utxo balance from indexer

* remove temp code

* founder spending to check for pending spends

* Add session storage structure named UnconfirmedInfo for any pending inputs and outputs

* separate the pending account structure from other pending outputs (like founder spend)

* Fix qrcode to be a popup

* Add support for pending in the recovery page

* Making minor changes in the display of the qrcode modal button

* Act on review

* Fix a bug on the recover page and change the spinner

* Fix page titles

* Fixed bug in the flow of loading items in the browse page

* Changed the pending to be flagged on UTXO on the account info and not as it's own list in session storage

* Testing pending payment and fixed other minor bugs

* Removed commented code

---------

Co-authored-by: Milad Raeisi <[email protected]>
Co-authored-by: TheDude <[email protected]>
  • Loading branch information
3 people authored Dec 21, 2023
1 parent ab8c016 commit d708d46
Show file tree
Hide file tree
Showing 29 changed files with 633 additions and 246 deletions.
4 changes: 2 additions & 2 deletions src/Angor.Test/WalletOperationsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ private void AddCoins(AccountInfo accountInfo, int utxos, long amount)
{
var res = new List<UtxoData>
{
new UtxoData
new ()
{
address =address,
value = Money.Satoshis(amount).Satoshi,
outpoint = new Outpoint {outputIndex = outputIndex++, transactionId = uint256.Zero.ToString() },
outpoint = new Outpoint( uint256.Zero.ToString(),outputIndex++ ),
scriptHex = new Blockcore.NBitcoin.BitcoinWitPubKeyAddress(address,network).ScriptPubKey.ToHex()
}
};
Expand Down
97 changes: 71 additions & 26 deletions src/Angor/Client/Components/ShowQrCode.razor
Original file line number Diff line number Diff line change
@@ -1,51 +1,70 @@
@using Angor.Shared.Services
@using Angor.Client.Models
@using Angor.Client.Services
@using Nostr.Client.Messages
@using Nostr.Client.Messages.Metadata
@using QRCoder

@inject IClipboardService _clipboardService

<div class="col-lg-6">
<h4 class="card-title">QR Code</h4>
@if (!string.IsNullOrEmpty(base64qrcode))
{
<img src="data:image /png;base64,@base64qrcode" class="card-img-top qrcode" alt="QR Code"/>
}
</div>
<!-- Trigger Button -->
<button class="btn btn-secondary qr-button" @onclick="ShowModal">Show QR Code</button>

<!-- Modal -->
@if (showModal)
{
<div class="modal fade show d-block" id="qrcodeModal" tabindex="-1" aria-labelledby="qrcodeModalLabel" aria-hidden="true">
<div class="modal-dialog custom-modal-size">
<!-- Custom modal size -->
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="coinControlModalLabel">QR Code</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" @onclick="HideModal" aria-label="Close"></button>
</div>
<div class="modal-body">
<img src="data:image/png;base64,@base64qrcode" class="qrcode-image" alt="QR Code" />
<div class="address-container">
<div class="d-flex align-items-center">
<p id="receive-address" class="card-text mb-0">@Data</p>
<button class="btn btn-outline-secondary btn-sm address-copy-button" @onclick="CopyToClipboard">
<i class="oi oi-file"></i>
</button>
</div>
</div>
</div>

</div>
</div>
</div>
}

@code {

[Parameter]
public string Data { get; set; }

private static string lastqrcode;
private static string lastaddress;
private string base64qrcode;

protected override async Task OnInitializedAsync()
private bool showModal = false;

private void ShowModal()
{
GenerateQRCode(Data);
showModal = true;
}

public Task GenerateQRCode(string newData)
private void HideModal()
{
Data = newData;

if (lastaddress == Data)
{
base64qrcode = lastqrcode;
return Task.CompletedTask;
}
showModal = false;
}

return Task.Run(() =>
{
base64qrcode = GenerateQRCodeInternal(Data);
lastqrcode = base64qrcode;
lastaddress = Data;
public void GenerateQRCode(string newData)
{
Data = newData;

StateHasChanged();
base64qrcode = GenerateQRCodeInternal(Data);

});
StateHasChanged();
}

public static string GenerateQRCodeInternal(string content)
Expand All @@ -55,4 +74,30 @@
using PngByteQRCode pngByteQRCode = new PngByteQRCode(qrCodeData);
return Convert.ToBase64String(pngByteQRCode.GetGraphic(10));
}
}

private async Task CopyToClipboard()
{
await _clipboardService.WriteTextAsync(Data);

}
}

<style>
.custom-modal-size {
max-width: 450px; /* Adjust this value as needed */
}
.modal-content {
padding: 10px; /* Reduced padding */
}
.modal-body {
padding: 10px; /* Reduced padding */
}
.qrcode-image {
width: 100%; /* Image takes full width of the modal body */
height: auto; /* Maintain aspect ratio */
}
</style>
53 changes: 26 additions & 27 deletions src/Angor/Client/Pages/Browse.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,44 @@
@inject IRelayService _RelayService
@inject IIndexerService _IndexerService

<div class="container">
<div class="container my-4">
<h1>Browse Projects</h1>

<NotificationComponent @ref="notificationComponent"/>

<div class="row">
<div class="col">
<h3>Browse Projects</h3>

<NotificationComponent @ref="notificationComponent"/>

<!-- Search form -->
<form @onsubmit="SearchProjects">
<div class="mb-3">
<label for="searchQuery" class="form-label">Search</label>
<input type="text" id="searchQuery" @bind="searchQuery" class="form-control" placeholder="Enter search query" />
<input type="text" id="searchQuery" @bind="searchQuery" class="form-control" placeholder="Enter search query"/>
</div>
<button type="submit" class="btn btn-primary" disabled="@searchInProgress">Search</button>
<br /> <br />
<br/> <br/>
</form>

<!-- List of projects -->
@if (projects.Count == 0)
@if (searchInProgress)
{
@if (searchInProgress)
{
<div class="loader"></div>
}
else
{
<p>No projects found.</p>
}
<div class="loader"></div>
}
else
{
foreach (var project in projects.OrderByDescending(project => project.CreatedOnBlock))
@if (projects.Count == 0)
{
@if (SessionStorage.IsProjectInStorageById(project.ProjectIdentifier)) //TODO should we show that projects exist but the user needs to add the right relay?
<p>No projects found.</p>
}
else
{
foreach (var project in projects.OrderByDescending(project => project.CreatedOnBlock))
{
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">@project.ProjectIdentifier</h5>
<p class="card-text">Nostr ID: <a href="/" target="_blank">@(NostrPublicKey.FromHex(project.NostrPubKey).Bech32)</a></p>
@if(searchInProgress)
{
<div class="loader"></div>
}
else
{
<button @onclick="() => ViewProjectDetails(project.ProjectIdentifier)" class="btn btn-primary">View</button>
}
<button @onclick="() => ViewProjectDetails(project.ProjectIdentifier)" class="btn btn-primary">View</button>
</div>
</div>
}
Expand Down Expand Up @@ -93,7 +84,6 @@

var projectsForLookup = projectsNotInList
.Where(_ => _.NostrPubKey != null) //For old projects in the indexer
//.Where(_ => !SessionStorage.IsProjectInStorageById(_.ProjectIdentifier))
.Select(_ => _.NostrPubKey)
.ToArray();

Expand All @@ -103,7 +93,16 @@
if (!SessionStorage.IsProjectInStorageById(_.ProjectIdentifier))
SessionStorage.StoreProjectInfo(_);
},
OnEndOfStreamAction: StateHasChanged,
OnEndOfStreamAction: () =>
{
projects = projects //Remove projects that were not found on the relays
.Where(_ => SessionStorage.IsProjectInStorageById(_.ProjectIdentifier))
.ToList();

SessionStorage.SetProjectIndexerData(projects);

StateHasChanged();
},
nostrPubKey: projectsForLookup);

StateHasChanged();
Expand Down
5 changes: 2 additions & 3 deletions src/Angor/Client/Pages/CheckTransactionCode.razor
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ else
SendToAddress = privateFounderKey.Neuter().PubKey.GetSegwitAddress(network).ToString()
};

_walletOperations.CalculateTransactionFee(sendInfo, localAccountInfo, 1000);
_walletOperations.CalculateTransactionFee(sendInfo, localAccountInfo, 1000);

var fee = await _walletOperations.GetFeeEstimationAsync();

Expand All @@ -193,8 +193,7 @@ else
founderTransaction = FounderTransactionActions.SpendFounderStage(context.ProjectInfo, new List<string>(){ transaction.ToHex()}, 1,
testAddress.ScriptPubKey , privateFounderKey.PrivateKey.ToHex(network.Consensus.ConsensusFactory), fee.First());

transaction = _walletOperations.AddInputsAndSignTransaction(context.ChangeAddress, transaction, walletWords, localAccountInfo,
fee.First());
transaction = _walletOperations.AddInputsAndSignTransaction(context.ChangeAddress, transaction, walletWords, localAccountInfo, fee.First());

ShowTransaction = true;
}
Expand Down
5 changes: 4 additions & 1 deletion src/Angor/Client/Pages/Create.razor
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
@inject IDerivationOperations _derivationOperations
@inject IWalletStorage _walletStorage;
@inject IClientStorage storage;
@inject ICacheStorage _cacheStorage;
@inject NavigationManager NavigationManager
@inject IWalletOperations _WalletOperations
@inject IRelayService _RelayService
Expand Down Expand Up @@ -152,7 +153,7 @@
<h5 class="modal-title">Confirmation</h5>
<button type="button" class="btn-close" @onclick="() => showCreateModal = false"></button>
</div>
<div class="modal-body">
<div class="modal-body modal-body-scroll">
<p class="mb-1"><strong>Project Identifier:</strong> @project.ProjectIdentifier</p>
<p class="mb-1"><strong>Founder Key:</strong> @project.FounderKey.Substring(0, 10)...</p> <!-- Display only the first 10 characters -->

Expand Down Expand Up @@ -316,6 +317,7 @@
var operationResult = await notificationComponent.LongOperation(async () =>
{
var accountInfo = storage.GetAccountInfo(network.Name);
var unconfirmedInfo = _cacheStorage.GetUnconfirmedInboundFunds();

var fetchFees = await _WalletOperations.GetFeeEstimationAsync();
feeData.FeeEstimations.Fees.Clear();
Expand Down Expand Up @@ -355,6 +357,7 @@
feeData.SelectedFeeEstimation = feeData.FeeEstimations.Fees.OrderBy(fee => fee.Confirmations).ToList()[res - 1];

var accountInfo = storage.GetAccountInfo(network.Name);
var unconfirmedInfo = _cacheStorage.GetUnconfirmedInboundFunds();

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

Expand Down
21 changes: 11 additions & 10 deletions src/Angor/Client/Pages/Founder.razor
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,25 @@
return;
}

<div class="container">
<div class="container my-4">
<h1>Founder Page</h1>

<NotificationComponent @ref="notificationComponent"/>

<div class="row">
<div class="col">
<h3>Founder Page</h3>

<NotificationComponent @ref="notificationComponent"/>

<p>Welcome to the founder page! Here you can create a new project or view your existing project.</p>

<p>Creating a project requires an on-chain transaction and a nostr did</p>

</div>
</div>

@if (founderProjects.Count == 0)
{
<p>No projects found.</p>

<div class="row">
<div class="col">
@if (scanningForProjects)
Expand All @@ -45,12 +46,12 @@
}
else
{
<button class="btn btn-primary mb-4" @onclick="LookupProjectKeysOnIndexerAsync">Scan for founder projects</button>
<button class="btn btn-primary mb-4" @onclick="LookupProjectKeysOnIndexerAsync">Scan for founder projects</button>
<br/>
}
</div>
</div>

}
else
{
Expand All @@ -63,7 +64,7 @@
</div>
}
}

<div class="row">
<div class="col">
<button class="btn btn-primary mb-3" @onclick="NavigateToCreateProject">Create Project</button>
Expand Down
16 changes: 9 additions & 7 deletions src/Angor/Client/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

<PageTitle>Index</PageTitle>

<h1>Welcome to Angor !</h1>
<div class="container my-4">
<h1>Welcome to Angor !</h1>

<h4>
<br />
A decentralized crowdfunding platform built on Bitcoin and Nostr.
<br /><br />
Think of it as a trustless, risk-minimized ICO.
</h4>
<h5>
<br />
A decentralized crowdfunding platform built on the Bitcoin and Nostr.
<br /><br />
Think of it as a trustless, risk-minimized ICO.
</h5>
</div>
Loading

0 comments on commit d708d46

Please sign in to comment.