Compare commits
2 Commits
ln-deploy
...
feature/at
Author | SHA1 | Date | |
---|---|---|---|
cf496f1d34 | |||
85d9d348f0 |
BTCPayServer.Common
BTCPayServer.Tests
BTCPayServerTester.csChangellyTests.csPaymentRequestTests.csSeleniumTester.csServerTester.csUnitTest1.cs
BTCPayServer
AtomicSwaps
BTCPayServer.csprojControllers
EscrowScriptBuilder.csHosting
Services
Views/Wallets
AtomicSwapDetails.cshtmlAtomicSwapDetailsMarkerWaitingTaker.cshtmlAtomicSwapDetailsMarkerWaitingTakerViewModel.csAtomicSwapDetailsTakerWaitingTaker.cshtmlAtomicSwapDetailsTakerWaitingTakerViewModel.csAtomicSwapEscrow.cshtmlAtomicSwapEscrowViewModel.csAtomicSwapList.cshtmlAtomicSwapRepository.csListViewModel.csNewAtomicSwap.cshtmlNewViewModel.csOfferViewModel.csTakeAtomicSwap.cshtmlTakeViewModel.csWalletsNavPages.cs_Nav.cshtml
@ -24,6 +24,7 @@ namespace BTCPayServer
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("0'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
BlockTime = TimeSpan.FromMinutes(10),
|
||||
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
|
||||
ElectrumMapping = NetworkType == NetworkType.Mainnet
|
||||
? new Dictionary<uint, DerivationType>()
|
||||
|
@ -26,7 +26,8 @@ namespace BTCPayServer
|
||||
},
|
||||
CryptoImagePath = "imlegacy/dogecoin.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("3'") : new KeyPath("1'")
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("3'") : new KeyPath("1'"),
|
||||
BlockTime = TimeSpan.FromMinutes(2.5)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ namespace BTCPayServer
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
BlockTime = TimeSpan.FromMinutes(1),
|
||||
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
|
||||
ElectrumMapping = NetworkType == NetworkType.Mainnet
|
||||
? new Dictionary<uint, DerivationType>()
|
||||
|
@ -31,6 +31,7 @@ namespace BTCPayServer
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
BlockTime = TimeSpan.FromMinutes(1),
|
||||
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
|
||||
ElectrumMapping = NetworkType == NetworkType.Mainnet
|
||||
? new Dictionary<uint, DerivationType>()
|
||||
|
@ -131,6 +131,18 @@ namespace BTCPayServer
|
||||
|
||||
public string CryptoImagePath { get; set; }
|
||||
public string[] DefaultRateRules { get; internal set; } = Array.Empty<string>();
|
||||
public TimeSpan? BlockTime { get; internal set; }
|
||||
|
||||
public TimeSpan GetTimeSpan(int blockCount)
|
||||
{
|
||||
return new TimeSpan(BlockTime.Value.Ticks * blockCount);
|
||||
}
|
||||
|
||||
public int GetBlockCount(TimeSpan span)
|
||||
{
|
||||
return (int)Math.Round(((double)span.Ticks / BlockTime.Value.Ticks), MidpointRounding.ToEven);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return CryptoCode;
|
||||
|
@ -2,10 +2,7 @@
|
||||
using System.Linq;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
@ -14,27 +11,18 @@ using BTCPayServer.Tests.Mocks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using OpenIddict.Abstractions;
|
||||
using Xunit;
|
||||
using BTCPayServer.Services;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
@ -10,10 +9,8 @@ using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments.Changelly;
|
||||
using BTCPayServer.Payments.Changelly.Models;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
@ -1,26 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Models.PaymentRequestViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments.Changelly;
|
||||
using BTCPayServer.Payments.Changelly.Models;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
@ -1,26 +1,18 @@
|
||||
using System;
|
||||
using BTCPayServer;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using NBitcoin;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Chrome;
|
||||
using Xunit;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
|
@ -1,28 +1,17 @@
|
||||
using BTCPayServer.Controllers;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Linq;
|
||||
using NBitcoin;
|
||||
using NBitcoin.RPC;
|
||||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Globalization;
|
||||
using BTCPayServer.Tests.Lnd;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
|
@ -60,6 +60,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using BTCPayServer.U2F.Models;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using MemoryCache = Microsoft.Extensions.Caching.Memory.MemoryCache;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
@ -1488,6 +1489,104 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanSwapCurrencies()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var userBTCWallet = user.RegisterDerivationScheme("BTC");
|
||||
var userLTCWallet = user.RegisterDerivationScheme("LTC");
|
||||
WalletId userBTCWallet2 = null;
|
||||
WalletId userLTCWallet2 = null;
|
||||
// For having faster test, let's create user2 concurrently
|
||||
var creatingUser2 = Task.Run(() =>
|
||||
{
|
||||
var u = tester.NewAccount();
|
||||
u.GrantAccess();
|
||||
userBTCWallet2 = u.RegisterDerivationScheme("BTC");
|
||||
userLTCWallet2 = u.RegisterDerivationScheme("LTC");
|
||||
return u;
|
||||
});
|
||||
|
||||
|
||||
|
||||
var atomics = user.GetController<WalletsController>();
|
||||
var newXSwap = Assert.IsType<NewViewModel>(Assert.IsType<ViewResult>(atomics.NewAtomicSwap(userBTCWallet).Result).Model);
|
||||
Assert.Equal(2, newXSwap.WalletList.Count());
|
||||
Assert.Contains(userBTCWallet.ToString(), newXSwap.WalletList.Select(c => c.Value));
|
||||
Assert.Contains(userLTCWallet.ToString(), newXSwap.WalletList.Select(c => c.Value));
|
||||
newXSwap.Amount = 1.0;
|
||||
newXSwap.Spread = 5;
|
||||
newXSwap.RateRule = "coinaverage(BTC_USD) * coinaverage(USD_LTC);";
|
||||
newXSwap.SelectedWallet = userLTCWallet.ToString();
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(atomics.NewAtomicSwap(userBTCWallet, newXSwap).GetAwaiter().GetResult());
|
||||
Assert.NotNull(atomics.CreatedOfferId);
|
||||
var entry = atomics.AtomicSwapRepository.GetEntry(atomics.CreatedOfferId).Result;
|
||||
Assert.NotNull(entry);
|
||||
var offer = entry.Offer;
|
||||
// The amount of BTC is less than the amount of LTC because it is more expensive
|
||||
Assert.True(offer.Offer.Amount < offer.Price.Amount);
|
||||
Assert.Equal("BTC", offer.Offer.CryptoCode);
|
||||
Assert.Equal("LTC", offer.Price.CryptoCode);
|
||||
Assert.Equal("coinaverage(BTC_USD) * coinaverage(USD_LTC)", offer.Rule);
|
||||
|
||||
// Let's check it the offer appear in the list
|
||||
var list = Assert.IsType<ListViewModel>(Assert.IsType<ViewResult>(atomics.AtomicSwapList(userBTCWallet).Result).Model);
|
||||
var item = list.Swaps[0];
|
||||
Assert.Equal(XSwapRole.Maker.ToString(), item.Role);
|
||||
Assert.Equal("1.00000000 BTC", item.Sent);
|
||||
Assert.Equal("10.50000000 LTC", item.Received);
|
||||
Assert.Equal(XSwapStatus.WaitingTaker.ToString(), item.Status);
|
||||
|
||||
// Let's get user2 take the offer
|
||||
var user2 = creatingUser2.Result;
|
||||
var apps2 = user2.GetController<AppsController>();
|
||||
|
||||
var atomics2 = user2.GetController<WalletsController>();
|
||||
var takeVM = Assert.IsType<TakeViewModel>(Assert.IsType<ViewResult>(atomics2.TakeAtomicSwap(userBTCWallet2)).Model);
|
||||
takeVM.MakerUri = atomics.AtomicSwapRepository.GetEntry(atomics.CreatedOfferId).Result.Offer.MarketMakerUri.ToString();
|
||||
|
||||
var list2 = Assert.IsType<ListViewModel>(Assert.IsType<ViewResult>(atomics2.AtomicSwapList(userBTCWallet2).Result).Model);
|
||||
Assert.Empty(list2.Swaps);
|
||||
|
||||
atomics2.TakeAtomicSwap(userBTCWallet2, takeVM).Wait();
|
||||
|
||||
list = Assert.IsType<ListViewModel>(Assert.IsType<ViewResult>(atomics.AtomicSwapList(userBTCWallet).Result).Model);
|
||||
list2 = Assert.IsType<ListViewModel>(Assert.IsType<ViewResult>(atomics2.AtomicSwapList(userBTCWallet2).Result).Model);
|
||||
|
||||
Assert.Single(list.Swaps);
|
||||
Assert.Single(list2.Swaps);
|
||||
|
||||
Assert.Null(list.Swaps[0].Partner);
|
||||
Assert.Equal("127.0.0.1", list2.Swaps[0].Partner);
|
||||
Assert.Equal(XSwapStatus.WaitingTaker.ToString(), list.Swaps[0].Status);
|
||||
Assert.Equal(XSwapStatus.WaitingTaker.ToString(), list2.Swaps[0].Status);
|
||||
|
||||
var takerVM = Assert.IsType<AtomicSwapDetailsTakerWaitingTakerViewModel>(Assert.IsType<ViewResult>(atomics2.AtomicSwapDetails(userBTCWallet2, atomics2.CreatedOfferId).Result).Model);
|
||||
|
||||
atomics2.AcceptAtomicSwapOffer(userBTCWallet2, atomics2.CreatedOfferId, takerVM).Wait();
|
||||
|
||||
list = Assert.IsType<ListViewModel>(Assert.IsType<ViewResult>(atomics.AtomicSwapList(userBTCWallet).Result).Model);
|
||||
list2 = Assert.IsType<ListViewModel>(Assert.IsType<ViewResult>(atomics2.AtomicSwapList(userBTCWallet2).Result).Model);
|
||||
|
||||
Assert.Equal(XSwapStatus.WaitingEscrow.ToString(), list.Swaps[0].Status);
|
||||
Assert.Equal(XSwapStatus.WaitingEscrow.ToString(), list2.Swaps[0].Status);
|
||||
Assert.Equal(XSwapRole.Maker.ToString(), list.Swaps[0].Role);
|
||||
Assert.Equal(XSwapRole.Taker.ToString(), list2.Swaps[0].Role);
|
||||
Assert.Equal("1.00000000 BTC", list.Swaps[0].Sent);
|
||||
Assert.Equal("10.50000000 LTC", list.Swaps[0].Received);
|
||||
Assert.Equal("10.50000000 LTC", list2.Swaps[0].Sent);
|
||||
Assert.Equal("1.00000000 BTC", list2.Swaps[0].Received);
|
||||
|
||||
var swapControler= Assert.IsType<AtomicSwapEscrowViewModel>(Assert.IsType<ViewResult>(atomics.AtomicSwapDetails(userBTCWallet, atomics.CreatedOfferId).Result).Model);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Altcoins", "Altcoins")]
|
||||
|
130
BTCPayServer/AtomicSwaps/AtomicSwapClient.cs
Normal file
130
BTCPayServer/AtomicSwaps/AtomicSwapClient.cs
Normal file
@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.AtomicSwaps
|
||||
{
|
||||
public class AtomicSwapClient
|
||||
{
|
||||
public AtomicSwapClient(Uri serverAddress)
|
||||
{
|
||||
if (serverAddress == null)
|
||||
throw new ArgumentNullException(nameof(serverAddress));
|
||||
ServerAddress = serverAddress;
|
||||
}
|
||||
|
||||
private static readonly HttpClient SharedClient = new HttpClient();
|
||||
internal HttpClient Client = SharedClient;
|
||||
|
||||
public void SetClient(HttpClient client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
public Uri ServerAddress { get; }
|
||||
|
||||
internal string GetFullUri(string relativePath, params object[] parameters)
|
||||
{
|
||||
relativePath = string.Format(CultureInfo.InvariantCulture, relativePath, parameters ?? Array.Empty<object>());
|
||||
var uri = ServerAddress.AbsoluteUri;
|
||||
if (!uri.EndsWith("/", StringComparison.Ordinal))
|
||||
uri += "/";
|
||||
uri += relativePath;
|
||||
return uri;
|
||||
}
|
||||
private Task<T> GetAsync<T>(string relativePath, object[] parameters, CancellationToken cancellation)
|
||||
{
|
||||
return SendAsync<T>(HttpMethod.Get, null, relativePath, parameters, cancellation);
|
||||
}
|
||||
private async Task<T> SendAsync<T>(HttpMethod method, object body, string relativePath, object[] parameters, CancellationToken cancellation)
|
||||
{
|
||||
HttpRequestMessage message = CreateMessage(method, body, relativePath, parameters);
|
||||
var result = await Client.SendAsync(message, cancellation).ConfigureAwait(false);
|
||||
if ((int)result.StatusCode == 404)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return await ParseResponse<T>(result).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal HttpRequestMessage CreateMessage(HttpMethod method, object body, string relativePath, object[] parameters)
|
||||
{
|
||||
var uri = GetFullUri(relativePath, parameters);
|
||||
var message = new HttpRequestMessage(method, uri);
|
||||
if (body != null)
|
||||
{
|
||||
if (body is byte[])
|
||||
message.Content = new ByteArrayContent((byte[])body);
|
||||
else
|
||||
message.Content = new StringContent(NBitcoin.JsonConverters.Serializer.ToString(body), Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private async Task<T> ParseResponse<T>(HttpResponseMessage response)
|
||||
{
|
||||
using (response)
|
||||
{
|
||||
if (response.IsSuccessStatusCode)
|
||||
if (response.Content.Headers.ContentLength == 0)
|
||||
return default(T);
|
||||
else if (response.Content.Headers.ContentType.MediaType.Equals("application/json", StringComparison.Ordinal))
|
||||
{
|
||||
var str = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
return NBitcoin.JsonConverters.Serializer.ToObject<T>(str);
|
||||
}
|
||||
else if (response.Content.Headers.ContentType.MediaType.Equals("application/octet-stream", StringComparison.Ordinal))
|
||||
{
|
||||
return (T)(object)await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
}
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
return default(T);
|
||||
var aaa = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
//if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
|
||||
response.EnsureSuccessStatusCode();
|
||||
//var error = _Serializer.ToObject<NBXplorerError>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
|
||||
//if (error == null)
|
||||
// response.EnsureSuccessStatusCode();
|
||||
//throw error.AsException();
|
||||
return default(T);
|
||||
}
|
||||
}
|
||||
|
||||
private Task ParseResponse(HttpResponseMessage response)
|
||||
{
|
||||
using (response)
|
||||
{
|
||||
if (response.IsSuccessStatusCode)
|
||||
return Task.CompletedTask;
|
||||
//if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
|
||||
response.EnsureSuccessStatusCode();
|
||||
//var error = _Serializer.ToObject<NBXplorerError>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
|
||||
//if (error == null)
|
||||
// response.EnsureSuccessStatusCode();
|
||||
//throw error.AsException();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<AtomicSwapOffer> GetOffer(CancellationToken cancellation = default)
|
||||
{
|
||||
return await SendAsync<AtomicSwapOffer>(HttpMethod.Get, null, "offer", null, cancellation);
|
||||
}
|
||||
|
||||
public async Task<AtomicSwapTakeResponse> Take(AtomicSwapTakeRequest atomicSwapTakeRequest, CancellationToken cancellation = default)
|
||||
{
|
||||
return await SendAsync<AtomicSwapTakeResponse>(HttpMethod.Post, atomicSwapTakeRequest, "take", null, cancellation);
|
||||
}
|
||||
}
|
||||
}
|
17
BTCPayServer/AtomicSwaps/AtomicSwapTakeRequest.cs
Normal file
17
BTCPayServer/AtomicSwaps/AtomicSwapTakeRequest.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.AtomicSwaps
|
||||
{
|
||||
[Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidateNeverAttribute]
|
||||
public class AtomicSwapTakeRequest
|
||||
{
|
||||
[JsonConverter(typeof(KeyJsonConverter))]
|
||||
public PubKey MakerSentCryptoPubkey { get; set; }
|
||||
[JsonConverter(typeof(KeyJsonConverter))]
|
||||
public PubKey MakerReceivedCryptoPubkey { get; set; }
|
||||
public Uri TakerUri { get; set; }
|
||||
}
|
||||
}
|
16
BTCPayServer/AtomicSwaps/AtomicSwapTakeResponse.cs
Normal file
16
BTCPayServer/AtomicSwaps/AtomicSwapTakeResponse.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.AtomicSwaps
|
||||
{
|
||||
public class AtomicSwapTakeResponse
|
||||
{
|
||||
[JsonConverter(typeof(UInt160JsonConverter))]
|
||||
public uint160 Hash { get; set; }
|
||||
[JsonConverter(typeof(KeyJsonConverter))]
|
||||
public PubKey MakerSentCryptoPubkey { get; set; }
|
||||
[JsonConverter(typeof(KeyJsonConverter))]
|
||||
public PubKey MakerReceivedCryptoPubkey { get; set; }
|
||||
}
|
||||
}
|
31
BTCPayServer/AtomicSwaps/Preimage.cs
Normal file
31
BTCPayServer/AtomicSwaps/Preimage.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Crypto;
|
||||
|
||||
namespace BTCPayServer.AtomicSwaps
|
||||
{
|
||||
public class Preimage
|
||||
{
|
||||
public Preimage()
|
||||
{
|
||||
Bytes = RandomUtils.GetBytes(32);
|
||||
}
|
||||
public Preimage(byte[] bytes)
|
||||
{
|
||||
Bytes = bytes;
|
||||
}
|
||||
|
||||
public byte[] Bytes
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public uint160 GetHash()
|
||||
{
|
||||
return new uint160(Hashes.Hash160(Bytes, Bytes.Length));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,135 +1,135 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')"/>
|
||||
<Import Project="../Build/Common.csproj"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Build\**" />
|
||||
<Compile Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<Compile Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<Content Remove="Build\**" />
|
||||
<Content Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<Content Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<EmbeddedResource Remove="Build\**" />
|
||||
<EmbeddedResource Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<EmbeddedResource Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<None Remove="Build\**" />
|
||||
<None Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<None Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<Compile Remove="Build\**"/>
|
||||
<Compile Remove="wwwroot\bundles\jqueryvalidate\**"/>
|
||||
<Compile Remove="wwwroot\vendor\jquery-nice-select\**"/>
|
||||
<Content Remove="Build\**"/>
|
||||
<Content Remove="wwwroot\bundles\jqueryvalidate\**"/>
|
||||
<Content Remove="wwwroot\vendor\jquery-nice-select\**"/>
|
||||
<EmbeddedResource Remove="Build\**"/>
|
||||
<EmbeddedResource Remove="wwwroot\bundles\jqueryvalidate\**"/>
|
||||
<EmbeddedResource Remove="wwwroot\vendor\jquery-nice-select\**"/>
|
||||
<None Remove="Build\**"/>
|
||||
<None Remove="wwwroot\bundles\jqueryvalidate\**"/>
|
||||
<None Remove="wwwroot\vendor\jquery-nice-select\**"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Currencies.txt" />
|
||||
<None Remove="Currencies.txt"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="bundleconfig.json" />
|
||||
<EmbeddedResource Include="Currencies.txt" />
|
||||
<EmbeddedResource Include="bundleconfig.json"/>
|
||||
<EmbeddedResource Include="Currencies.txt"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="1.1.3" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.8" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="3.2.435" />
|
||||
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="4.0.217" />
|
||||
<PackageReference Include="LedgerWallet" Version="2.0.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="1.1.3"/>
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.8"/>
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="3.2.435"/>
|
||||
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435"/>
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435"/>
|
||||
<PackageReference Include="HtmlSanitizer" Version="4.0.217"/>
|
||||
<PackageReference Include="LedgerWallet" Version="2.0.0.5"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2"/>
|
||||
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.9.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.35" />
|
||||
<PackageReference Include="DBriize" Version="1.0.1.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3" />
|
||||
<PackageReference Include="NicolasDorier.RateLimits" Version="1.1.0" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18" />
|
||||
<PackageReference Include="Serilog" Version="2.9.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
|
||||
<PackageReference Include="SSH.NET" Version="2016.1.0" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.35"/>
|
||||
<PackageReference Include="DBriize" Version="1.0.1.3"/>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3"/>
|
||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2"/>
|
||||
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3"/>
|
||||
<PackageReference Include="NicolasDorier.RateLimits" Version="1.1.0"/>
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18"/>
|
||||
<PackageReference Include="Serilog" Version="2.9.0"/>
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0"/>
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0"/>
|
||||
<PackageReference Include="SSH.NET" Version="2016.1.0"/>
|
||||
<PackageReference Include="Text.Analyzers" Version="2.6.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TwentyTwenty.Storage" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Amazon" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.12.1" />
|
||||
<PackageReference Include="U2F.Core" Version="1.0.4" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.0.0" />
|
||||
<PackageReference Include="OpenIddict" Version="3.0.0-alpha1.20058.15" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TwentyTwenty.Storage" Version="2.12.1"/>
|
||||
<PackageReference Include="TwentyTwenty.Storage.Amazon" Version="2.12.1"/>
|
||||
<PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.12.1"/>
|
||||
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.12.1"/>
|
||||
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.12.1"/>
|
||||
<PackageReference Include="U2F.Core" Version="1.0.4"/>
|
||||
<PackageReference Include="YamlDotNet" Version="8.0.0"/>
|
||||
<PackageReference Include="OpenIddict" Version="3.0.0-alpha1.20058.15"/>
|
||||
<PackageReference Include="OpenIddict.Server.AspNetCore" Version="3.0.0-alpha1.20058.15"></PackageReference>
|
||||
<PackageReference Include="OpenIddict.Validation.AspNetCore" Version="3.0.0-alpha1.20058.15"></PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.0" Condition="'$(Configuration)' == 'Debug'" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.0" Condition="'$(Configuration)' == 'Debug'"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="wwwroot\main\bootstrap4-creativestart\creative.js" />
|
||||
<None Include="wwwroot\vendor\font-awesome\fonts\fontawesome-webfont.svg" />
|
||||
<None Include="wwwroot\vendor\font-awesome\fonts\fontawesome-webfont.woff2" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\animated.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\bordered-pulled.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\core.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\fixed-width.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\font-awesome.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\icons.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\larger.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\list.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\mixins.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\path.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\rotated-flipped.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\screen-reader.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\stacked.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\less\variables.less" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\font-awesome.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_animated.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_bordered-pulled.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_core.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_fixed-width.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_icons.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_larger.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_list.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_mixins.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_path.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_rotated-flipped.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_screen-reader.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_stacked.scss" />
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_variables.scss" />
|
||||
<None Include="wwwroot\vendor\jquery-easing\jquery.easing.compatibility.js" />
|
||||
<None Include="wwwroot\vendor\jquery-easing\jquery.easing.js" />
|
||||
<None Include="wwwroot\vendor\jquery-easing\jquery.easing.min.js" />
|
||||
<None Include="wwwroot\vendor\jquery\jquery.js" />
|
||||
<None Include="wwwroot\vendor\jquery\jquery.min.js" />
|
||||
<None Include="wwwroot\vendor\magnific-popup\jquery.magnific-popup.js" />
|
||||
<None Include="wwwroot\vendor\magnific-popup\jquery.magnific-popup.min.js" />
|
||||
<None Include="wwwroot\vendor\popper\popper.js" />
|
||||
<None Include="wwwroot\vendor\popper\popper.min.js" />
|
||||
<None Include="wwwroot\vendor\scrollreveal\scrollreveal.js" />
|
||||
<None Include="wwwroot\vendor\scrollreveal\scrollreveal.min.js" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="wwwroot\main\bootstrap4-creativestart\creative.js"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\fonts\fontawesome-webfont.svg"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\fonts\fontawesome-webfont.woff2"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\animated.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\bordered-pulled.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\core.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\fixed-width.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\font-awesome.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\icons.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\larger.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\list.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\mixins.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\path.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\rotated-flipped.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\screen-reader.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\stacked.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\less\variables.less"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\font-awesome.scss"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_animated.scss"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_bordered-pulled.scss"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_core.scss"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_fixed-width.scss"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_icons.scss"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_larger.scss"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_list.scss"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_mixins.scss"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_path.scss"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_rotated-flipped.scss"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_screen-reader.scss"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_stacked.scss"/>
|
||||
<None Include="wwwroot\vendor\font-awesome\scss\_variables.scss"/>
|
||||
<None Include="wwwroot\vendor\jquery-easing\jquery.easing.compatibility.js"/>
|
||||
<None Include="wwwroot\vendor\jquery-easing\jquery.easing.js"/>
|
||||
<None Include="wwwroot\vendor\jquery-easing\jquery.easing.min.js"/>
|
||||
<None Include="wwwroot\vendor\jquery\jquery.js"/>
|
||||
<None Include="wwwroot\vendor\jquery\jquery.min.js"/>
|
||||
<None Include="wwwroot\vendor\magnific-popup\jquery.magnific-popup.js"/>
|
||||
<None Include="wwwroot\vendor\magnific-popup\jquery.magnific-popup.min.js"/>
|
||||
<None Include="wwwroot\vendor\popper\popper.js"/>
|
||||
<None Include="wwwroot\vendor\popper\popper.min.js"/>
|
||||
<None Include="wwwroot\vendor\scrollreveal\scrollreveal.js"/>
|
||||
<None Include="wwwroot\vendor\scrollreveal\scrollreveal.min.js"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\vendor\clipboard.js\" />
|
||||
<Folder Include="wwwroot\vendor\highlightjs\" />
|
||||
<Folder Include="wwwroot\vendor\summernote" />
|
||||
<Folder Include="wwwroot\vendor\u2f" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\vendor\clipboard.js\"/>
|
||||
<Folder Include="wwwroot\vendor\highlightjs\"/>
|
||||
<Folder Include="wwwroot\vendor\summernote"/>
|
||||
<Folder Include="wwwroot\vendor\u2f"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Data\BTCPayServer.Data.csproj" />
|
||||
<ProjectReference Include="..\BTCPayServer.Rating\BTCPayServer.Rating.csproj" />
|
||||
<ProjectReference Include="..\BTCPayServer.Common\BTCPayServer.Common.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Data\BTCPayServer.Data.csproj"/>
|
||||
<ProjectReference Include="..\BTCPayServer.Rating\BTCPayServer.Rating.csproj"/>
|
||||
<ProjectReference Include="..\BTCPayServer.Common\BTCPayServer.Common.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Update="Views\Apps\_ViewImports.cshtml">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
@ -158,6 +158,27 @@
|
||||
<Content Update="Views\Server\DynamicDnsService.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\AtomicSwapDetailsMarkerWaitingTaker.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\AtomicSwapEscrow.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\AtomicSwapDetailsTakerWaitingTaker.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\AtomicSwapDetails.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\TakeAtomicSwap.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\NewAtomicSwap.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\AtomicSwapList.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\SSHService.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
@ -167,55 +188,55 @@
|
||||
<Content Update="Views\Stores\PayButtonEnable.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Stores\PayButton.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Public\PayButtonHandle.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\LndServices.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\Maintenance.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\Services.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\ListWallets.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletPSBTCombine.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletPSBTReady.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletPSBT.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletRescan.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletSendVault.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletSendLedger.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletTransactions.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Stores\PayButton.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Public\PayButtonHandle.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\LndServices.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\Maintenance.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\Services.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\ListWallets.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletPSBTCombine.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletPSBTReady.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletPSBT.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletRescan.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletSendVault.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletSendLedger.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletTransactions.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Remove="Views\Server\EditGoogleCloudStorageStorageProvider.cshtml">
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\_Nav.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\_ViewImports.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\_ViewStart.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<Content Update="Views\Wallets\_Nav.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\_ViewImports.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\_ViewStart.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
97
BTCPayServer/Controllers/AtomicSwapController.cs
Normal file
97
BTCPayServer/Controllers/AtomicSwapController.cs
Normal file
@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.AtomicSwaps;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Crypto;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Route("api/xswap")]
|
||||
public partial class AtomicSwapController : Controller
|
||||
{
|
||||
public AtomicSwapController(AtomicSwapRepository atomicSwapRepository, AtomicSwapClientFactory atomicSwapClientFactory)
|
||||
{
|
||||
AtomicSwapRepository = atomicSwapRepository;
|
||||
AtomicSwapClientFactory = atomicSwapClientFactory;
|
||||
}
|
||||
|
||||
public AtomicSwapRepository AtomicSwapRepository { get; }
|
||||
public AtomicSwapClientFactory AtomicSwapClientFactory { get; }
|
||||
|
||||
[Route("{offerId}/offer")]
|
||||
public async Task<IActionResult> GetOfferAPI(string offerId)
|
||||
{
|
||||
var entry = await AtomicSwapRepository.GetEntry(offerId);
|
||||
if (entry == null ||
|
||||
(entry.Status != XSwapStatus.WaitingTaker && entry.Role == XSwapRole.Maker) ||
|
||||
(entry.Status != XSwapStatus.WaitingEscrow && entry.Role == XSwapRole.Taker))
|
||||
return NotFound();
|
||||
return Json(entry.Offer);
|
||||
}
|
||||
|
||||
[Route("{offerId}/take")]
|
||||
[HttpPost]
|
||||
[IgnoreAntiforgeryToken]
|
||||
public async Task<IActionResult> TakeOfferAPI(string offerId, [FromBody] AtomicSwapTakeRequest request)
|
||||
{
|
||||
var entry = await AtomicSwapRepository.GetEntry(offerId);
|
||||
if (entry == null || entry.Status != XSwapStatus.WaitingTaker)
|
||||
return NotFound();
|
||||
// TODO atomically take the offer
|
||||
var client = AtomicSwapClientFactory.Create(request.TakerUri);
|
||||
AtomicSwapTakeResponse response = null;
|
||||
try
|
||||
{
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
cts.CancelAfter(5000);
|
||||
var takerOffer = await client.GetOffer(cts.Token);
|
||||
if (takerOffer.MarketMakerUri != entry.Offer.MarketMakerUri)
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
entry.Partner = request.TakerUri.DnsSafeHost;
|
||||
entry.OtherUri = request.TakerUri;
|
||||
entry.Status = XSwapStatus.WaitingEscrow;
|
||||
entry.Sent.MyKey = new Key();
|
||||
entry.Sent.OtherKey = request.MakerSentCryptoPubkey;
|
||||
entry.Received.MyKey = new Key();
|
||||
entry.Received.OtherKey = request.MakerReceivedCryptoPubkey;
|
||||
entry.Preimage = new Preimage();
|
||||
entry.Hash = entry.Preimage.GetHash();
|
||||
|
||||
response = new AtomicSwapTakeResponse()
|
||||
{
|
||||
Hash = entry.Preimage.GetHash(),
|
||||
MakerReceivedCryptoPubkey = entry.Received.MyKey.PubKey,
|
||||
MakerSentCryptoPubkey = entry.Received.MyKey.PubKey
|
||||
};
|
||||
|
||||
await AtomicSwapRepository.UpdateEntry(offerId, entry);
|
||||
return Json(response);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@ -17,6 +18,7 @@ using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using LedgerWallet;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@ -40,8 +42,10 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
public StoreRepository Repository { get; }
|
||||
public WalletRepository WalletRepository { get; }
|
||||
public AtomicSwapRepository AtomicSwapRepository { get; }
|
||||
public BTCPayNetworkProvider NetworkProvider { get; }
|
||||
public ExplorerClientProvider ExplorerClientProvider { get; }
|
||||
public AtomicSwapClientFactory AtomicSwapClientFactory { get; }
|
||||
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly JsonSerializerSettings _serializerSettings;
|
||||
@ -53,6 +57,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
CurrencyNameTable _currencyTable;
|
||||
public WalletsController(StoreRepository repo,
|
||||
AtomicSwapRepository atomicSwapRepository,
|
||||
WalletRepository walletRepository,
|
||||
CurrencyNameTable currencyTable,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
@ -63,7 +68,8 @@ namespace BTCPayServer.Controllers
|
||||
IAuthorizationService authorizationService,
|
||||
ExplorerClientProvider explorerProvider,
|
||||
IFeeProviderFactory feeRateProvider,
|
||||
BTCPayWalletProvider walletProvider)
|
||||
BTCPayWalletProvider walletProvider,
|
||||
AtomicSwapClientFactory atomicSwapClientFactory)
|
||||
{
|
||||
_currencyTable = currencyTable;
|
||||
Repository = repo;
|
||||
@ -77,6 +83,8 @@ namespace BTCPayServer.Controllers
|
||||
ExplorerClientProvider = explorerProvider;
|
||||
_feeRateProvider = feeRateProvider;
|
||||
_walletProvider = walletProvider;
|
||||
AtomicSwapClientFactory = atomicSwapClientFactory;
|
||||
AtomicSwapRepository = atomicSwapRepository;
|
||||
}
|
||||
|
||||
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
||||
|
398
BTCPayServer/Controllers/WalletsControllers.XSwap.cs
Normal file
398
BTCPayServer/Controllers/WalletsControllers.XSwap.cs
Normal file
@ -0,0 +1,398 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.AtomicSwaps;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
|
||||
public partial class WalletsController : Controller
|
||||
{
|
||||
[Route("{walletId}/xswap")]
|
||||
public async Task<IActionResult> AtomicSwapList([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId)
|
||||
{
|
||||
var derivationStrategy = await GetDerivationStrategy(walletId);
|
||||
if (derivationStrategy == null)
|
||||
return NotFound();
|
||||
ListViewModel list = new ListViewModel();
|
||||
list.CryptoCode = walletId.CryptoCode;
|
||||
foreach (var entry in AtomicSwapRepository.GetEntries(walletId))
|
||||
{
|
||||
ListViewModel.SwapItem item = new ListViewModel.SwapItem()
|
||||
{
|
||||
WalletId = walletId.ToString(),
|
||||
OfferId = entry.Id,
|
||||
Partner = entry.Partner,
|
||||
Role = entry.Role.ToString(),
|
||||
Status = entry.Status.ToString(),
|
||||
Timestamp = entry.Offer.CreatedAt,
|
||||
Sent = FormatAmount(entry.Sent),
|
||||
Received = FormatAmount(entry.Received),
|
||||
};
|
||||
list.Swaps.Add(item);
|
||||
}
|
||||
return View(list);
|
||||
}
|
||||
|
||||
private string FormatAmount(AtomicSwapEscrowData entry)
|
||||
{
|
||||
return _currencyTable.DisplayFormatCurrency(entry.Amount.ToDecimal(MoneyUnit.BTC), entry.CryptoCode);
|
||||
}
|
||||
|
||||
[Route("{walletId}/xswap/new")]
|
||||
public async Task<IActionResult> NewAtomicSwap([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId)
|
||||
{
|
||||
var derivationStrategy = await GetDerivationStrategy(walletId);
|
||||
if (derivationStrategy == null)
|
||||
return NotFound();
|
||||
var storeData = await Repository.FindStore(walletId.StoreId, GetUserId());
|
||||
var wallets = await GetNamedWallets(walletId.CryptoCode);
|
||||
var newVM = new NewViewModel();
|
||||
var rateRules = storeData.GetStoreBlob().GetRateRules(NetworkProvider);
|
||||
newVM.SetWalletList(wallets, walletId.ToString());
|
||||
newVM.CryptoCode = walletId.CryptoCode;
|
||||
return View(newVM);
|
||||
}
|
||||
|
||||
private async Task<NamedWallet[]> GetNamedWallets(string fromCrypto)
|
||||
{
|
||||
var stores = await Repository.GetStoresByUserId(GetUserId());
|
||||
return stores
|
||||
.SelectMany(s => s.GetSupportedPaymentMethods(NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Where(p => p.Network.BlockTime != null)
|
||||
.Where(p => p != null && ExplorerClientProvider.IsAvailable(p.Network))
|
||||
.Select(p => new NamedWallet()
|
||||
{
|
||||
Name = $"{p.PaymentId.CryptoCode}: {s.StoreName}",
|
||||
DerivationStrategy = p,
|
||||
CryptoCode = p.PaymentId.CryptoCode,
|
||||
WalletId = new WalletId(s.Id, p.PaymentId.CryptoCode),
|
||||
Rule = GetRuleNoSpread(s.GetStoreBlob(), fromCrypto, p.PaymentId.CryptoCode),
|
||||
Spread = s.GetStoreBlob().Spread
|
||||
}))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private RateRule GetRuleNoSpread(StoreBlob storeBlob, string cryptoCodeA, string cryptoCodeB)
|
||||
{
|
||||
var rules = storeBlob.GetRateRules(NetworkProvider);
|
||||
rules.Spread = 0;
|
||||
var rule = rules.GetRuleFor(new CurrencyPair(cryptoCodeA, cryptoCodeB));
|
||||
return rule;
|
||||
}
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage { get; set; }
|
||||
public string CreatedOfferId { get; private set; }
|
||||
|
||||
[Route("{walletId}/xswap/new")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> NewAtomicSwap(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId,
|
||||
NewViewModel newVM)
|
||||
{
|
||||
var fromWallet = await GetDerivationStrategy(walletId);
|
||||
var statusAsync = ExplorerClientProvider.GetExplorerClient(fromWallet.Network).GetStatusAsync();
|
||||
if (fromWallet == null)
|
||||
return NotFound();
|
||||
var wallets = await GetNamedWallets(walletId.CryptoCode);
|
||||
newVM.SetWalletList(wallets, newVM.SelectedWallet);
|
||||
newVM.CryptoCode = fromWallet.Network.CryptoCode;
|
||||
if (!WalletId.TryParse(newVM.SelectedWallet, out var selectedWalletId))
|
||||
{
|
||||
ModelState.AddModelError(nameof(newVM.SelectedWallet), "Invalid wallet id");
|
||||
return View(newVM);
|
||||
}
|
||||
var toWallet = await GetDerivationStrategy(selectedWalletId);
|
||||
if (toWallet == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(newVM.SelectedWallet), "Invalid wallet id");
|
||||
return View(newVM);
|
||||
}
|
||||
|
||||
var id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20));
|
||||
AtomicSwapOffer offer = new AtomicSwapOffer();
|
||||
offer.MarketMakerUri = new Uri($"{this.Request.GetAbsoluteRoot().WithTrailingSlash()}api/xswap/{id}", UriKind.Absolute);
|
||||
offer.Offer = new AtomicSwapOfferAsset()
|
||||
{
|
||||
Amount = Money.Coins((decimal)newVM.Amount),
|
||||
CryptoCode = walletId.CryptoCode,
|
||||
};
|
||||
|
||||
var minRelayFee = (await statusAsync).BitcoinStatus.MinRelayTxFee;
|
||||
var minimumAmount = minRelayFee.GetFee(200); // Arbitrary but should cover the dust of any output
|
||||
if (offer.Offer.Amount <= minimumAmount)
|
||||
{
|
||||
ModelState.AddModelError(nameof(newVM.Amount), $"Amount must be above {minimumAmount}");
|
||||
return View(newVM);
|
||||
}
|
||||
offer.Price = new AtomicSwapOfferAsset()
|
||||
{
|
||||
CryptoCode = toWallet.PaymentId.CryptoCode
|
||||
};
|
||||
var lockTimespan = TimeSpan.FromDays(2);
|
||||
offer.CreatedAt = DateTimeOffset.UtcNow;
|
||||
|
||||
var storeData = await Repository.FindStore(walletId.StoreId, GetUserId());
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
var pair = new CurrencyPair("AAA", "BBB");
|
||||
newVM.RateRule = $"{pair} = {newVM.RateRule}";
|
||||
|
||||
if (RateRules.TryParse(newVM.RateRule, out var rules, out var rateRulesErrors))
|
||||
{
|
||||
rules.Spread = (decimal)newVM.Spread / 100.0m;
|
||||
var rateResult = await RateFetcher.FetchRate(pair, rules, CancellationToken.None);
|
||||
if (rateResult.BidAsk == null)
|
||||
{
|
||||
string errorMessage = "Error when fetching rate";
|
||||
if (rateResult.EvaluatedRule != null)
|
||||
{
|
||||
errorMessage += $" ({rateResult.EvaluatedRule})";
|
||||
}
|
||||
ModelState.AddModelError(nameof(newVM.RateRule), errorMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
offer.Price.Amount = Money.Coins(offer.Offer.Amount.ToDecimal(MoneyUnit.BTC) * rateResult.BidAsk.Ask);
|
||||
|
||||
rules.Spread = 0;
|
||||
offer.Rule = rules.GetRuleFor(pair).ToString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string errorDetails = "";
|
||||
if (rateRulesErrors.Count > 0)
|
||||
{
|
||||
errorDetails = $" ({rateRulesErrors[0]})";
|
||||
}
|
||||
ModelState.AddModelError(nameof(newVM.RateRule), $"Impossible to parse rate rules{errorDetails}");
|
||||
}
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
return View(newVM);
|
||||
|
||||
var statusSent = ExplorerClientProvider.GetExplorerClient(offer.Offer.CryptoCode).GetStatusAsync();
|
||||
var statusReceived = ExplorerClientProvider.GetExplorerClient(offer.Price.CryptoCode).GetStatusAsync();
|
||||
|
||||
offer.Offer.LockTime = new LockTime((await statusSent).ChainHeight + fromWallet.Network.GetBlockCount(lockTimespan));
|
||||
offer.Price.LockTime = new LockTime((await statusReceived).ChainHeight + toWallet.Network.GetBlockCount(lockTimespan));
|
||||
StatusMessage = $"Offer created, share the following link with the marker takers: {offer.MarketMakerUri}";
|
||||
CreatedOfferId = id;
|
||||
|
||||
var entry = new AtomicSwapEntry();
|
||||
entry.Offer = offer;
|
||||
entry.Role = XSwapRole.Maker;
|
||||
entry.Status = XSwapStatus.WaitingTaker;
|
||||
entry.Sent = new SentAtomicSwapAsset(offer.Offer)
|
||||
{
|
||||
Refund = await GetDestination(fromWallet),
|
||||
WalletId = walletId,
|
||||
};
|
||||
entry.Received = new ReceivedAtomicSwapAsset(offer.Price)
|
||||
{
|
||||
Destination = await GetDestination(toWallet),
|
||||
WalletId = selectedWalletId
|
||||
};
|
||||
await AtomicSwapRepository.SaveEntry(walletId, id, entry);
|
||||
return RedirectToAction(nameof(AtomicSwapDetails), new { offerId = id, walletId = walletId.ToString() });
|
||||
}
|
||||
|
||||
private async Task<Script> GetDestination(DerivationSchemeSettings derivationStrategy)
|
||||
{
|
||||
var explorer = this.ExplorerClientProvider.GetExplorerClient(derivationStrategy.PaymentId.CryptoCode);
|
||||
var scriptPubKey = (await explorer.GetUnusedAsync(derivationStrategy.AccountDerivation, NBXplorer.DerivationStrategy.DerivationFeature.Deposit, reserve: true)).ScriptPubKey;
|
||||
return scriptPubKey;
|
||||
}
|
||||
|
||||
[Route("{walletId}/xswap/take")]
|
||||
public IActionResult TakeAtomicSwap(WalletId walletId)
|
||||
{
|
||||
return View(new TakeViewModel());
|
||||
}
|
||||
|
||||
[Route("{walletId}/xswap/{offerId}/details")]
|
||||
public async Task<IActionResult> AtomicSwapDetails(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId,
|
||||
string offerId)
|
||||
{
|
||||
var derivationStrategy = await GetDerivationStrategy(walletId);
|
||||
if (derivationStrategy == null)
|
||||
return NotFound();
|
||||
var offer = await AtomicSwapRepository.GetEntry(offerId);
|
||||
if (offer.Status == XSwapStatus.WaitingTaker)
|
||||
{
|
||||
if (offer.Role == XSwapRole.Maker)
|
||||
{
|
||||
var vm = new AtomicSwapDetailsMarkerWaitingTakerViewModel()
|
||||
{
|
||||
ToSend = FormatAmount(offer.Sent),
|
||||
ToReceive = FormatAmount(offer.Received),
|
||||
OfferUri = new Uri($"{this.Request.GetAbsoluteRoot().WithTrailingSlash()}api/xswap/{offerId}", UriKind.Absolute)
|
||||
};
|
||||
return View("AtomicSwapDetailsMarkerWaitingTaker", vm);
|
||||
}
|
||||
if (offer.Role == XSwapRole.Taker)
|
||||
{
|
||||
var vm = new AtomicSwapDetailsTakerWaitingTakerViewModel()
|
||||
{
|
||||
ToSend = FormatAmount(offer.Sent),
|
||||
ToReceive = FormatAmount(offer.Received),
|
||||
WalletId = walletId.ToString(),
|
||||
RefundTime = await GetRefundTime(offer),
|
||||
};
|
||||
var wallets = (await GetNamedWallets(offer.Received.CryptoCode)).Where(w => w.DerivationStrategy.PaymentId.CryptoCode == offer.Received.CryptoCode).ToArray();
|
||||
vm.SetWalletList(wallets, null);
|
||||
return View("AtomicSwapDetailsTakerWaitingTaker", vm);
|
||||
}
|
||||
}
|
||||
else if (offer.Status == XSwapStatus.WaitingEscrow)
|
||||
{
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(offer.Sent.CryptoCode);
|
||||
var vm = new AtomicSwapEscrowViewModel()
|
||||
{
|
||||
ToSend = FormatAmount(offer.Sent),
|
||||
ToReceive = FormatAmount(offer.Received),
|
||||
SentToWalletId = walletId.ToString(),
|
||||
SentFromWalletId = offer.Sent.WalletId.ToString(),
|
||||
EscrowAddress = offer.Sent.GetEscrow().ToScript().GetDestinationAddress(network.NBitcoinNetwork).ToString(),
|
||||
Amount = offer.Sent.Amount.ToString(),
|
||||
RefundTime = await GetRefundTime(offer)
|
||||
};
|
||||
return View("AtomicSwapEscrow", vm);
|
||||
}
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
public class RefundTime
|
||||
{
|
||||
public int BlockCount { get; set; }
|
||||
public TimeSpan Time { get; set; }
|
||||
}
|
||||
private async Task<RefundTime> GetRefundTime(AtomicSwapEntry offer)
|
||||
{
|
||||
var status = await ExplorerClientProvider.GetExplorerClient(offer.Sent.CryptoCode).GetStatusAsync();
|
||||
var network = NetworkProvider.GetNetwork(offer.Sent.CryptoCode);
|
||||
var blocksToWait = Math.Max(0, offer.Sent.LockTime.Height - status.ChainHeight);
|
||||
var refundTime = network.GetTimeSpan(blocksToWait);
|
||||
return new RefundTime() { BlockCount = blocksToWait, Time = refundTime };
|
||||
}
|
||||
|
||||
[Route("{walletId}/xswap/take")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> TakeAtomicSwap([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, TakeViewModel takeViewModel)
|
||||
{
|
||||
var derivationStrategy = await GetDerivationStrategy(walletId);
|
||||
var makerUri = new Uri(takeViewModel.MakerUri, UriKind.Absolute);
|
||||
AtomicSwapClient client = AtomicSwapClientFactory.Create(makerUri);
|
||||
var offer = await client.GetOffer();
|
||||
if (offer == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(takeViewModel.MakerUri), "Offer not found");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (offer.Offer.CryptoCode != walletId.CryptoCode)
|
||||
{
|
||||
ModelState.AddModelError(nameof(takeViewModel.MakerUri), $"Offer for {offer.Offer.CryptoCode}, but this wallet is for {walletId.CryptoCode}");
|
||||
}
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
return View(takeViewModel);
|
||||
var entry = new AtomicSwapEntry()
|
||||
{
|
||||
Partner = new Uri(takeViewModel.MakerUri, UriKind.Absolute).DnsSafeHost,
|
||||
OtherUri = makerUri,
|
||||
Offer = offer,
|
||||
Received = new ReceivedAtomicSwapAsset(offer.Offer)
|
||||
{
|
||||
OtherKey = offer.Offer.Pubkey,
|
||||
Destination = await GetDestination(derivationStrategy),
|
||||
WalletId = walletId
|
||||
},
|
||||
Sent = new SentAtomicSwapAsset(offer.Price)
|
||||
{
|
||||
OtherKey = offer.Price.Pubkey,
|
||||
},
|
||||
Role = XSwapRole.Taker,
|
||||
Status = XSwapStatus.WaitingTaker
|
||||
};
|
||||
var id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20));
|
||||
CreatedOfferId = id;
|
||||
await AtomicSwapRepository.SaveEntry(walletId, id, entry);
|
||||
return RedirectToAction(nameof(AtomicSwapDetails), new { offerId = id, walletId = walletId.ToString() });
|
||||
}
|
||||
|
||||
[Route("{walletId}/xswap/{offerId}/accept")]
|
||||
public async Task<IActionResult> AcceptAtomicSwapOffer(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId,
|
||||
string offerId, AtomicSwapDetailsTakerWaitingTakerViewModel viewModel)
|
||||
{
|
||||
var derivationStrategy = await GetDerivationStrategy(walletId);
|
||||
if (derivationStrategy == null)
|
||||
return NotFound();
|
||||
|
||||
var entry = await AtomicSwapRepository.GetEntry(offerId);
|
||||
if (entry == null)
|
||||
return NotFound();
|
||||
|
||||
|
||||
WalletId.TryParse(viewModel.SelectedWallet, out var selectedWalletId);
|
||||
var destinationStrategy = await GetDerivationStrategy(selectedWalletId);
|
||||
|
||||
entry.Sent.WalletId = walletId;
|
||||
entry.Sent.Refund = await GetDestination(derivationStrategy);
|
||||
entry.Sent.MyKey = new Key();
|
||||
|
||||
entry.Received.Destination = await GetDestination(destinationStrategy);
|
||||
entry.Received.WalletId = selectedWalletId;
|
||||
entry.Received.MyKey = new Key();
|
||||
entry.Status = XSwapStatus.WaitingEscrow;
|
||||
|
||||
var maker = AtomicSwapClientFactory.Create(entry.OtherUri);
|
||||
var response = await maker.Take(new AtomicSwapTakeRequest()
|
||||
{
|
||||
TakerUri = new Uri($"{this.Request.GetAbsoluteRoot().WithTrailingSlash()}api/xswap/{entry.Id}", UriKind.Absolute),
|
||||
MakerReceivedCryptoPubkey = entry.Sent.MyKey.PubKey,
|
||||
MakerSentCryptoPubkey = entry.Received.MyKey.PubKey,
|
||||
});
|
||||
|
||||
if (response == null)
|
||||
return NotFound();
|
||||
|
||||
entry.Received.OtherKey = response.MakerSentCryptoPubkey;
|
||||
entry.Sent.OtherKey = response.MakerReceivedCryptoPubkey;
|
||||
entry.Hash = response.Hash;
|
||||
|
||||
await AtomicSwapRepository.UpdateEntry(offerId, entry);
|
||||
|
||||
return RedirectToAction(nameof(AtomicSwapDetails), new { offerId = offerId, walletId = walletId.ToString() });
|
||||
}
|
||||
|
||||
async Task<DerivationSchemeSettings> GetDerivationStrategy(WalletId walletId)
|
||||
{
|
||||
if (walletId?.StoreId == null || GetUserId() == null)
|
||||
return null;
|
||||
var storeData = await this.Repository.FindStore(walletId.StoreId, GetUserId());
|
||||
var strategy = storeData.GetSupportedPaymentMethods(NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.FirstOrDefault(s => s.PaymentId.CryptoCode == walletId.CryptoCode);
|
||||
if (strategy == null || !ExplorerClientProvider.IsAvailable(strategy.Network))
|
||||
return null;
|
||||
return strategy;
|
||||
}
|
||||
}
|
||||
}
|
114
BTCPayServer/EscrowScriptBuilder.cs
Normal file
114
BTCPayServer/EscrowScriptBuilder.cs
Normal file
@ -0,0 +1,114 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
//<Bob.PubKey>
|
||||
//OP_DEPTH OP_3 OP_EQUAL
|
||||
//OP_IF
|
||||
|
||||
// OP_SWAP
|
||||
// <Alice.PubKey> OP_CHECKSIGVERIFY OP_CODESEPARATOR
|
||||
//OP_ELSE
|
||||
// 0 OP_CLTV OP_DROP
|
||||
//OP_ENDIF
|
||||
//OP_CHECKSIG
|
||||
public class EscrowScriptPubKeyParameters
|
||||
{
|
||||
|
||||
public EscrowScriptPubKeyParameters()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public EscrowScriptPubKeyParameters(PubKey initiator, PubKey receiver, LockTime lockTime)
|
||||
{
|
||||
this.Initiator = initiator;
|
||||
this.Receiver = receiver;
|
||||
this.LockTime = lockTime;
|
||||
}
|
||||
public PubKey Initiator
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public PubKey Receiver
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public LockTime LockTime
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
static readonly Comparer<PubKey> LexicographicComparer = Comparer<PubKey>.Create((a, b) => Comparer<string>.Default.Compare(a?.ToHex(), b?.ToHex()));
|
||||
|
||||
|
||||
// OP_DEPTH 2 OP_EQUAL
|
||||
// OP_IF
|
||||
// <Receiver.PubKey> OP_CHECKSIGVERIFY
|
||||
// OP_ELSE
|
||||
// 0 OP_CLTV OP_DROP
|
||||
// OP_ENDIF
|
||||
// <Initiator.PubKey> OP_CHECKSIG
|
||||
public Script ToRedeemScript()
|
||||
{
|
||||
if (Initiator == null || Receiver == null || LockTime == default(LockTime))
|
||||
throw new InvalidOperationException("Parameters are incomplete");
|
||||
EscrowScriptPubKeyParameters parameters = new EscrowScriptPubKeyParameters();
|
||||
List<Op> ops = new List<Op>();
|
||||
ops.Add(OpcodeType.OP_DEPTH);
|
||||
ops.Add(OpcodeType.OP_2);
|
||||
ops.Add(OpcodeType.OP_EQUAL);
|
||||
ops.Add(OpcodeType.OP_IF);
|
||||
{
|
||||
ops.Add(Op.GetPushOp(Receiver.ToBytes()));
|
||||
ops.Add(OpcodeType.OP_CHECKSIGVERIFY);
|
||||
}
|
||||
ops.Add(OpcodeType.OP_ELSE);
|
||||
{
|
||||
ops.Add(Op.GetPushOp(LockTime));
|
||||
ops.Add(OpcodeType.OP_CHECKLOCKTIMEVERIFY);
|
||||
ops.Add(OpcodeType.OP_DROP);
|
||||
}
|
||||
ops.Add(OpcodeType.OP_ENDIF);
|
||||
ops.Add(Op.GetPushOp(Initiator.ToBytes()));
|
||||
ops.Add(OpcodeType.OP_CHECKSIG);
|
||||
return new Script(ops.ToArray());
|
||||
}
|
||||
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
EscrowScriptPubKeyParameters item = obj as EscrowScriptPubKeyParameters;
|
||||
if (item == null)
|
||||
return false;
|
||||
return ToRedeemScript().Equals(item.ToRedeemScript());
|
||||
}
|
||||
public static bool operator ==(EscrowScriptPubKeyParameters a, EscrowScriptPubKeyParameters b)
|
||||
{
|
||||
if (System.Object.ReferenceEquals(a, b))
|
||||
return true;
|
||||
if (((object)a == null) || ((object)b == null))
|
||||
return false;
|
||||
return a.ToRedeemScript() == b.ToRedeemScript();
|
||||
}
|
||||
|
||||
public static bool operator !=(EscrowScriptPubKeyParameters a, EscrowScriptPubKeyParameters b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return ToRedeemScript().GetHashCode();
|
||||
}
|
||||
|
||||
internal Script ToScript()
|
||||
{
|
||||
return ToRedeemScript().WitHash.ScriptPubKey.Hash.ScriptPubKey;
|
||||
}
|
||||
}
|
||||
}
|
@ -53,6 +53,7 @@ using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using Serilog;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
@ -69,6 +70,8 @@ namespace BTCPayServer.Hosting
|
||||
o.UseOpenIddict<BTCPayOpenIdClient, BTCPayOpenIdAuthorization, OpenIddictScope<string>, BTCPayOpenIdToken, string>();
|
||||
});
|
||||
services.AddHttpClient();
|
||||
services.TryAddSingleton<AtomicSwapRepository>();
|
||||
services.TryAddSingleton<AtomicSwapClientFactory>();
|
||||
services.AddHttpClient(nameof(ExplorerClientProvider), httpClient =>
|
||||
{
|
||||
httpClient.Timeout = Timeout.InfiniteTimeSpan;
|
||||
|
23
BTCPayServer/Services/AtomicSwapClientFactory.cs
Normal file
23
BTCPayServer/Services/AtomicSwapClientFactory.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using BTCPayServer.AtomicSwaps;
|
||||
|
||||
namespace BTCPayServer.Services
|
||||
{
|
||||
public class AtomicSwapClientFactory
|
||||
{
|
||||
public AtomicSwapClientFactory(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
HttpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public IHttpClientFactory HttpClientFactory { get; }
|
||||
|
||||
public AtomicSwapClient Create(Uri serverUri)
|
||||
{
|
||||
var client = new AtomicSwapClient(serverUri);
|
||||
client.SetClient(HttpClientFactory.CreateClient());
|
||||
return client;
|
||||
}
|
||||
}
|
||||
}
|
21
BTCPayServer/Views/Wallets/AtomicSwapDetails.cshtml
Normal file
21
BTCPayServer/Views/Wallets/AtomicSwapDetails.cshtml
Normal file
@ -0,0 +1,21 @@
|
||||
@*@model OfferViewModel
|
||||
@{
|
||||
Layout = "_Layout.cshtml";
|
||||
}
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2 class="section-heading">@Model.Title</h2>
|
||||
<hr class="primary">
|
||||
<p>You need to send <b>@Model.ToSend</b> to <b>@Model.EscrowAddress</b>, if the other party get unresponsive, you will get back money on your <a href="@Model.WalletUrl">BTCPay store's wallet</a> after @Model.RefundTime.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>*@
|
@ -0,0 +1,21 @@
|
||||
@model AtomicSwapDetailsMarkerWaitingTakerViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["Title"] = "Manage atomic swaps";
|
||||
ViewData.SetActivePageAndTitle(WalletsNavPages.AtomicSwaps);
|
||||
}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<p>You will buy <b>@Model.ToReceive</b> against <b>@Model.ToSend</b>, you need to find a partner for this trade.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-10 text-center">
|
||||
<div class="alert alert-warning alert-dismissible" role="alert">
|
||||
<span>Share the following link with the party willing to take your offer</span>
|
||||
<span><b>@Model.OfferUri</b></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Views.Wallets
|
||||
{
|
||||
public class AtomicSwapDetailsMarkerWaitingTakerViewModel
|
||||
{
|
||||
public string ToSend { get; set; }
|
||||
public string ToReceive { get; internal set; }
|
||||
public Uri OfferUri { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
@model AtomicSwapDetailsTakerWaitingTakerViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["Title"] = "Manage atomic swaps";
|
||||
ViewData.SetActivePageAndTitle(WalletsNavPages.AtomicSwaps);
|
||||
}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<p>You will buy <b>@Model.ToReceive</b> against <b>@Model.ToSend</b>, if your peer get unresponsive, you will get back your money on <a asp-action="WalletTransactions" asp-route-walletId="@Model.WalletId">your wallet</a> in <b>@Model.RefundTime.Time.TimeString() (@Model.RefundTime.BlockCount blocks remaining)</b>.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label asp-for="SelectedWallet" class="control-label"></label>
|
||||
<select asp-for="SelectedWallet" asp-items="Model.WalletList" class="form-control"></select>
|
||||
<span asp-validation-for="SelectedWallet" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Accept offer" asp-action="AcceptAtomicSwapOffer" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace BTCPayServer.Views.Wallets
|
||||
{
|
||||
public class AtomicSwapDetailsTakerWaitingTakerViewModel
|
||||
{
|
||||
public string ToSend { get; set; }
|
||||
public string ToReceive { get; internal set; }
|
||||
public string WalletId { get; set; }
|
||||
public Controllers.WalletsController.RefundTime RefundTime { get; set; }
|
||||
|
||||
[Display(Name = "Receive on wallet...")]
|
||||
public string SelectedWallet { get; set; }
|
||||
public SelectList WalletList { get; set; }
|
||||
|
||||
public void SetWalletList(NamedWallet[] namedWallet, string selectedWallet)
|
||||
{
|
||||
var choices = namedWallet.Select(o => new { Name = o.Name, Value = o.WalletId.ToString() }).ToArray();
|
||||
var chosen = choices.FirstOrDefault(f => f.Value == selectedWallet) ?? choices.FirstOrDefault();
|
||||
WalletList = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);
|
||||
SelectedWallet = chosen.Value;
|
||||
}
|
||||
}
|
||||
}
|
25
BTCPayServer/Views/Wallets/AtomicSwapEscrow.cshtml
Normal file
25
BTCPayServer/Views/Wallets/AtomicSwapEscrow.cshtml
Normal file
@ -0,0 +1,25 @@
|
||||
@model AtomicSwapEscrowViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["Title"] = "Manage atomic swaps";
|
||||
ViewData.SetActivePageAndTitle(WalletsNavPages.AtomicSwaps);
|
||||
}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<p>You will buy <b>@Model.ToReceive</b> against <b>@Model.ToSend</b>, if your peer get unresponsive, you will get back your money in <a asp-action="WalletTransactions" asp-route-walletId="@Model.SentToWalletId">your wallet</a> in <b>@Model.RefundTime.Time.TimeString() (@Model.RefundTime.BlockCount blocks remaining)</b>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<form asp-action="WalletSend" method="get">
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Proceed to payment" class="btn btn-primary" />
|
||||
<input type="hidden" name="defaultDestination" value="@Model.EscrowAddress" />
|
||||
<input type="hidden" name="defaultAmount" value="@Model.Amount" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
18
BTCPayServer/Views/Wallets/AtomicSwapEscrowViewModel.cs
Normal file
18
BTCPayServer/Views/Wallets/AtomicSwapEscrowViewModel.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Views.Wallets
|
||||
{
|
||||
public class AtomicSwapEscrowViewModel
|
||||
{
|
||||
public string ToSend { get; set; }
|
||||
public string ToReceive { get; internal set; }
|
||||
public string SentToWalletId { get; set; }
|
||||
public Controllers.WalletsController.RefundTime RefundTime { get; set; }
|
||||
public string SentFromWalletId { get; set; }
|
||||
public string EscrowAddress { get; internal set; }
|
||||
public string Amount { get; set; }
|
||||
}
|
||||
}
|
47
BTCPayServer/Views/Wallets/AtomicSwapList.cshtml
Normal file
47
BTCPayServer/Views/Wallets/AtomicSwapList.cshtml
Normal file
@ -0,0 +1,47 @@
|
||||
@model ListViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["Title"] = "Manage atomic swaps";
|
||||
ViewData.SetActivePageAndTitle(WalletsNavPages.AtomicSwaps);
|
||||
}
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-10 text-center">
|
||||
<partial name="_StatusMessage" for="@TempData["TempDataProperty-StatusMessage"]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<a asp-route-walletId="@this.Context.GetRouteValue("walletId")" asp-action="NewAtomicSwap" class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Make a @Model.CryptoCode offer</a>
|
||||
<a asp-route-walletId="@this.Context.GetRouteValue("walletId")" asp-action="TakeAtomicSwap" class="btn btn-secondary" role="button"><span class="fa fa-sign-in"></span> Take a @Model.CryptoCode offer</a>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align:center">Date</th>
|
||||
<th style="text-align:center">Partner</th>
|
||||
<th style="text-align:right">Send</th>
|
||||
<th style="text-align:right">Receive</th>
|
||||
<th style="text-align:center">Status</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var swap in Model.Swaps)
|
||||
{
|
||||
<tr>
|
||||
<td>@swap.Timestamp.ToTimeAgo()</td>
|
||||
<td>@swap.Partner</td>
|
||||
<td style="text-align:right;">@swap.Sent</td>
|
||||
<td style="text-align:right;">@swap.Received</td>
|
||||
<td style="text-align:center">@swap.Status</td>
|
||||
<td style="text-align:right">
|
||||
<a asp-action="AtomicSwapDetails" asp-route-walletId="@swap.WalletId" asp-route-offerId="@swap.OfferId">Details</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
151
BTCPayServer/Views/Wallets/AtomicSwapRepository.cs
Normal file
151
BTCPayServer/Views/Wallets/AtomicSwapRepository.cs
Normal file
@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.AtomicSwaps;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Views.Wallets
|
||||
{
|
||||
public class AtomicSwapRepository
|
||||
{
|
||||
ConcurrentDictionary<string, AtomicSwapEntry> _Offers = new ConcurrentDictionary<string, AtomicSwapEntry>();
|
||||
|
||||
public Task SaveEntry(WalletId walletId, string offerId, AtomicSwapEntry entry)
|
||||
{
|
||||
entry.Id = offerId;
|
||||
_Offers.TryAdd(offerId, entry);
|
||||
_OfferIdsByWalletId.Add(walletId, offerId);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<AtomicSwapEntry> GetEntry(string offerId)
|
||||
{
|
||||
_Offers.TryGetValue(offerId, out var offer);
|
||||
return Task.FromResult(offer);
|
||||
}
|
||||
|
||||
internal IEnumerable<AtomicSwapEntry> GetEntries(WalletId walletId)
|
||||
{
|
||||
if (!_OfferIdsByWalletId.TryGetValue(walletId, out var offers))
|
||||
return Array.Empty<AtomicSwapEntry>();
|
||||
return _OfferIdsByWalletId[walletId].Select(c => GetEntry(c).Result).OrderByDescending(o => o.Offer.CreatedAt);
|
||||
}
|
||||
|
||||
MultiValueDictionary<WalletId, string> _OfferIdsByWalletId = new MultiValueDictionary<WalletId, string>();
|
||||
|
||||
internal Task UpdateEntry(string offerId, AtomicSwapEntry entry)
|
||||
{
|
||||
_Offers.AddOrUpdate(offerId, entry, (k, oldv) => entry);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public enum XSwapRole
|
||||
{
|
||||
Maker,
|
||||
Taker
|
||||
}
|
||||
|
||||
public enum XSwapStatus
|
||||
{
|
||||
WaitingTaker,
|
||||
WaitingEscrow,
|
||||
WaitingPeerEscrow,
|
||||
WaitingBlocks,
|
||||
CashingOut,
|
||||
Refunding,
|
||||
CashedOut,
|
||||
Refunded
|
||||
}
|
||||
|
||||
public class AtomicSwapEntry
|
||||
{
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
public XSwapRole Role { get; set; }
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
public XSwapStatus Status { get; set; }
|
||||
public AtomicSwapOffer Offer { get; set; }
|
||||
public SentAtomicSwapAsset Sent { get; set; }
|
||||
public ReceivedAtomicSwapAsset Received { get; set; }
|
||||
public string Partner { get; set; }
|
||||
public Uri OtherUri { get; set; }
|
||||
public string Id { get; internal set; }
|
||||
|
||||
public Preimage Preimage { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt160JsonConverter))]
|
||||
public uint160 Hash { get; set; }
|
||||
}
|
||||
|
||||
public class AtomicSwapEscrowData
|
||||
{
|
||||
public AtomicSwapEscrowData(AtomicSwapOfferAsset offerAsset)
|
||||
{
|
||||
Amount = offerAsset.Amount;
|
||||
LockTime = offerAsset.LockTime;
|
||||
CryptoCode = offerAsset.CryptoCode;
|
||||
}
|
||||
public AtomicSwapEscrowData()
|
||||
{
|
||||
|
||||
}
|
||||
public string CryptoCode { get; set; }
|
||||
public Key MyKey { get; set; }
|
||||
public PubKey OtherKey { get; set; }
|
||||
public LockTime LockTime { get; set; }
|
||||
public Money Amount { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
public EscrowScriptPubKeyParameters GetEscrow()
|
||||
{
|
||||
return new EscrowScriptPubKeyParameters(MyKey.PubKey, OtherKey, LockTime);
|
||||
}
|
||||
}
|
||||
|
||||
public class ReceivedAtomicSwapAsset : AtomicSwapEscrowData
|
||||
{
|
||||
public ReceivedAtomicSwapAsset(AtomicSwapOfferAsset offerAsset) : base(offerAsset)
|
||||
{
|
||||
}
|
||||
public ReceivedAtomicSwapAsset()
|
||||
{
|
||||
|
||||
}
|
||||
public Script Destination { get; set; }
|
||||
}
|
||||
|
||||
public class SentAtomicSwapAsset : AtomicSwapEscrowData
|
||||
{
|
||||
public SentAtomicSwapAsset(AtomicSwapOfferAsset offerAsset) : base(offerAsset)
|
||||
{
|
||||
}
|
||||
public SentAtomicSwapAsset()
|
||||
{
|
||||
|
||||
}
|
||||
public Script Refund { get; set; }
|
||||
}
|
||||
|
||||
public class AtomicSwapOffer
|
||||
{
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.UnixDateTimeConverter))]
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public string Rule { get; set; }
|
||||
public AtomicSwapOfferAsset Offer { get; set; }
|
||||
public AtomicSwapOfferAsset Price { get; set; }
|
||||
public Uri MarketMakerUri { get; set; }
|
||||
}
|
||||
|
||||
public class AtomicSwapOfferAsset
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.LockTimeJsonConverter))]
|
||||
public LockTime LockTime { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.MoneyJsonConverter))]
|
||||
public Money Amount { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyJsonConverter))]
|
||||
public PubKey Pubkey { get; set; }
|
||||
}
|
||||
}
|
24
BTCPayServer/Views/Wallets/ListViewModel.cs
Normal file
24
BTCPayServer/Views/Wallets/ListViewModel.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Views.Wallets
|
||||
{
|
||||
public class ListViewModel
|
||||
{
|
||||
public class SwapItem
|
||||
{
|
||||
public string OfferId { get; set; }
|
||||
public string WalletId { get; set; }
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
public string Partner { get; set; }
|
||||
public string Sent { get; set; }
|
||||
public string Received { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string Role { get; set; }
|
||||
}
|
||||
public string CryptoCode { get; set; }
|
||||
public List<SwapItem> Swaps { get; set; } = new List<SwapItem>();
|
||||
}
|
||||
}
|
73
BTCPayServer/Views/Wallets/NewAtomicSwap.cshtml
Normal file
73
BTCPayServer/Views/Wallets/NewAtomicSwap.cshtml
Normal file
@ -0,0 +1,73 @@
|
||||
@model NewViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["Title"] = "Create new atomic swap offer";
|
||||
ViewData.SetActivePageAndTitle(WalletsNavPages.AtomicSwaps);
|
||||
}
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<form method="post">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="SelectedWallet" class="control-label"></label>
|
||||
<select asp-for="SelectedWallet" asp-items="Model.WalletList" class="form-control" onchange="updateCryptoDestination();"></select>
|
||||
<span asp-validation-for="SelectedWallet" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="RateRule" class="control-label"></label>
|
||||
<div class="input-group">
|
||||
<input asp-for="RateRule" class="form-control" />
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" style="display:none;" id="cryptoDestination"></span>
|
||||
</div>
|
||||
</div>
|
||||
<span asp-validation-for="RateRule" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Amount"></label>
|
||||
<div class="input-group">
|
||||
<input asp-for="Amount" class="form-control" />
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">@Model.CryptoCode</span>
|
||||
</div>
|
||||
</div>
|
||||
<span asp-validation-for="Amount" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Spread"></label>
|
||||
<div class="input-group">
|
||||
<input asp-for="Spread" class="form-control" />
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
<span asp-validation-for="Spread" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="New offer" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
<a asp-action="AtomicSwapList">Back to List</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts
|
||||
{
|
||||
<script type="text/javascript">
|
||||
var srvModel = @Html.Raw(Json.Serialize(Model));
|
||||
$(function () {
|
||||
updateCryptoDestination();
|
||||
});
|
||||
function updateCryptoDestination() {
|
||||
var val = $("#SelectedWallet").val();
|
||||
var selected = srvModel.walletData[val];
|
||||
$("#cryptoDestination").text(selected.cryptoCode + " per " + '@Model.CryptoCode');
|
||||
$("#RateRule").val(selected.rule);
|
||||
$("#Spread").val(selected.spread * 100);
|
||||
$("#cryptoDestination").show();
|
||||
}
|
||||
</script>
|
||||
}
|
62
BTCPayServer/Views/Wallets/NewViewModel.cs
Normal file
62
BTCPayServer/Views/Wallets/NewViewModel.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.SqlServer.Server;
|
||||
|
||||
namespace BTCPayServer.Views.Wallets
|
||||
{
|
||||
public class NamedWallet
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
public Rating.RateRule Rule { get; set; }
|
||||
public decimal Spread { get; set; }
|
||||
public DerivationSchemeSettings DerivationStrategy { get; set; }
|
||||
}
|
||||
public class NewViewModel
|
||||
{
|
||||
public class NameWalletObj
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public decimal Spread { get; set; }
|
||||
public string Rule { get; set; }
|
||||
}
|
||||
[Display(Name = "To wallet")]
|
||||
public string SelectedWallet { get; set; }
|
||||
|
||||
[Display(Name = "Amount to receive in the destination wallet (or rating rule)")]
|
||||
[Required()]
|
||||
public string RateRule { get; set; }
|
||||
|
||||
[Range(0, double.MaxValue)]
|
||||
[Display(Name = "Amount to send from this wallet")]
|
||||
public double Amount { get; set; }
|
||||
|
||||
public string CryptoCode { get; set; }
|
||||
|
||||
[Display(Name = "Add a spread on exchange rate of ... %")]
|
||||
[Range(0.0, 100.0)]
|
||||
public double Spread
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Dictionary<WalletId, NameWalletObj> WalletData { get; set; }
|
||||
|
||||
public SelectList WalletList { get; set; }
|
||||
|
||||
public void SetWalletList(NamedWallet[] namedWallet, string selectedWallet)
|
||||
{
|
||||
var choices = namedWallet.Select(o => new { Name = o.Name, Value = o.WalletId.ToString() }).ToArray();
|
||||
var chosen = choices.FirstOrDefault(f => f.Value == selectedWallet) ?? choices.FirstOrDefault();
|
||||
WalletList = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);
|
||||
SelectedWallet = chosen.Value;
|
||||
WalletData = namedWallet.ToDictionary(o => o.WalletId, o => new NameWalletObj() { CryptoCode = o.CryptoCode, Rule = o.Rule.ToString(), Spread = o.Spread });
|
||||
}
|
||||
}
|
||||
}
|
16
BTCPayServer/Views/Wallets/OfferViewModel.cs
Normal file
16
BTCPayServer/Views/Wallets/OfferViewModel.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Views.Wallets
|
||||
{
|
||||
public class OfferViewModel
|
||||
{
|
||||
public string Title { get; set; }
|
||||
|
||||
public string EscrowAddress { get; set; }
|
||||
public string WalletUrl { get; set; }
|
||||
public string RefundTime { get; set; }
|
||||
}
|
||||
}
|
24
BTCPayServer/Views/Wallets/TakeAtomicSwap.cshtml
Normal file
24
BTCPayServer/Views/Wallets/TakeAtomicSwap.cshtml
Normal file
@ -0,0 +1,24 @@
|
||||
@model TakeViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["Title"] = "Take an atomic swap offer";
|
||||
ViewData.SetActivePageAndTitle(WalletsNavPages.AtomicSwaps);
|
||||
}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<form method="post">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="MakerUri" class="control-label"></label>*
|
||||
<input asp-for="MakerUri" class="form-control" />
|
||||
<span asp-validation-for="MakerUri" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Take offer" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
<a asp-action="AtomicSwapList">Back to List</a>
|
||||
</div>
|
||||
</div>
|
18
BTCPayServer/Views/Wallets/TakeViewModel.cs
Normal file
18
BTCPayServer/Views/Wallets/TakeViewModel.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace BTCPayServer.Views.Wallets
|
||||
{
|
||||
public class TakeViewModel
|
||||
{
|
||||
[Display(Name = "Maker's URI")]
|
||||
[UriAttribute]
|
||||
[Required]
|
||||
public string MakerUri { get; set; }
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ namespace BTCPayServer.Views.Wallets
|
||||
Transactions,
|
||||
Rescan,
|
||||
PSBT,
|
||||
Settings
|
||||
Settings,
|
||||
AtomicSwaps
|
||||
}
|
||||
}
|
||||
|
@ -15,5 +15,6 @@
|
||||
{
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.PSBT)" asp-action="WalletPSBT" asp-route-walletId="@this.Context.GetRouteValue("walletId")">PSBT</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Settings)" asp-action="WalletSettings" asp-route-walletId="@this.Context.GetRouteValue("walletId")" id="WalletSettings">Settings</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.AtomicSwaps)" asp-action="AtomicSwapList" asp-route-walletId="@this.Context.GetRouteValue("walletId")">Atomic Swaps</a>
|
||||
}
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user