Compare commits

...

15 Commits

52 changed files with 1848 additions and 738 deletions

View File

@ -1,6 +1,6 @@
# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
[Bb]in/
[Oo]bj/
**/[Bb]in/
**/[Oo]bj/
node_modules/
dist/
@ -121,4 +121,3 @@ bower_components
output
.vs
BTCPayServer.Tests/

1
.gitignore vendored
View File

@ -47,7 +47,6 @@ dlldata.c
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
*_i.c
*_p.c

View File

@ -8,7 +8,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170720-02" />
<PackageReference Include="NBitcoin.TestFramework" Version="1.4.4" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
</ItemGroup>
@ -17,4 +16,13 @@
<ProjectReference Include="..\BTCPayServer\BTCPayServer.csproj" />
</ItemGroup>
<ItemGroup>
<None Update=".dockerignore">
<DependentUpon>Dockerfile</DependentUpon>
</None>
<None Update="docker-compose.yml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBitcoin.Tests;
using NBXplorer;
using NBXplorer.DerivationStrategy;
using System;
@ -59,6 +58,11 @@ namespace BTCPayServer.Tests
get; set;
}
public string Postgres
{
get; set;
}
IWebHost _Host;
public void Start()
{
@ -73,6 +77,8 @@ namespace BTCPayServer.Tests
config.AppendLine($"explorer.url={NBXplorerUri.AbsoluteUri}");
config.AppendLine($"explorer.cookiefile={CookieFile}");
config.AppendLine($"hdpubkey={HDPrivateKey.Neuter().ToString(Network.RegTest)}");
if(Postgres != null)
config.AppendLine($"postgres=" + Postgres);
File.WriteAllText(Path.Combine(_Directory, "settings.config"), config.ToString());
ServerUri = new Uri("http://127.0.0.1:" + port + "/");

View File

@ -0,0 +1,12 @@
FROM microsoft/dotnet:2.0.0-sdk
WORKDIR /app
# caches restore result by copying csproj file separately
COPY BTCPayServer.Tests/BTCPayServer.Tests.csproj BTCPayServer.Tests/BTCPayServer.Tests.csproj
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer/BTCPayServer.csproj
WORKDIR /app/BTCPayServer.Tests
RUN dotnet restore
# copies the rest of your code
COPY . ../.
ENTRYPOINT ["dotnet", "test"]

View File

@ -1,112 +0,0 @@
using NBitcoin;
using NBitcoin.Tests;
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.Text;
using System.Threading;
using Xunit;
namespace BTCPayServer.Tests
{
public class NBXplorerTester : IDisposable
{
private string _Directory;
public NBXplorerTester(string scope)
{
if(scope == null)
throw new ArgumentNullException(nameof(scope));
this._Directory = scope;
}
Process _Process;
public CoreNode Node
{
get; set;
}
public void Start()
{
ProcessLauncher launcher = new ProcessLauncher();
launcher.GoTo("Repositories", true);
if(!launcher.GoTo(new[] { "nxbplorer", "NBXplorer" }))
{
launcher.Run("git", "clone https://github.com/dgarage/NBXplorer nxbplorer");
Assert.True(launcher.GoTo(new[] { "nxbplorer", "NBXplorer" }), "Could not clone nxbplorer");
}
launcher.PushDirectory();
if(!launcher.GoTo(new[] { "bin", "Release", "netcoreapp2.0" }) || !launcher.Exists("NBXplorer.dll"))
{
launcher.PopDirectory();
launcher.Run("git", "pull");
launcher.Run("git", "checkout master");
try
{
//Need to do that or VS insist in adding this repo as submodules :/
Utils.DeleteDirectory(Path.GetFullPath(Path.Combine(launcher.CurrentDirectory, "..", ".git")));
}
catch { }
launcher.Run("dotnet", "build /p:Configuration=Release");
Assert.True(launcher.GoTo(new[] { "bin", "Release", "netcoreapp2.0" }), "Could not build NBXplorer");
launcher.AssertExists("NBXplorer.dll");
}
var port = Utils.FreeTcpPort();
var launcher2 = new ProcessLauncher();
launcher2.GoTo(_Directory, true);
launcher2.GoTo("nbxplorer-datadir", true);
StringBuilder config = new StringBuilder();
config.AppendLine($"regtest=1");
config.AppendLine($"port={port}");
config.AppendLine($"rpc.url={Node.RPCUri.AbsoluteUri}");
config.AppendLine($"rpc.auth={Node.AuthenticationString}");
config.AppendLine($"node.endpoint={Node.NodeEndpoint.Address}:{Node.NodeEndpoint.Port}");
File.WriteAllText(Path.Combine(launcher2.CurrentDirectory, "settings.config"), config.ToString());
_Process = launcher.Start("dotnet", $"NBXplorer.dll --datadir \"{launcher2.CurrentDirectory}\"");
ExplorerClient = new NBXplorer.ExplorerClient(Node.Network, new Uri($"http://127.0.0.1:{port}/"));
CookieFile = Path.Combine(launcher2.CurrentDirectory, ".cookie");
File.Create(CookieFile).Close(); //Will be wipedout when the client starts
ExplorerClient.SetCookieAuth(CookieFile);
try
{
var cancellationSource = new CancellationTokenSource(10000);
ExplorerClient.WaitServerStarted(cancellationSource.Token);
}
catch(OperationCanceledException)
{
Assert.False(_Process.HasExited, "NBXplorer failed to launch");
throw;
}
}
public NBXplorer.ExplorerClient ExplorerClient
{
get; set;
}
public string CookieFile
{
get;
set;
}
public static NBXplorerTester Create([CallerMemberNameAttribute] string scope = null)
{
return new NBXplorerTester(scope);
}
public void Dispose()
{
if(_Process != null && !_Process.HasExited)
_Process.Kill();
}
}
}

View File

@ -0,0 +1,32 @@
# How to run the tests
The tests depends on having a proper environment running with Postgres, Bitcoind, NBxplorer configured.
You can however use the `docker-compose.yml` of this folder to get it running.
```
docker-compose up nbxplorer
```
You can run the tests while it is running through your favorite IDE, or with
```
dotnet test
```
Once you want to stop
```
docker-compose down
```
You can run the tests inside a container by running
```
docker-compose run --rm tests
```
The Bitcoin RPC server is exposed to the host, for example, you can send 0.23111090 BTC to mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf.
```
bitcoin-cli -regtest -rpcport=43782 -rpcuser=ceiwHEbqWI83 -rpcpassword=DwubwWsoo3 sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090
```

View File

@ -2,10 +2,12 @@
using BTCPayServer.Models.AccountViewModels;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBitcoin.Tests;
using NBitcoin.RPC;
using NBitpayClient;
using NBXplorer;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
@ -21,8 +23,6 @@ namespace BTCPayServer.Tests
}
string _Directory;
NodeBuilder _Builder;
public ServerTester(string scope)
{
_Directory = scope;
@ -34,42 +34,45 @@ namespace BTCPayServer.Tests
Utils.DeleteDirectory(_Directory);
if(!Directory.Exists(_Directory))
Directory.CreateDirectory(_Directory);
_Builder = NodeBuilder.Create(_Directory, "0.14.2");
ExplorerNode = _Builder.CreateNode(false);
ExplorerNode.WhiteBind = true;
ExplorerNode.Start();
ExplorerNode.CreateRPCClient().Generate(101);
ExplorerTester = NBXplorerTester.Create(Path.Combine(_Directory, "explorer"));
ExplorerTester.Node = ExplorerNode;
ExplorerTester.Start();
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_RPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), Network);
ExplorerClient = new ExplorerClient(Network, new Uri(GetEnvironment("TESTS_NBXPLORERURL", "http://127.0.0.1:32838/")));
PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay"))
{
NBXplorerUri = ExplorerTester.ExplorerClient.Address,
CookieFile = ExplorerTester.CookieFile
NBXplorerUri = ExplorerClient.Address,
Postgres = GetEnvironment("TESTS_POSTGRES", "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver")
};
PayTester.Start();
}
public TestAccount CreateAccount()
private string GetEnvironment(string variable, string defaultValue)
{
var var = Environment.GetEnvironmentVariable(variable);
return String.IsNullOrEmpty(var) ? defaultValue : var;
}
public TestAccount NewAccount()
{
return new TestAccount(this);
}
public CoreNode ExplorerNode
public RPCClient ExplorerNode
{
get; set;
}
public ExplorerClient ExplorerClient
{
get; set;
}
public BTCPayServerTester PayTester
{
get; set;
}
public NBXplorerTester ExplorerTester
{
get; set;
}
public Network Network
{
get;
@ -80,10 +83,6 @@ namespace BTCPayServer.Tests
{
if(PayTester != null)
PayTester.Dispose();
if(ExplorerTester != null)
ExplorerTester.Dispose();
if(_Builder != null)
_Builder.Dispose();
}
}
}

View File

@ -26,30 +26,53 @@ namespace BTCPayServer.Tests
{
GrantAccessAsync().GetAwaiter().GetResult();
}
public void Register()
{
RegisterAsync().GetAwaiter().GetResult();
}
public BitcoinExtKey ExtKey
{
get; set;
}
public async Task GrantAccessAsync()
{
var extKey = new ExtKey().GetWif(parent.Network);
await RegisterAsync();
var store = await CreateStoreAsync();
var pairingCode = BitPay.RequestClientAuthorization("test", Facade.Merchant);
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
await store.Pair(pairingCode.ToString(), StoreId);
}
public StoresController CreateStore()
{
return CreateStoreAsync().GetAwaiter().GetResult();
}
public async Task<StoresController> CreateStoreAsync()
{
ExtKey = new ExtKey().GetWif(parent.Network);
var store = parent.PayTester.GetController<StoresController>(UserId);
await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" });
StoreId = store.CreatedStoreId;
await store.UpdateStore(StoreId, new StoreViewModel()
{
DerivationScheme = ExtKey.Neuter().ToString() + "-[legacy]",
SpeedPolicy = SpeedPolicy.MediumSpeed
}, "Save");
return store;
}
private async Task RegisterAsync()
{
var account = parent.PayTester.GetController<AccountController>();
await account.Register(new RegisterViewModel()
{
Email = "Bob@toto.com",
Email = Guid.NewGuid() + "@toto.com",
ConfirmPassword = "Kitten0@",
Password = "Kitten0@",
});
UserId = account.RegisteredUserId;
var store = parent.PayTester.GetController<StoresController>(account.RegisteredUserId);
await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" });
StoreId = store.CreatedStoreId;
await store.UpdateStore(StoreId, new StoreViewModel()
{
ExtPubKey = extKey.Neuter().ToString(),
SpeedPolicy = SpeedPolicy.MediumSpeed
});
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
await store.Pair(pairingCode.ToString(), StoreId);
}
public Bitpay BitPay

View File

@ -13,6 +13,8 @@ using BTCPayServer.Servcices.Invoices;
using Newtonsoft.Json;
using System.IO;
using Newtonsoft.Json.Linq;
using BTCPayServer.Controllers;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Tests
{
@ -63,7 +65,7 @@ namespace BTCPayServer.Tests
using(var tester = ServerTester.Create())
{
tester.Start();
var user = tester.CreateAccount();
var user = tester.NewAccount();
user.GrantAccess();
var invoice = user.BitPay.CreateInvoice(new Invoice()
{
@ -85,7 +87,7 @@ namespace BTCPayServer.Tests
Transaction tx = new Transaction();
tx.Outputs.AddRange(request.Details.Outputs.Select(o => new TxOut(o.Amount, o.Script)));
var cashCow = tester.ExplorerNode.CreateRPCClient();
var cashCow = tester.ExplorerNode;
tx = cashCow.FundRawTransaction(tx).Transaction;
tx = cashCow.SignRawTransaction(tx);
@ -104,6 +106,31 @@ namespace BTCPayServer.Tests
}
}
[Fact]
public void CanUseServerInitiatedPairingCode()
{
using(var tester = ServerTester.Create())
{
tester.Start();
var acc = tester.NewAccount();
acc.Register();
acc.CreateStore();
var controller = tester.PayTester.GetController<StoresController>(acc.UserId);
var token = (RedirectToActionResult)controller.CreateToken(acc.StoreId, new Models.StoreViewModels.CreateTokenViewModel()
{
Facade = Facade.Merchant.ToString(),
Label = "bla",
PublicKey = null
}).GetAwaiter().GetResult();
var pairingCode = (string)token.RouteValues["pairingCode"];
acc.BitPay.AuthorizeClient(new PairingCode(pairingCode)).GetAwaiter().GetResult();
Assert.True(acc.BitPay.TestAccess(Facade.Merchant));
}
}
[Fact]
public void CanSendIPN()
{
@ -112,7 +139,7 @@ namespace BTCPayServer.Tests
using(var tester = ServerTester.Create())
{
tester.Start();
var acc = tester.CreateAccount();
var acc = tester.NewAccount();
acc.GrantAccess();
var invoice = acc.BitPay.CreateInvoice(new Invoice()
{
@ -125,7 +152,7 @@ namespace BTCPayServer.Tests
FullNotifications = true
});
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
tester.ExplorerNode.CreateRPCClient().SendToAddress(url.Address, url.Amount);
tester.ExplorerNode.SendToAddress(url.Address, url.Amount);
callbackServer.ProcessNextRequest((ctx) =>
{
var ipn = new StreamReader(ctx.Request.Body).ReadToEnd();
@ -143,7 +170,7 @@ namespace BTCPayServer.Tests
using(var tester = ServerTester.Create())
{
tester.Start();
var user = tester.CreateAccount();
var user = tester.NewAccount();
Assert.False(user.BitPay.TestAccess(Facade.Merchant));
user.GrantAccess();
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
@ -193,7 +220,7 @@ namespace BTCPayServer.Tests
var rate = user.BitPay.GetRates();
var cashCow = tester.ExplorerNode.CreateRPCClient();
var cashCow = tester.ExplorerNode;
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
cashCow.SendToAddress(invoiceAddress, firstPayment);

View File

@ -0,0 +1,50 @@
version: "3"
services:
tests:
build:
context: ..
dockerfile: BTCPayServer.Tests/Dockerfile
environment:
TESTS_RPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3
TESTS_NBXPLORERURL: http://nbxplorer:32838/
TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver
links:
- nbxplorer
nbxplorer:
image: nicolasdorier/nbxplorer:1.0.0.16
ports:
- "32838:32838"
expose:
- "32838"
environment:
NBXPLORER_NETWORK: regtest
NBXPLORER_RPCURL: http://bitcoind:43782/
NBXPLORER_RPCUSER: ceiwHEbqWI83
NBXPLORER_RPCPASSWORD: DwubwWsoo3
NBXPLORER_NODEENDPOINT: bitcoind:39388
NBXPLORER_BIND: 0.0.0.0:32838
NBXPLORER_NOAUTH: 1
links:
- bitcoind
- postgres
bitcoind:
image: nicolasdorier/docker-bitcoin:0.15.0.1
ports:
- "43782:43782"
- "39388:39388"
environment:
BITCOIN_EXTRA_ARGS: "regtest=1\nrpcport=43782\nport=39388\nwhitelist=0.0.0.0/0"
BITCOIN_RPC_USER: ceiwHEbqWI83
BITCOIN_RPC_PASSWORD: DwubwWsoo3
expose:
- "43782"
- "39388"
postgres:
image: postgres:9.6.5
ports:
- "39372:5432"

View File

@ -1,123 +0,0 @@
# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
[Bb]in/
[Oo]bj/
node_modules/
dist/
# mstest test results
TestResults
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
x64/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.log
*.vspscc
*.vssscc
.builds
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*
# NCrunch
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish
# Publish Web Output
*.Publish.xml
# NuGet Packages Directory
packages
# Windows Azure Build Output
csx
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
[Bb]in
[Oo]bj
sql
TestResults
[Tt]est[Rr]esult*
*.Cache
ClientBin
[Ss]tyle[Cc]op.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
src/Rapporteringsregisteret.Web/assets/less/*.css
MetricResults/
*.sln.ide/
_configs/
# vnext stuff
bower_components
output
.vs

View File

@ -8,7 +8,7 @@ namespace BTCPayServer.Authentication
{
public class BitTokenEntity
{
public string Name
public string Facade
{
get; set;
}
@ -16,15 +16,7 @@ namespace BTCPayServer.Authentication
{
get; set;
}
public DateTimeOffset DateCreated
{
get; set;
}
public bool Active
{
get; set;
}
public string PairedId
public string StoreId
{
get; set;
}
@ -46,11 +38,9 @@ namespace BTCPayServer.Authentication
{
return new BitTokenEntity()
{
Active = Active,
DateCreated = DateCreated,
Label = Label,
Name = Name,
PairedId = PairedId,
Facade = Facade,
StoreId = StoreId,
PairingTime = PairingTime,
SIN = SIN,
Value = Value

View File

@ -26,17 +26,17 @@ namespace BTCPayServer.Authentication
get;
set;
}
public DateTimeOffset PairingTime
public DateTimeOffset CreatedTime
{
get;
set;
}
public DateTimeOffset PairingExpiration
public DateTimeOffset Expiration
{
get;
set;
}
public string Token
public string TokenValue
{
get;
set;
@ -44,7 +44,7 @@ namespace BTCPayServer.Authentication
public bool IsExpired()
{
return DateTimeOffset.UtcNow > PairingExpiration;
return DateTimeOffset.UtcNow > Expiration;
}
}
}

View File

@ -1,4 +1,5 @@
using DBreeze;
using BTCPayServer.Data;
using DBreeze;
using NBitcoin;
using NBitcoin.DataEncoders;
using Newtonsoft.Json;
@ -6,175 +7,177 @@ using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using System.Linq;
namespace BTCPayServer.Authentication
{
public class TokenRepository
{
public TokenRepository(DBreezeEngine engine)
ApplicationDbContextFactory _Factory;
public TokenRepository(ApplicationDbContextFactory dbFactory)
{
_Engine = engine;
if(dbFactory == null)
throw new ArgumentNullException(nameof(dbFactory));
_Factory = dbFactory;
}
private readonly DBreezeEngine _Engine;
public DBreezeEngine Engine
public async Task<BitTokenEntity[]> GetTokens(string sin)
{
get
using(var ctx = _Factory.CreateContext())
{
return _Engine;
return (await ctx.PairedSINData
.Where(p => p.SIN == sin)
.ToListAsync())
.Select(p => CreateTokenEntity(p))
.ToArray();
}
}
public Task<BitTokenEntity[]> GetTokens(string sin)
private BitTokenEntity CreateTokenEntity(PairedSINData data)
{
List<BitTokenEntity> tokens = new List<BitTokenEntity>();
using(var tx = _Engine.GetTransaction())
return new BitTokenEntity()
{
tx.ValuesLazyLoadingIsOn = false;
foreach(var row in tx.SelectForward<string, byte[]>($"T_{sin}"))
{
var token = ToObject<BitTokenEntity>(row.Value);
tokens.Add(token);
}
}
return Task.FromResult(tokens.ToArray());
}
public Task<BitTokenEntity> CreateToken(string sin, string tokenName)
{
var token = new BitTokenEntity
{
Name = tokenName,
Value = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32)),
DateCreated = DateTimeOffset.UtcNow
Label = data.Label,
Facade = data.Facade,
Value = data.Id,
SIN = data.SIN,
PairingTime = data.PairingTime,
StoreId = data.StoreDataId
};
using(var tx = _Engine.GetTransaction())
{
tx.Insert<string, byte[]>($"T_{sin}", token.Name, ToBytes(token));
tx.Commit();
}
return Task.FromResult(token);
}
public Task<bool> PairWithAsync(string pairingCode, string pairedId)
public async Task<string> CreatePairingCodeAsync()
{
if(pairedId == null)
throw new ArgumentNullException(nameof(pairedId));
using(var tx = _Engine.GetTransaction())
string pairingCodeId = Encoders.Base58.EncodeData(RandomUtils.GetBytes(6));
using(var ctx = _Factory.CreateContext())
{
var row = tx.Select<string, byte[]>("PairingCodes", pairingCode);
if(row == null || !row.Exists)
return Task.FromResult(false);
tx.RemoveKey<string>("PairingCodes", pairingCode);
try
var now = DateTime.UtcNow;
var expiration = DateTime.UtcNow + TimeSpan.FromMinutes(15);
await ctx.PairingCodes.AddAsync(new PairingCodeData()
{
var pairingEntity = ToObject<PairingCodeEntity>(row.Value);
if(pairingEntity.IsExpired())
return Task.FromResult(false);
row = tx.Select<string, byte[]>($"T_{pairingEntity.SIN}", pairingEntity.Facade);
if(row == null || !row.Exists)
return Task.FromResult(false);
var token = ToObject<BitTokenEntity>(row.Value);
if(token.Active)
return Task.FromResult(false);
token.Active = true;
token.PairedId = pairedId;
token.SIN = pairingEntity.SIN;
token.Label = pairingEntity.Label;
token.PairingTime = DateTimeOffset.UtcNow;
tx.Insert($"TbP_{pairedId}", token.Value, ToBytes(token));
tx.Insert($"T_{pairingEntity.SIN}", pairingEntity.Facade, ToBytes(token));
}
finally
{
tx.Commit();
}
Id = pairingCodeId,
DateCreated = now,
Expiration = expiration,
TokenValue = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32))
});
await ctx.SaveChangesAsync();
}
return Task.FromResult(true);
return pairingCodeId;
}
public Task<BitTokenEntity[]> GetTokensByPairedIdAsync(string pairedId)
public async Task<PairingCodeEntity> UpdatePairingCode(PairingCodeEntity pairingCodeEntity)
{
List<BitTokenEntity> tokens = new List<BitTokenEntity>();
using(var tx = _Engine.GetTransaction())
using(var ctx = _Factory.CreateContext())
{
tx.ValuesLazyLoadingIsOn = false;
foreach(var row in tx.SelectForward<string, byte[]>($"TbP_{pairedId}"))
{
tokens.Add(ToObject<BitTokenEntity>(row.Value));
}
}
return Task.FromResult(tokens.ToArray());
}
public Task<PairingCodeEntity> GetPairingAsync(string pairingCode)
{
using(var tx = _Engine.GetTransaction())
{
var row = tx.Select<string, byte[]>("PairingCodes", pairingCode);
if(row == null || !row.Exists)
return Task.FromResult<PairingCodeEntity>(null);
var pairingEntity = ToObject<PairingCodeEntity>(row.Value);
if(pairingEntity.IsExpired())
return Task.FromResult<PairingCodeEntity>(null);
return Task.FromResult(pairingEntity);
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeEntity.Id);
pairingCode.Label = pairingCodeEntity.Label;
pairingCode.Facade = pairingCodeEntity.Facade;
await ctx.SaveChangesAsync();
return CreatePairingCodeEntity(pairingCode);
}
}
public Task<PairingCodeEntity> AddPairingCodeAsync(PairingCodeEntity pairingCodeEntity)
public async Task<bool> PairWithStoreAsync(string pairingCodeId, string storeId)
{
pairingCodeEntity = Clone(pairingCodeEntity);
pairingCodeEntity.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(6));
using(var tx = _Engine.GetTransaction())
using(var ctx = _Factory.CreateContext())
{
tx.Insert("PairingCodes", pairingCodeEntity.Id, ToBytes(pairingCodeEntity));
tx.Commit();
}
return Task.FromResult(pairingCodeEntity);
}
private byte[] ToBytes<T>(T obj)
{
return ZipUtils.Zip(JsonConvert.SerializeObject(obj));
}
private T ToObject<T>(byte[] value)
{
return JsonConvert.DeserializeObject<T>(ZipUtils.Unzip(value));
}
private T Clone<T>(T obj)
{
return ToObject<T>(ToBytes(obj));
}
public async Task<bool> DeleteToken(string sin, string tokenName, string storeId)
{
var token = await GetToken(sin, tokenName);
if(token == null || (token.PairedId != null && token.PairedId != storeId))
return false;
using(var tx = _Engine.GetTransaction())
{
tx.RemoveKey<string>($"T_{sin}", tokenName);
if(token.PairedId != null)
tx.RemoveKey<string>($"TbP_" + token.PairedId, token.Value);
tx.Commit();
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
if(pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
return false;
pairingCode.StoreDataId = storeId;
await ActivateIfComplete(ctx, pairingCode);
await ctx.SaveChangesAsync();
}
return true;
}
private Task<BitTokenEntity> GetToken(string sin, string tokenName)
public async Task<bool> PairWithSINAsync(string pairingCodeId, string sin)
{
using(var tx = _Engine.GetTransaction())
using(var ctx = _Factory.CreateContext())
{
tx.ValuesLazyLoadingIsOn = true;
var row = tx.Select<string, byte[]>($"T_{sin}", tokenName);
if(row == null || !row.Exists)
return Task.FromResult<BitTokenEntity>(null);
var token = ToObject<BitTokenEntity>(row.Value);
if(!token.Active)
return Task.FromResult<BitTokenEntity>(null);
return Task.FromResult(token);
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
if(pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
return false;
pairingCode.SIN = sin;
await ActivateIfComplete(ctx, pairingCode);
await ctx.SaveChangesAsync();
}
return true;
}
private async Task ActivateIfComplete(ApplicationDbContext ctx, PairingCodeData pairingCode)
{
if(!string.IsNullOrEmpty(pairingCode.SIN) && !string.IsNullOrEmpty(pairingCode.StoreDataId))
{
ctx.PairingCodes.Remove(pairingCode);
await ctx.PairedSINData.AddAsync(new PairedSINData()
{
Id = pairingCode.TokenValue,
PairingTime = DateTime.UtcNow,
Facade = pairingCode.Facade,
Label = pairingCode.Label,
StoreDataId = pairingCode.StoreDataId,
SIN = pairingCode.SIN
});
}
}
public async Task<BitTokenEntity[]> GetTokensByStoreIdAsync(string storeId)
{
using(var ctx = _Factory.CreateContext())
{
return (await ctx.PairedSINData.Where(p => p.StoreDataId == storeId).ToListAsync())
.Select(c => CreateTokenEntity(c))
.ToArray();
}
}
public async Task<PairingCodeEntity> GetPairingAsync(string pairingCode)
{
using(var ctx = _Factory.CreateContext())
{
return CreatePairingCodeEntity(await ctx.PairingCodes.FindAsync(pairingCode));
}
}
private PairingCodeEntity CreatePairingCodeEntity(PairingCodeData data)
{
return new PairingCodeEntity()
{
Facade = data.Facade,
Id = data.Id,
Label = data.Label,
Expiration = data.Expiration,
CreatedTime = data.DateCreated,
TokenValue = data.TokenValue,
SIN = data.SIN
};
}
public async Task<bool> DeleteToken(string tokenId)
{
using(var ctx = _Factory.CreateContext())
{
var token = await ctx.PairedSINData.FindAsync(tokenId);
if(token == null)
return false;
ctx.PairedSINData.Remove(token);
await ctx.SaveChangesAsync();
return true;
}
}
public async Task<BitTokenEntity> GetToken(string tokenId)
{
using(var ctx = _Factory.CreateContext())
{
var token = await ctx.PairedSINData.FindAsync(tokenId);
return CreateTokenEntity(token);
}
}

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<Version>1.0.0.1</Version>
<Version>1.0.0.7</Version>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Build\dockerfiles\**" />
@ -22,9 +22,9 @@
<PackageReference Include="Hangfire.PostgreSql" Version="1.4.8.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
<PackageReference Include="NBitcoin" Version="4.0.0.38" />
<PackageReference Include="NBitpayClient" Version="1.0.0.9" />
<PackageReference Include="NBitpayClient" Version="1.0.0.10" />
<PackageReference Include="DBreeze" Version="1.87.0" />
<PackageReference Include="NBXplorer.Client" Version="1.0.0.12" />
<PackageReference Include="NBXplorer.Client" Version="1.0.0.16" />
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.2" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.13" />

View File

@ -38,7 +38,7 @@ namespace BTCPayServer.Configuration
if(!Explorer.SetCookieAuth(opts.CookieFile))
Explorer.SetNoAuth();
CancellationTokenSource cts = new CancellationTokenSource(10000);
CancellationTokenSource cts = new CancellationTokenSource(30000);
try
{
Logs.Configuration.LogInformation("Trying to connect to explorer " + Explorer.Address.AbsoluteUri);
@ -51,7 +51,6 @@ namespace BTCPayServer.Configuration
}
DBreezeEngine db = new DBreezeEngine(CreateDBPath(opts, "TokensDB"));
_Resources.Add(db);
TokenRepository = new TokenRepository(db);
db = new DBreezeEngine(CreateDBPath(opts, "InvoiceDB"));
_Resources.Add(db);
@ -70,11 +69,6 @@ namespace BTCPayServer.Configuration
}
DBFactory = dbContext;
InvoiceRepository = new InvoiceRepository(dbContext, db, Network);
db = new DBreezeEngine(CreateDBPath(opts, "AddressMapping"));
_Resources.Add(db);
Wallet = new BTCPayWallet(Explorer, db);
}
private static string CreateDBPath(BTCPayServerOptions opts, string name)
@ -104,20 +98,11 @@ namespace BTCPayServer.Configuration
get;
private set;
}
public TokenRepository TokenRepository
{
get; set;
}
public InvoiceRepository InvoiceRepository
{
get;
set;
}
public BTCPayWallet Wallet
{
get;
set;
}
public ApplicationDbContextFactory DBFactory
{
get;

View File

@ -21,7 +21,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet]
[Route("tokens")]
public async Task<GetTokensResponse> GetTokens()
public async Task<GetTokensResponse> Tokens()
{
var tokens = await _TokenRepository.GetTokens(this.GetBitIdentity().SIN);
return new GetTokensResponse(tokens);
@ -29,33 +29,51 @@ namespace BTCPayServer.Controllers
[HttpPost]
[Route("tokens")]
public async Task<DataWrapper<List<PairingCodeResponse>>> GetPairingCode([FromBody] PairingCodeRequest token)
public async Task<DataWrapper<List<PairingCodeResponse>>> Tokens([FromBody] TokenRequest request)
{
var now = DateTimeOffset.UtcNow;
var pairingEntity = new PairingCodeEntity()
PairingCodeEntity pairingEntity = null;
if(string.IsNullOrEmpty(request.PairingCode))
{
Facade = token.Facade,
Label = token.Label,
SIN = token.Id,
PairingTime = now,
PairingExpiration = now + TimeSpan.FromMinutes(15)
};
var grantedToken = await _TokenRepository.CreateToken(token.Id, token.Facade);
pairingEntity.Token = grantedToken.Name;
pairingEntity = await _TokenRepository.AddPairingCodeAsync(pairingEntity);
if(string.IsNullOrEmpty(request.Id) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(request.Id))
throw new BitpayHttpException(400, "'id' property is required");
if(string.IsNullOrEmpty(request.Facade))
throw new BitpayHttpException(400, "'facade' property is required");
var pairingCode = await _TokenRepository.CreatePairingCodeAsync();
await _TokenRepository.PairWithSINAsync(pairingCode, request.Id);
pairingEntity = await _TokenRepository.UpdatePairingCode(new PairingCodeEntity()
{
Id = pairingCode,
Facade = request.Facade,
Label = request.Label
});
}
else
{
var sin = this.GetBitIdentity(false)?.SIN ?? request.Id;
if(string.IsNullOrEmpty(request.Id) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(request.Id))
throw new BitpayHttpException(400, "'id' property is required, alternatively, use BitId");
pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode);
pairingEntity.SIN = sin;
if(!await _TokenRepository.PairWithSINAsync(request.PairingCode, sin))
throw new BitpayHttpException(400, "Unknown pairing code");
}
var pairingCodes = new List<PairingCodeResponse>
{
new PairingCodeResponse()
{
PairingCode = pairingEntity.Id,
PairingExpiration = pairingEntity.PairingExpiration,
DateCreated = pairingEntity.PairingTime,
Facade = grantedToken.Name,
Token = grantedToken.Value,
Label = pairingEntity.Label
}
};
new PairingCodeResponse()
{
PairingCode = pairingEntity.Id,
PairingExpiration = pairingEntity.Expiration,
DateCreated = pairingEntity.CreatedTime,
Facade = pairingEntity.Facade,
Token = pairingEntity.TokenValue,
Label = pairingEntity.Label
}
};
return DataWrapper.Create(pairingCodes);
}
}

View File

@ -83,26 +83,26 @@ namespace BTCPayServer.Controllers
if(facade == null)
throw new ArgumentNullException(nameof(facade));
var actualTokens = (await _TokenRepository.GetTokens(this.GetBitIdentity().SIN)).Where(t => t.Active).ToArray();
var actualTokens = (await _TokenRepository.GetTokens(this.GetBitIdentity().SIN)).ToArray();
actualTokens = actualTokens.SelectMany(t => GetCompatibleTokens(t)).ToArray();
var actualToken = actualTokens.FirstOrDefault(a => a.Value.Equals(expectedToken, StringComparison.Ordinal));
if(expectedToken == null || actualToken == null)
{
Logs.PayServer.LogDebug($"No token found for facade {facade} for SIN {this.GetBitIdentity().SIN}");
throw new BitpayHttpException(401, $"This endpoint does not support the `{actualTokens.Select(a => a.Name).Concat(new[] { "user" }).FirstOrDefault()}` facade");
throw new BitpayHttpException(401, $"This endpoint does not support the `{actualTokens.Select(a => a.Facade).Concat(new[] { "user" }).FirstOrDefault()}` facade");
}
return actualToken;
}
private IEnumerable<BitTokenEntity> GetCompatibleTokens(BitTokenEntity token)
{
if(token.Name == Facade.Merchant.ToString())
if(token.Facade == Facade.Merchant.ToString())
{
yield return token.Clone(Facade.User);
yield return token.Clone(Facade.PointOfSale);
}
if(token.Name == Facade.PointOfSale.ToString())
if(token.Facade == Facade.PointOfSale.ToString())
{
yield return token.Clone(Facade.User);
}
@ -111,7 +111,7 @@ namespace BTCPayServer.Controllers
private async Task<StoreData> FindStore(BitTokenEntity bitToken)
{
var store = await _StoreRepository.FindStore(bitToken.PairedId);
var store = await _StoreRepository.FindStore(bitToken.StoreId);
if(store == null)
throw new BitpayHttpException(401, "Unknown store");
return store;

View File

@ -33,6 +33,8 @@ using BTCPayServer.Servcices.Invoices;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Wallets;
using BTCPayServer.Validations;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc.Routing;
namespace BTCPayServer.Controllers
@ -99,7 +101,7 @@ namespace BTCPayServer.Controllers
entity.DepositAddress = await _Wallet.ReserveAddressAsync(derivationStrategy);
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity);
await _Wallet.MapAsync(entity.DepositAddress, entity.Id);
await _Wallet.MapAsync(entity.DepositAddress.ScriptPubKey, entity.Id);
await _Watcher.WatchAsync(entity.Id);
var resp = entity.EntityToDTO();
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };

View File

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBitpayClient;
using NBXplorer.DerivationStrategy;
using System;
using System.Collections.Generic;
using System.Linq;
@ -28,6 +29,7 @@ namespace BTCPayServer.Controllers
UserManager<ApplicationUser> userManager,
AccessTokenController tokenController,
BTCPayWallet wallet,
Network network,
IHostingEnvironment env)
{
_Repo = repo;
@ -36,7 +38,9 @@ namespace BTCPayServer.Controllers
_TokenController = tokenController;
_Wallet = wallet;
_Env = env;
_Network = network;
}
Network _Network;
BTCPayWallet _Wallet;
AccessTokenController _TokenController;
StoreRepository _Repo;
@ -82,13 +86,17 @@ namespace BTCPayServer.Controllers
StoresViewModel result = new StoresViewModel();
result.StatusMessage = StatusMessage;
var stores = await _Repo.GetStoresByUserId(GetUserId());
foreach(var store in stores)
var balances = stores.Select(async s => string.IsNullOrEmpty(s.DerivationStrategy) ? Money.Zero : await _Wallet.GetBalance(s.DerivationStrategy)).ToArray();
for(int i = 0; i < stores.Length; i++)
{
var store = stores[i];
result.Stores.Add(new StoresViewModel.StoreViewModel()
{
Id = store.Id,
Name = store.StoreName,
WebSite = store.StoreWebsite
WebSite = store.StoreWebsite,
Balance = await balances[i]
});
}
return View(result);
@ -106,7 +114,7 @@ namespace BTCPayServer.Controllers
vm.StoreName = store.StoreName;
vm.StoreWebsite = store.StoreWebsite;
vm.SpeedPolicy = store.SpeedPolicy;
vm.ExtPubKey = store.DerivationStrategy;
vm.DerivationScheme = store.DerivationStrategy;
vm.StatusMessage = StatusMessage;
return View(vm);
}
@ -114,7 +122,7 @@ namespace BTCPayServer.Controllers
[HttpPost]
[ValidateAntiForgeryToken]
[Route("{storeId}")]
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model)
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model, string command)
{
if(!ModelState.IsValid)
{
@ -124,48 +132,64 @@ namespace BTCPayServer.Controllers
if(store == null)
return NotFound();
bool needUpdate = false;
if(store.SpeedPolicy != model.SpeedPolicy)
if(command == "Save")
{
needUpdate = true;
store.SpeedPolicy = model.SpeedPolicy;
}
if(store.StoreName != model.StoreName)
{
needUpdate = true;
store.StoreName = model.StoreName;
}
if(store.StoreWebsite != model.StoreWebsite)
{
needUpdate = true;
store.StoreWebsite = model.StoreWebsite;
}
if(store.DerivationStrategy != model.ExtPubKey)
{
needUpdate = true;
try
bool needUpdate = false;
if(store.SpeedPolicy != model.SpeedPolicy)
{
await _Wallet.TrackAsync(model.ExtPubKey);
store.DerivationStrategy = model.ExtPubKey;
needUpdate = true;
store.SpeedPolicy = model.SpeedPolicy;
}
catch
if(store.StoreName != model.StoreName)
{
ModelState.AddModelError(nameof(model.ExtPubKey), "Invalid Derivation Scheme");
return View(model);
needUpdate = true;
store.StoreName = model.StoreName;
}
if(store.StoreWebsite != model.StoreWebsite)
{
needUpdate = true;
store.StoreWebsite = model.StoreWebsite;
}
}
if(needUpdate)
{
await _Repo.UpdateStore(store);
StatusMessage = "Store successfully updated";
}
if(store.DerivationStrategy != model.DerivationScheme)
{
needUpdate = true;
try
{
await _Wallet.TrackAsync(model.DerivationScheme);
store.DerivationStrategy = model.DerivationScheme;
}
catch
{
ModelState.AddModelError(nameof(model.DerivationScheme), "Invalid Derivation Scheme");
return View(model);
}
}
return RedirectToAction(nameof(UpdateStore), new
if(needUpdate)
{
await _Repo.UpdateStore(store);
StatusMessage = "Store successfully updated";
}
return RedirectToAction(nameof(UpdateStore), new
{
storeId = storeId
});
}
else
{
storeId = storeId
});
var facto = new DerivationStrategyFactory(_Network);
var scheme = facto.Parse(model.DerivationScheme);
var line = scheme.GetLineFor(DerivationFeature.Deposit);
for(int i = 0; i < 10; i++)
{
var address = line.Derive((uint)i);
model.AddressSamples.Add((line.Path.Derive((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(_Network).ToString()));
}
return View(model);
}
}
[HttpGet]
@ -173,11 +197,11 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> ListTokens(string storeId)
{
var model = new TokensViewModel();
var tokens = await _TokenRepository.GetTokensByPairedIdAsync(storeId);
var tokens = await _TokenRepository.GetTokensByStoreIdAsync(storeId);
model.StatusMessage = StatusMessage;
model.Tokens = tokens.Select(t => new TokenViewModel()
{
Facade = t.Name,
Facade = t.Facade,
Label = t.Label,
SIN = t.SIN,
Id = t.Value
@ -195,16 +219,34 @@ namespace BTCPayServer.Controllers
return View(model);
}
var pairingCode = await _TokenController.GetPairingCode(new PairingCodeRequest()
var tokenRequest = new TokenRequest()
{
Facade = model.Facade,
Label = model.Label,
Id = NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(new PubKey(model.PublicKey))
});
Id = model.PublicKey == null ? null : NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(new PubKey(model.PublicKey))
};
string pairingCode = null;
if(model.PublicKey == null)
{
tokenRequest.PairingCode = await _TokenRepository.CreatePairingCodeAsync();
await _TokenRepository.UpdatePairingCode(new PairingCodeEntity()
{
Id = tokenRequest.PairingCode,
Facade = model.Facade,
Label = model.Label,
});
await _TokenRepository.PairWithStoreAsync(tokenRequest.PairingCode, storeId);
pairingCode = tokenRequest.PairingCode;
}
else
{
pairingCode = ((DataWrapper<List<PairingCodeResponse>>)await _TokenController.Tokens(tokenRequest)).Data[0].PairingCode;
}
return RedirectToAction(nameof(RequestPairing), new
{
pairingCode = pairingCode.Data[0].PairingCode,
pairingCode = pairingCode,
selectedStore = storeId
});
}
@ -215,22 +257,21 @@ namespace BTCPayServer.Controllers
{
var model = new CreateTokenViewModel();
model.Facade = "merchant";
if(_Env.IsDevelopment())
{
model.PublicKey = new Key().PubKey.ToHex();
}
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
[Route("{storeId}/Tokens/Delete")]
public async Task<IActionResult> DeleteToken(string storeId, string name, string sin)
public async Task<IActionResult> DeleteToken(string storeId, string tokenId)
{
if(await _TokenRepository.DeleteToken(sin, name, storeId))
StatusMessage = "Token revoked";
else
var token = await _TokenRepository.GetToken(tokenId);
if(token == null ||
token.StoreId != storeId ||
!await _TokenRepository.DeleteToken(tokenId))
StatusMessage = "Failure to revoke this token";
else
StatusMessage = "Token revoked";
return RedirectToAction(nameof(ListTokens));
}
@ -253,7 +294,7 @@ namespace BTCPayServer.Controllers
Id = pairing.Id,
Facade = pairing.Facade,
Label = pairing.Label,
SIN = pairing.SIN,
SIN = pairing.SIN ?? "Server-Initiated Pairing",
SelectedStore = selectedStore ?? stores.FirstOrDefault()?.Id,
Stores = stores.Select(s => new PairingModel.StoreViewModel()
{
@ -270,11 +311,14 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> Pair(string pairingCode, string selectedStore)
{
var store = await _Repo.FindStore(selectedStore, GetUserId());
if(store == null)
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
if(store == null || pairing == null)
return NotFound();
if(pairingCode != null && await _TokenRepository.PairWithAsync(pairingCode, store.Id))
if(pairingCode != null && await _TokenRepository.PairWithStoreAsync(pairingCode, store.Id))
{
StatusMessage = "Pairing is successfull";
if(pairing.SIN == null)
StatusMessage = "Server initiated pairing code: " + pairingCode;
return RedirectToAction(nameof(ListTokens), new
{
storeId = store.Id

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Data
{
public class AddressInvoiceData
{
public string Address
{
get; set;
}
public InvoiceData InvoiceData
{
get; set;
}
public string InvoiceDataId
{
get; set;
}
}
}

View File

@ -46,11 +46,27 @@ namespace BTCPayServer.Data
get; set;
}
public DbSet<AddressInvoiceData> AddressInvoices
{
get; set;
}
public DbSet<SettingData> Settings
{
get; set;
}
public DbSet<PairingCodeData> PairingCodes
{
get; set;
}
public DbSet<PairedSINData> PairedSINData
{
get; set;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var isConfigured = optionsBuilder.Options.Extensions.OfType<RelationalOptionsExtension>().Any();
@ -86,6 +102,18 @@ namespace BTCPayServer.Data
.HasOne(pt => pt.StoreData)
.WithMany(t => t.UserStores)
.HasForeignKey(pt => pt.StoreDataId);
builder.Entity<AddressInvoiceData>()
.HasKey(o => o.Address);
builder.Entity<PairingCodeData>()
.HasKey(o => o.Id);
builder.Entity<PairedSINData>(b =>
{
b.HasIndex(o => o.SIN);
b.HasIndex(o => o.StoreDataId);
});
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Data
{
public class PairedSINData
{
public string Id
{
get; set;
}
public string Facade
{
get; set;
}
public string StoreDataId
{
get; set;
}
public string Label
{
get;
set;
}
public DateTimeOffset PairingTime
{
get;
set;
}
public string SIN
{
get; set;
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Data
{
public class PairingCodeData
{
public string Id
{
get; set;
}
public string Facade
{
get; set;
}
public string StoreDataId
{
get; set;
}
public DateTimeOffset Expiration
{
get;
set;
}
public string Label
{
get;
set;
}
public string SIN
{
get;
set;
}
public DateTime DateCreated
{
get;
set;
}
public string TokenValue
{
get;
set;
}
}
}

View File

@ -1,18 +0,0 @@
FROM microsoft/aspnetcore-build AS builder
WORKDIR /source
# caches restore result by copying csproj file separately
COPY *.csproj .
RUN dotnet restore
# copies the rest of your code
COPY . .
RUN dotnet publish --output /app/ --configuration Release
FROM microsoft/aspnetcore:2.0.0
WORKDIR /app
RUN mkdir /datadir
ENV BTCPAY_DATADIR=/datadir
VOLUME /datadir
COPY --from=builder "/app" .
ENTRYPOINT ["dotnet", "BTCPayServer.dll"]

View File

@ -38,10 +38,10 @@ namespace BTCPayServer
}
public static BitIdentity GetBitIdentity(this Controller controller)
public static BitIdentity GetBitIdentity(this Controller controller, bool throws = true)
{
if(!(controller.User.Identity is BitIdentity))
throw new UnauthorizedAccessException("no-bitid");
return throws ? throw new UnauthorizedAccessException("no-bitid") : (BitIdentity)null;
return (BitIdentity)controller.User.Identity;
}
}

View File

@ -31,6 +31,8 @@ using Microsoft.AspNetCore.Identity;
using BTCPayServer.Models;
using System.Threading.Tasks;
using System.Threading;
using BTCPayServer.Services.Wallets;
using BTCPayServer.Authentication;
namespace BTCPayServer.Hosting
{
@ -106,12 +108,12 @@ namespace BTCPayServer.Hosting
runtime.Configure(o.GetRequiredService<BTCPayServerOptions>());
return runtime;
});
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().TokenRepository);
services.TryAddSingleton<TokenRepository>();
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().InvoiceRepository);
services.TryAddSingleton<Network>(o => o.GetRequiredService<BTCPayServerOptions>().Network);
services.TryAddSingleton<ApplicationDbContextFactory>(o => o.GetRequiredService<BTCPayServerRuntime>().DBFactory);
services.TryAddSingleton<StoreRepository>();
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().Wallet);
services.TryAddSingleton<BTCPayWallet>();
services.TryAddSingleton<CurrencyNameTable>();
services.TryAddSingleton<IFeeProvider>(o => new NBXplorerFeeProvider()
{

View File

@ -92,8 +92,9 @@ namespace BTCPayServer.Hosting
private static async Task HandleBitpayHttpException(HttpContext httpContext, BitpayHttpException ex)
{
httpContext.Response.StatusCode = ex.StatusCode;
using(var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, true))
using(var writer = new StreamWriter(httpContext.Response.Body, new UTF8Encoding(false), 1024, true))
{
httpContext.Response.ContentType = "application/json";
var result = JsonConvert.SerializeObject(new BitpayErrorsModel(ex));
writer.Write(result);
await writer.FlushAsync();

View File

@ -61,15 +61,13 @@ namespace BTCPayServer.Hosting
}
public void ConfigureServices(IServiceCollection services)
{
// Big hack, tests fails because Hangfire fail at initializing at the second test run
services.ConfigureBTCPayServer(Configuration);
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Big hack, tests fails because Hangfire fail at initializing at the second test run
AddHangfireFix(services);
services.AddBTCPayServer();
services.AddMvc();

View File

@ -0,0 +1,392 @@
// <auto-generated />
using BTCPayServer.Data;
using BTCPayServer.Servcices.Invoices;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using System;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20171006013443_AddressMapping")]
partial class AddressMapping
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.Property<string>("Address")
.ValueGeneratedOnAdd();
b.Property<string>("InvoiceDataId");
b.HasKey("Address");
b.HasIndex("InvoiceDataId");
b.ToTable("AddressInvoices");
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("Blob");
b.Property<DateTimeOffset>("Created");
b.Property<string>("CustomerEmail");
b.Property<string>("ExceptionStatus");
b.Property<string>("ItemCode");
b.Property<string>("OrderId");
b.Property<string>("Status");
b.Property<string>("StoreDataId");
b.HasKey("Id");
b.HasIndex("StoreDataId");
b.ToTable("Invoices");
});
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("Blob");
b.Property<string>("InvoiceDataId");
b.HasKey("Id");
b.HasIndex("InvoiceDataId");
b.ToTable("Payments");
});
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("Blob");
b.Property<string>("InvoiceDataId");
b.HasKey("Id");
b.HasIndex("InvoiceDataId");
b.ToTable("RefundAddresses");
});
modelBuilder.Entity("BTCPayServer.Data.SettingData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("Settings");
});
modelBuilder.Entity("BTCPayServer.Data.StoreData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("DerivationStrategy");
b.Property<int>("SpeedPolicy");
b.Property<byte[]>("StoreCertificate");
b.Property<string>("StoreName");
b.Property<string>("StoreWebsite");
b.HasKey("Id");
b.ToTable("Stores");
});
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
{
b.Property<string>("ApplicationUserId");
b.Property<string>("StoreDataId");
b.Property<string>("Role");
b.HasKey("ApplicationUserId", "StoreDataId");
b.HasIndex("StoreDataId");
b.ToTable("UserStore");
});
modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<bool>("RequiresEmailConfirmation");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany()
.HasForeignKey("InvoiceDataId");
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
.WithMany()
.HasForeignKey("StoreDataId");
});
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany("Payments")
.HasForeignKey("InvoiceDataId");
});
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany("RefundAddresses")
.HasForeignKey("InvoiceDataId");
});
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
.WithMany("UserStores")
.HasForeignKey("ApplicationUserId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
.WithMany("UserStores")
.HasForeignKey("StoreDataId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,41 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace BTCPayServer.Migrations
{
public partial class AddressMapping : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AddressInvoices",
columns: table => new
{
Address = table.Column<string>(type: "TEXT", nullable: false),
InvoiceDataId = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AddressInvoices", x => x.Address);
table.ForeignKey(
name: "FK_AddressInvoices_Invoices_InvoiceDataId",
column: x => x.InvoiceDataId,
principalTable: "Invoices",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_AddressInvoices_InvoiceDataId",
table: "AddressInvoices",
column: "InvoiceDataId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AddressInvoices");
}
}
}

View File

@ -0,0 +1,444 @@
// <auto-generated />
using BTCPayServer.Data;
using BTCPayServer.Servcices.Invoices;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using System;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20171010082424_Tokens")]
partial class Tokens
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.Property<string>("Address")
.ValueGeneratedOnAdd();
b.Property<string>("InvoiceDataId");
b.HasKey("Address");
b.HasIndex("InvoiceDataId");
b.ToTable("AddressInvoices");
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("Blob");
b.Property<DateTimeOffset>("Created");
b.Property<string>("CustomerEmail");
b.Property<string>("ExceptionStatus");
b.Property<string>("ItemCode");
b.Property<string>("OrderId");
b.Property<string>("Status");
b.Property<string>("StoreDataId");
b.HasKey("Id");
b.HasIndex("StoreDataId");
b.ToTable("Invoices");
});
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Facade");
b.Property<string>("Label");
b.Property<string>("Name");
b.Property<DateTimeOffset>("PairingTime");
b.Property<string>("SIN");
b.Property<string>("StoreDataId");
b.HasKey("Id");
b.HasIndex("SIN");
b.HasIndex("StoreDataId");
b.ToTable("PairedSINData");
});
modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("DateCreated");
b.Property<DateTimeOffset>("Expiration");
b.Property<string>("Facade");
b.Property<string>("Label");
b.Property<string>("Name");
b.Property<string>("SIN");
b.Property<string>("StoreDataId");
b.Property<string>("TokenValue");
b.HasKey("Id");
b.ToTable("PairingCodes");
});
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("Blob");
b.Property<string>("InvoiceDataId");
b.HasKey("Id");
b.HasIndex("InvoiceDataId");
b.ToTable("Payments");
});
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("Blob");
b.Property<string>("InvoiceDataId");
b.HasKey("Id");
b.HasIndex("InvoiceDataId");
b.ToTable("RefundAddresses");
});
modelBuilder.Entity("BTCPayServer.Data.SettingData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("Settings");
});
modelBuilder.Entity("BTCPayServer.Data.StoreData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("DerivationStrategy");
b.Property<int>("SpeedPolicy");
b.Property<byte[]>("StoreCertificate");
b.Property<string>("StoreName");
b.Property<string>("StoreWebsite");
b.HasKey("Id");
b.ToTable("Stores");
});
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
{
b.Property<string>("ApplicationUserId");
b.Property<string>("StoreDataId");
b.Property<string>("Role");
b.HasKey("ApplicationUserId", "StoreDataId");
b.HasIndex("StoreDataId");
b.ToTable("UserStore");
});
modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<bool>("RequiresEmailConfirmation");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany()
.HasForeignKey("InvoiceDataId");
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
.WithMany()
.HasForeignKey("StoreDataId");
});
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany("Payments")
.HasForeignKey("InvoiceDataId");
});
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany("RefundAddresses")
.HasForeignKey("InvoiceDataId");
});
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
.WithMany("UserStores")
.HasForeignKey("ApplicationUserId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
.WithMany("UserStores")
.HasForeignKey("StoreDataId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,67 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace BTCPayServer.Migrations
{
public partial class Tokens : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PairedSINData",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Facade = table.Column<string>(type: "TEXT", nullable: true),
Label = table.Column<string>(type: "TEXT", nullable: true),
Name = table.Column<string>(type: "TEXT", nullable: true),
PairingTime = table.Column<DateTimeOffset>(nullable: false),
SIN = table.Column<string>(type: "TEXT", nullable: true),
StoreDataId = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PairedSINData", x => x.Id);
});
migrationBuilder.CreateTable(
name: "PairingCodes",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
DateCreated = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTimeOffset>(nullable: false),
Facade = table.Column<string>(type: "TEXT", nullable: true),
Label = table.Column<string>(type: "TEXT", nullable: true),
Name = table.Column<string>(type: "TEXT", nullable: true),
SIN = table.Column<string>(type: "TEXT", nullable: true),
StoreDataId = table.Column<string>(type: "TEXT", nullable: true),
TokenValue = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PairingCodes", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_PairedSINData_SIN",
table: "PairedSINData",
column: "SIN");
migrationBuilder.CreateIndex(
name: "IX_PairedSINData_StoreDataId",
table: "PairedSINData",
column: "StoreDataId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PairedSINData");
migrationBuilder.DropTable(
name: "PairingCodes");
}
}
}

View File

@ -20,6 +20,20 @@ namespace BTCPayServer.Migrations
modelBuilder
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.Property<string>("Address")
.ValueGeneratedOnAdd();
b.Property<string>("InvoiceDataId");
b.HasKey("Address");
b.HasIndex("InvoiceDataId");
b.ToTable("AddressInvoices");
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
{
b.Property<string>("Id")
@ -48,6 +62,58 @@ namespace BTCPayServer.Migrations
b.ToTable("Invoices");
});
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Facade");
b.Property<string>("Label");
b.Property<string>("Name");
b.Property<DateTimeOffset>("PairingTime");
b.Property<string>("SIN");
b.Property<string>("StoreDataId");
b.HasKey("Id");
b.HasIndex("SIN");
b.HasIndex("StoreDataId");
b.ToTable("PairedSINData");
});
modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("DateCreated");
b.Property<DateTimeOffset>("Expiration");
b.Property<string>("Facade");
b.Property<string>("Label");
b.Property<string>("Name");
b.Property<string>("SIN");
b.Property<string>("StoreDataId");
b.Property<string>("TokenValue");
b.HasKey("Id");
b.ToTable("PairingCodes");
});
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
{
b.Property<string>("Id")
@ -286,6 +352,13 @@ namespace BTCPayServer.Migrations
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany()
.HasForeignKey("InvoiceDataId");
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")

View File

@ -39,13 +39,13 @@ namespace BTCPayServer.Models
{
JObject item = new JObject();
jarray.Add(item);
JProperty jProp = new JProperty(token.Name);
JProperty jProp = new JProperty(token.Facade);
item.Add(jProp);
jProp.Value = token.Value;
}
context.HttpContext.Response.Headers.Add("Content-Type", new Microsoft.Extensions.Primitives.StringValues("application/json"));
var str = JsonConvert.SerializeObject(jobj);
using(var writer = new StreamWriter(context.HttpContext.Response.Body, Encoding.UTF8, 1024 * 10, true))
using(var writer = new StreamWriter(context.HttpContext.Response.Body, new UTF8Encoding(false), 1024 * 10, true))
{
await writer.WriteLineAsync(str);
}

View File

@ -27,8 +27,8 @@ namespace BTCPayServer.Models.StoreViewModels
set;
}
[ExtPubKeyValidator]
public string ExtPubKey
[DerivationStrategyValidator]
public string DerivationScheme
{
get; set;
}
@ -39,6 +39,11 @@ namespace BTCPayServer.Models.StoreViewModels
get; set;
}
public List<(string KeyPath, string Address)> AddressSamples
{
get; set;
} = new List<(string KeyPath, string Address)>();
public string StatusMessage
{
get; set;

View File

@ -1,4 +1,5 @@
using System;
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -31,6 +32,10 @@ namespace BTCPayServer.Models.StoreViewModels
{
get; set;
}
public Money Balance
{
get; set;
}
}
}
}

View File

@ -6,7 +6,7 @@ using NBitcoin;
namespace BTCPayServer.Models
{
public class PairingCodeRequest
public class TokenRequest
{
[JsonProperty(PropertyName = "id")]
public string Id
@ -34,6 +34,12 @@ namespace BTCPayServer.Models
{
get; set;
}
[JsonProperty(PropertyName = "pairingCode")]
public string PairingCode
{
get; set;
}
}
public class PairingCodeResponse

View File

@ -52,10 +52,6 @@ namespace BTCPayServer
.Build();
host.StartAsync().GetAwaiter().GetResult();
var urls = host.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
if(urls.Count != 0)
{
OpenBrowser(urls.Select(url => url.Replace("0.0.0.0", "127.0.0.1")).First());
}
foreach(var url in urls)
{
logger.LogInformation("Listening on " + url);
@ -79,29 +75,5 @@ namespace BTCPayServer
loggerProvider.Dispose();
}
}
public static void OpenBrowser(string url)
{
try
{
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}")); // Works ok on windows
}
else if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", url); // Works ok on linux
}
else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", url); // Not tested
}
else
{
}
}
catch { }
}
}
}

View File

@ -0,0 +1,23 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:14139/",
"sslPort": 0
}
},
"profiles": {
"Docker-Regtest": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"BTCPAY_EXPLORERURL": "http://127.0.0.1:32838/",
"BTCPAY_NETWORK": "regtest",
"ASPNETCORE_ENVIRONMENT": "Development",
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
},
"applicationUrl": "http://localhost:14142/"
}
}
}

View File

@ -12,6 +12,7 @@ using System.Threading;
using Microsoft.Extensions.Hosting;
using System.Collections.Concurrent;
using Hangfire;
using BTCPayServer.Services.Wallets;
namespace BTCPayServer.Servcices.Invoices
{
@ -21,11 +22,14 @@ namespace BTCPayServer.Servcices.Invoices
ExplorerClient _ExplorerClient;
DerivationStrategyFactory _DerivationFactory;
InvoiceNotificationManager _NotificationManager;
BTCPayWallet _Wallet;
public InvoiceWatcher(ExplorerClient explorerClient,
InvoiceRepository invoiceRepository,
BTCPayWallet wallet,
InvoiceNotificationManager notificationManager)
{
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
_ExplorerClient = explorerClient ?? throw new ArgumentNullException(nameof(explorerClient));
_DerivationFactory = new DerivationStrategyFactory(_ExplorerClient.Network);
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
@ -93,10 +97,18 @@ namespace BTCPayServer.Servcices.Invoices
{
var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy);
changes = await _ExplorerClient.SyncAsync(strategy, changes, false, _Cts.Token).ConfigureAwait(false);
var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray();
var invoiceIds = utxos.Select(u => _Wallet.GetInvoiceId(u.Output.ScriptPubKey)).ToArray();
utxos =
utxos
.Where((u,i) => invoiceIds[i].GetAwaiter().GetResult() == invoice.Id)
.ToArray();
shouldWait = false; //should not wait, Sync is blocking call
List<Coin> receivedCoins = new List<Coin>();
foreach(var received in changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs))
foreach(var received in utxos)
if(received.Output.ScriptPubKey == invoice.DepositAddress.ScriptPubKey)
receivedCoins.Add(new Coin(received.Outpoint, received.Output));

View File

@ -1,5 +1,4 @@
using DBreeze;
using NBitcoin;
using NBitcoin;
using NBXplorer;
using NBXplorer.DerivationStrategy;
using System;
@ -7,56 +6,61 @@ using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Data;
namespace BTCPayServer.Services.Wallets
{
public class BTCPayWallet
{
public class BTCPayWallet
{
private ExplorerClient _Client;
private DBreezeEngine _Engine;
private Serializer _Serializer;
private DerivationStrategyFactory _DerivationStrategyFactory;
ApplicationDbContextFactory _DBFactory;
public BTCPayWallet(ExplorerClient client, DBreezeEngine dbreeze)
public BTCPayWallet(ExplorerClient client, ApplicationDbContextFactory factory)
{
if(client == null)
throw new ArgumentNullException(nameof(client));
if(dbreeze == null)
throw new ArgumentNullException(nameof(dbreeze));
if(factory == null)
throw new ArgumentNullException(nameof(factory));
_Client = client;
_Engine = dbreeze;
_DBFactory = factory;
_Serializer = new NBXplorer.Serializer(_Client.Network);
_DerivationStrategyFactory = new DerivationStrategyFactory(_Client.Network);
}
public async Task<BitcoinAddress> ReserveAddressAsync(string walletIdentifier)
{
var pathInfo = await _Client.GetUnusedAsync(_DerivationStrategyFactory.Parse(walletIdentifier), DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
using(var tx = _Engine.GetTransaction())
{
var pathInfoBytes = ToBytes(pathInfo);
tx.Insert(AddressToKeyInfo, pathInfo.Address.ToString(), pathInfoBytes);
tx.Commit();
}
return pathInfo.Address;
return pathInfo.ScriptPubKey.GetDestinationAddress(_DerivationStrategyFactory.Network);
}
public async Task TrackAsync(string walletIdentifier)
public Task TrackAsync(string walletIdentifier)
{
await _Client.SyncAsync(_DerivationStrategyFactory.Parse(walletIdentifier), null, null, true).ConfigureAwait(false);
return _Client.TrackAsync(_DerivationStrategyFactory.Parse(walletIdentifier));
}
const string AddressToId = "AtI";
const string AddressToKeyInfo = "AtK";
public Task MapAsync(BitcoinAddress address, string id)
public async Task<string> GetInvoiceId(Script scriptPubKey)
{
using(var tx = _Engine.GetTransaction())
using(var db = _DBFactory.CreateContext())
{
tx.Insert(AddressToId, address.ToString(), id);
tx.Commit();
var result = await db.AddressInvoices.FindAsync(scriptPubKey.Hash.ToString());
return result?.InvoiceDataId;
}
}
public async Task MapAsync(Script address, string invoiceId)
{
using(var db = _DBFactory.CreateContext())
{
db.AddressInvoices.Add(new AddressInvoiceData()
{
Address = address.Hash.ToString(),
InvoiceDataId = invoiceId
});
await db.SaveChangesAsync();
}
return Task.FromResult(true);
}
private byte[] ToBytes<T>(T obj)
@ -69,5 +73,13 @@ namespace BTCPayServer.Services.Wallets
var tasks = transactions.Select(t => _Client.BroadcastAsync(t)).ToArray();
return Task.WhenAll(tasks);
}
public async Task<Money> GetBalance(string derivationStrategy)
{
var result = await _Client.SyncAsync(_DerivationStrategyFactory.Parse(derivationStrategy), null, true);
return result.Confirmed.UTXOs.Select(u => u.Output.Value)
.Concat(result.Unconfirmed.UTXOs.Select(u => u.Output.Value))
.Sum();
}
}
}

View File

@ -1,4 +1,5 @@
using NBitcoin;
using NBXplorer.DerivationStrategy;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@ -6,7 +7,7 @@ using System.Text;
namespace BTCPayServer.Validations
{
public class ExtPubKeyValidatorAttribute : ValidationAttribute
public class DerivationStrategyValidatorAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
@ -19,7 +20,7 @@ namespace BTCPayServer.Validations
return new ValidationResult("No Network specified");
try
{
new BitcoinExtPubKey((string)value, network);
new DerivationStrategyFactory(network).Parse((string)value);
return ValidationResult.Success;
}
catch(Exception ex)

View File

@ -17,6 +17,7 @@
</div>
<div class="form-group">
<label asp-for="PublicKey"></label>
<small class="text-muted">Keep empty for server-initiated pairing</small>
<input asp-for="PublicKey" class="form-control" />
<span asp-validation-for="PublicKey" class="text-danger"></span>
</div>

View File

@ -27,6 +27,7 @@
<tr>
<th>Name</th>
<th>Website</th>
<th>Balance</th>
<th>Actions</th>
</tr>
</thead>
@ -40,6 +41,7 @@
{
<a href="@store.WebSite">@store.WebSite</a>
}</td>
<td>@store.Balance</td>
<td><a asp-action="UpdateStore" asp-route-storeId="@store.Id">Settings</a></td>
</tr>
}

View File

@ -27,8 +27,7 @@
<td>@token.Facade</td>
<td>
<form asp-action="DeleteToken" method="post">
<input type="hidden" name="name" value="@token.Facade">
<input type="hidden" name="sin" value="@token.SIN">
<input type="hidden" name="tokenId" value="@token.Id">
<button type="submit" class="btn btn-danger" role="button">Revoke</button>
</form>
</td>

View File

@ -36,11 +36,77 @@
<span asp-validation-for="SpeedPolicy" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ExtPubKey"></label>
<input asp-for="ExtPubKey" class="form-control" />
<span asp-validation-for="ExtPubKey" class="text-danger"></span>
<h5>Derivation Scheme</h5>
@if(Model.AddressSamples.Count == 0)
{
<span>The DerivationScheme represents the destination of the funds received by your invoice. It is generated by your wallet software. Please, verify that you are generating the right addresses by clicking on 'Check ExtPubKey'</span>
}
</div>
<button type="submit" class="btn btn-default">Save</button>
<div class="form-group">
<input asp-for="DerivationScheme" class="form-control" />
<span asp-validation-for="DerivationScheme" class="text-danger"></span>
</div>
<div class="form-group">
@if(Model.AddressSamples.Count == 0)
{
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Address type</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td>P2WPKH</td>
<td>xpub</td>
</tr>
<tr>
<td>P2SH-P2WPKH</td>
<td>xpub-[p2sh]</td>
</tr>
<tr>
<td>P2SH</td>
<td>xpub-[legacy]</td>
</tr>
<tr>
<td>Multi-sig P2WSH</td>
<td>2-of-xpub1-xpub2</td>
</tr>
<tr>
<td>Multi-sig P2SH-P2WSH</td>
<td>2-of-xpub1-xpub2-[p2sh]</td>
</tr>
<tr>
<td>Multi-sig P2SH</td>
<td>2-of-xpub1-xpub2-[legacy]</td>
</tr>
</tbody>
</table>
}
else
{
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Key path</th>
<th>Address</th>
</tr>
</thead>
<tbody>
@foreach(var sample in Model.AddressSamples)
{
<tr>
<td>@sample.KeyPath</td>
<td>@sample.Address</td>
</tr>
}
</tbody>
</table>
}
</div>
<button name="command" type="submit" class="btn btn-success" value="Save">Save</button>
<button name="command" type="submit" class="btn btn-default" value="Check">Check ExtPubKey</button>
</form>
</div>
</div>

View File

@ -1,48 +0,0 @@
version: "3"
services:
btcpayserver:
ports:
- 8080:49392
expose:
- "49392"
build:
context: .
dockerfile: DockerFile
environment:
BTCPAY_POSTGRES: "User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver"
BTCPAY_NETWORK: regtest
BTCPAY_EXPLORERURL: http://nbxplorer:32838/
BTCPAY_BIND: 0.0.0.0:49392
links:
- nbxplorer
- postgres
nbxplorer:
image: nicolasdorier/nbxplorer:1.0.0.12
expose:
- "32838"
environment:
NBXPLORER_NETWORK: regtest
NBXPLORER_RPCURL: http://bitcoind:43782/
NBXPLORER_RPCUSER: ceiwHEbqWI83
NBXPLORER_RPCPASSWORD: DwubwWsoo3
NBXPLORER_NODEENDPOINT: bitcoind:8332
NBXPLORER_BIND: 0.0.0.0:32838
NBXPLORER_NOAUTH: 1
links:
- bitcoind
bitcoind:
image: nicolasdorier/bitcoin:0.15.0.1
environment:
BITCOIN_EXTRA_ARGS: "regtest=1\nrpcport=43782\nport=8332"
BITCOIN_RPC_USER: ceiwHEbqWI83
BITCOIN_RPC_PASSWORD: DwubwWsoo3
expose:
- "43782"
- "8332"
postgres:
image: postgres:9.6.5

View File

@ -1,2 +0,0 @@
docker-compose -f docker-compose.regtest.yml down
docker-compose -f docker-compose.regtest.yml up --force-recreate --build

View File

@ -1,10 +1,10 @@
FROM microsoft/aspnetcore-build AS builder
WORKDIR /source
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer/BTCPayServer.csproj
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer.csproj
# Cache some dependencies
RUN cd BTCPayServer && dotnet restore && cd ..
COPY . .
RUN cd BTCPayServer && dotnet publish --output /app/ --configuration Release
RUN dotnet restore
COPY BTCPayServer/. .
RUN dotnet publish --output /app/ --configuration Release
FROM microsoft/aspnetcore:2.0.0
WORKDIR /app

View File

@ -1,48 +0,0 @@
version: "3"
services:
btcpayserver:
ports:
- 8080:49392
expose:
- "49392"
build:
context: .
dockerfile: DockerFile
environment:
BTCPAY_POSTGRES: "User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver"
BTCPAY_NETWORK: regtest
BTCPAY_EXPLORERURL: http://nbxplorer:32838/
BTCPAY_BIND: 0.0.0.0:49392
links:
- nbxplorer
- postgres
nbxplorer:
image: nicolasdorier/nbxplorer:1.0.0.12
expose:
- "32838"
environment:
NBXPLORER_NETWORK: regtest
NBXPLORER_RPCURL: http://bitcoind:43782/
NBXPLORER_RPCUSER: ceiwHEbqWI83
NBXPLORER_RPCPASSWORD: DwubwWsoo3
NBXPLORER_NODEENDPOINT: bitcoind:8332
NBXPLORER_BIND: 0.0.0.0:32838
NBXPLORER_NOAUTH: 1
links:
- bitcoind
bitcoind:
image: nicolasdorier/bitcoin:0.15.0.1
environment:
BITCOIN_EXTRA_ARGS: "regtest=1\nrpcport=43782\nport=8332"
BITCOIN_RPC_USER: ceiwHEbqWI83
BITCOIN_RPC_PASSWORD: DwubwWsoo3
expose:
- "43782"
- "8332"
postgres:
image: postgres:9.6.5