diff --git a/BTCPayServer.Common/BTCPayServer.Common.csproj b/BTCPayServer.Common/BTCPayServer.Common.csproj
index 53ebecf8d..555b7ed40 100644
--- a/BTCPayServer.Common/BTCPayServer.Common.csproj
+++ b/BTCPayServer.Common/BTCPayServer.Common.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/BTCPayServer.Data/Data/WalletObjectData.cs b/BTCPayServer.Data/Data/WalletObjectData.cs
index e73e087eb..1cc0ea954 100644
--- a/BTCPayServer.Data/Data/WalletObjectData.cs
+++ b/BTCPayServer.Data/Data/WalletObjectData.cs
@@ -21,7 +21,7 @@ namespace BTCPayServer.Data
public const string PayjoinExposed = "pj-exposed";
public const string Payout = "payout";
public const string PullPayment = "pull-payment";
- public const string Script = "script";
+ public const string Address = "address";
public const string Utxo = "utxo";
}
public string WalletId { get; set; }
diff --git a/BTCPayServer.Tests/FastTests.cs b/BTCPayServer.Tests/FastTests.cs
index 2a8378554..d1e506f08 100644
--- a/BTCPayServer.Tests/FastTests.cs
+++ b/BTCPayServer.Tests/FastTests.cs
@@ -678,7 +678,7 @@ namespace BTCPayServer.Tests
parser = new DerivationSchemeParser(networkProvider.BTC);
var od = "wpkh([8bafd160/49h/0h/0h]xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw/0/*)#9x4vkw48";
(strategyBase, rootedKeyPath) = parser.ParseOutputDescriptor(od);
- Assert.Equal(1, rootedKeyPath.Length);
+ Assert.Single(rootedKeyPath);
Assert.IsType(strategyBase);
Assert.True(((DirectDerivationStrategy)strategyBase).Segwit);
diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs
index f1f17bb2b..f09e1b8b3 100644
--- a/BTCPayServer.Tests/GreenfieldAPITests.cs
+++ b/BTCPayServer.Tests/GreenfieldAPITests.cs
@@ -1936,7 +1936,7 @@ namespace BTCPayServer.Tests
}
});
var invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", newInvoice.Id), false);
- Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "script");
+ Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "address");
Assert.EndsWith($"/i/{newInvoice.Id}", newInvoice.CheckoutLink);
var controller = tester.PayTester.GetController(user.UserId, user.StoreId);
@@ -1972,7 +1972,7 @@ namespace BTCPayServer.Tests
invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() { Amount = 1, Currency = "USD" });
invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", invoice.Id), false);
- Assert.DoesNotContain(invoiceObject.Links.Select(l => l.Type), t => t == "script");
+ Assert.DoesNotContain(invoiceObject.Links.Select(l => l.Type), t => t == "address");
paymentMethods = await client.GetInvoicePaymentMethods(store.Id, invoice.Id);
@@ -1981,7 +1981,7 @@ namespace BTCPayServer.Tests
await client.ActivateInvoicePaymentMethod(user.StoreId, invoice.Id,
paymentMethods.First().PaymentMethod);
invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", invoice.Id), false);
- Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "script");
+ Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "address");
paymentMethods = await client.GetInvoicePaymentMethods(store.Id, invoice.Id);
Assert.Single(paymentMethods);
@@ -2046,10 +2046,10 @@ namespace BTCPayServer.Tests
var pm = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id));
Assert.Single(pm.Payments);
Assert.Equal(-0.0001m, pm.Due);
- });
- invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", invoice.Id), false);
- Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "tx");
+ invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", invoice.Id), false);
+ Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "tx");
+ });
}
[Fact(Timeout = 60 * 20 * 1000)]
diff --git a/BTCPayServer.Tests/docker-compose.altcoins.yml b/BTCPayServer.Tests/docker-compose.altcoins.yml
index 23916e7ad..c2476387d 100644
--- a/BTCPayServer.Tests/docker-compose.altcoins.yml
+++ b/BTCPayServer.Tests/docker-compose.altcoins.yml
@@ -90,7 +90,7 @@ services:
expose:
- "4444"
nbxplorer:
- image: nicolasdorier/nbxplorer:2.3.40
+ image: nicolasdorier/nbxplorer:2.3.54
restart: unless-stopped
ports:
- "32838:32838"
diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml
index a4d689fc7..133bc5650 100644
--- a/BTCPayServer.Tests/docker-compose.yml
+++ b/BTCPayServer.Tests/docker-compose.yml
@@ -87,7 +87,7 @@ services:
expose:
- "4444"
nbxplorer:
- image: nicolasdorier/nbxplorer:2.3.40
+ image: nicolasdorier/nbxplorer:2.3.54
restart: unless-stopped
ports:
- "32838:32838"
diff --git a/BTCPayServer/Controllers/UIInvoiceController.cs b/BTCPayServer/Controllers/UIInvoiceController.cs
index 51eb14c3a..87f986fad 100644
--- a/BTCPayServer/Controllers/UIInvoiceController.cs
+++ b/BTCPayServer/Controllers/UIInvoiceController.cs
@@ -391,8 +391,8 @@ namespace BTCPayServer.Controllers
await _walletRepository.EnsureWalletObjectLink(
new WalletObjectId(
walletId,
- WalletObjectData.Types.Script,
- address.ScriptPubKey.ToHex()),
+ WalletObjectData.Types.Address,
+ address.ToString()),
new WalletObjectId(
walletId,
WalletObjectData.Types.Invoice,
diff --git a/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs b/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs
index 620cdd6c7..a6663e510 100644
--- a/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs
+++ b/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs
@@ -16,6 +16,7 @@ using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Labels;
using BTCPayServer.Services.PaymentRequests;
using NBitcoin;
+using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -25,9 +26,12 @@ namespace BTCPayServer.HostedServices
{
private readonly WalletRepository _walletRepository;
- public TransactionLabelMarkerHostedService(EventAggregator eventAggregator, WalletRepository walletRepository, Logs logs) :
+ public BTCPayNetworkProvider NetworkProvider { get; }
+
+ public TransactionLabelMarkerHostedService(BTCPayNetworkProvider networkProvider, EventAggregator eventAggregator, WalletRepository walletRepository, Logs logs) :
base(eventAggregator, logs)
{
+ NetworkProvider = networkProvider;
_walletRepository = walletRepository;
}
@@ -44,36 +48,40 @@ namespace BTCPayServer.HostedServices
// any utxo or script object matching it.
// If we find, then we create a link between them and the tx object.
case NewOnChainTransactionEvent transactionEvent:
- {
- var txHash = transactionEvent.NewTransactionEvent.TransactionData.TransactionHash.ToString();
-
- // find all wallet objects that fit this transaction
- // that means see if there are any utxo objects that match in/outs and scripts/addresses that match outs
- var matchedObjects = transactionEvent.NewTransactionEvent.TransactionData.Transaction.Inputs
- .Select(txIn => new ObjectTypeId(WalletObjectData.Types.Utxo, txIn.PrevOut.ToString()))
- .Concat(transactionEvent.NewTransactionEvent.TransactionData.Transaction.Outputs.AsIndexedOutputs().SelectMany(txOut =>
-
- new[]{
- new ObjectTypeId(WalletObjectData.Types.Script,txOut.TxOut.ScriptPubKey.ToHex()),
- new ObjectTypeId(WalletObjectData.Types.Utxo,txOut.ToCoin().Outpoint.ToString())
-
- } )).Distinct().ToArray();
-
- var objs = await _walletRepository.GetWalletObjects(new GetWalletObjectsQuery(){TypesIds = matchedObjects});
-
- foreach (var walletObjectDatas in objs.GroupBy(data => data.Key.WalletId))
{
- var txWalletObject = new WalletObjectId(walletObjectDatas.Key,
- WalletObjectData.Types.Tx, txHash);
- await _walletRepository.EnsureWalletObject(txWalletObject);
- foreach (var walletObjectData in walletObjectDatas)
- {
- await _walletRepository.EnsureWalletObjectLink(txWalletObject, walletObjectData.Key);
- }
- }
+ var network = NetworkProvider.GetNetwork(transactionEvent.CryptoCode);
+ var derivation = transactionEvent.NewTransactionEvent.DerivationStrategy;
+ if (network is null || derivation is null)
+ break;
+ var txHash = transactionEvent.NewTransactionEvent.TransactionData.TransactionHash.ToString();
- break;
- }
+ // find all wallet objects that fit this transaction
+ // that means see if there are any utxo objects that match in/outs and scripts/addresses that match outs
+ var matchedObjects = transactionEvent.NewTransactionEvent.TransactionData.Transaction.Inputs
+ .Select(txIn => new ObjectTypeId(WalletObjectData.Types.Utxo, txIn.PrevOut.ToString()))
+ .Concat(transactionEvent.NewTransactionEvent.Outputs.SelectMany(txOut =>
+
+ new[]{
+ new ObjectTypeId(WalletObjectData.Types.Address, GetAddress(derivation, txOut, network).ToString()),
+ new ObjectTypeId(WalletObjectData.Types.Utxo, new OutPoint(transactionEvent.NewTransactionEvent.TransactionData.TransactionHash, (uint)txOut.Index).ToString())
+
+ })).Distinct().ToArray();
+
+ var objs = await _walletRepository.GetWalletObjects(new GetWalletObjectsQuery() { TypesIds = matchedObjects });
+
+ foreach (var walletObjectDatas in objs.GroupBy(data => data.Key.WalletId))
+ {
+ var txWalletObject = new WalletObjectId(walletObjectDatas.Key,
+ WalletObjectData.Types.Tx, txHash);
+ await _walletRepository.EnsureWalletObject(txWalletObject);
+ foreach (var walletObjectData in walletObjectDatas)
+ {
+ await _walletRepository.EnsureWalletObjectLink(txWalletObject, walletObjectData.Key);
+ }
+ }
+
+ break;
+ }
case InvoiceEvent {Name: InvoiceEvent.ReceivedPayment} invoiceEvent when
invoiceEvent.Payment.GetPaymentMethodId()?.PaymentType == BitcoinPaymentType.Instance &&
invoiceEvent.Payment.GetCryptoPaymentData() is BitcoinLikePaymentData bitcoinLikePaymentData:
@@ -98,5 +106,11 @@ namespace BTCPayServer.HostedServices
}
}
}
+
+ private BitcoinAddress GetAddress(DerivationStrategyBase derivationStrategy, NBXplorer.Models.MatchedOutput txOut, BTCPayNetwork network)
+ {
+ // Old version of NBX doesn't give address in the event, so we need to guess
+ return (txOut.Address ?? network.NBXplorerNetwork.CreateAddress(derivationStrategy, txOut.KeyPath, txOut.ScriptPubKey));
+ }
}
}
diff --git a/BTCPayServer/Services/InvoiceActivator.cs b/BTCPayServer/Services/InvoiceActivator.cs
index bc1692bad..e3643c8ff 100644
--- a/BTCPayServer/Services/InvoiceActivator.cs
+++ b/BTCPayServer/Services/InvoiceActivator.cs
@@ -66,8 +66,8 @@ namespace BTCPayServer.Services
await _walletRepository.EnsureWalletObjectLink(
new WalletObjectId(
walletId,
- WalletObjectData.Types.Script,
- address.ScriptPubKey.ToHex()),
+ WalletObjectData.Types.Address,
+ address.ToString()),
new WalletObjectId(
walletId,
WalletObjectData.Types.Invoice,
diff --git a/BTCPayServer/Services/WalletRepository.cs b/BTCPayServer/Services/WalletRepository.cs
index 8689edd1f..f33d973c0 100644
--- a/BTCPayServer/Services/WalletRepository.cs
+++ b/BTCPayServer/Services/WalletRepository.cs
@@ -52,16 +52,11 @@ namespace BTCPayServer.Services
public string[]? Ids { get; set; }
public bool IncludeNeighbours { get; set; } = true;
public bool UseInefficientPath { get; set; }
-
- public static ObjectTypeId Get(Script script)
- {
- return new ObjectTypeId(WalletObjectData.Types.Script, script.ToHex());
- }
public static IEnumerable Get(ReceivedCoin coin)
{
yield return new ObjectTypeId(WalletObjectData.Types.Tx, coin.OutPoint.Hash.ToString());
- yield return Get(coin.ScriptPubKey);
+ yield return new ObjectTypeId(WalletObjectData.Types.Address, coin.Address.ToString());
yield return new ObjectTypeId(WalletObjectData.Types.Utxo, coin.OutPoint.ToString());
}
}
@@ -250,7 +245,7 @@ namespace BTCPayServer.Services
{
var wos = await GetWalletObjects(
new GetWalletObjectsQuery(walletId, WalletObjectData.Types.Tx, transactionIds));
- return await GetWalletTransactionsInfoCore(walletId, wos);
+ return GetWalletTransactionsInfoCore(walletId, wos);
}
public async Task> GetWalletTransactionsInfo(WalletId walletId,
@@ -259,10 +254,10 @@ namespace BTCPayServer.Services
var wos = await GetWalletObjects(
new GetWalletObjectsQuery(walletId, transactionIds));
- return await GetWalletTransactionsInfoCore(walletId, wos);
+ return GetWalletTransactionsInfoCore(walletId, wos);
}
- private async Task> GetWalletTransactionsInfoCore(WalletId walletId,
+ private Dictionary GetWalletTransactionsInfoCore(WalletId walletId,
Dictionary wos)
{
diff --git a/BTCPayServer/Services/Wallets/BTCPayWallet.cs b/BTCPayServer/Services/Wallets/BTCPayWallet.cs
index 7e9a67028..e091d240b 100644
--- a/BTCPayServer/Services/Wallets/BTCPayWallet.cs
+++ b/BTCPayServer/Services/Wallets/BTCPayWallet.cs
@@ -27,6 +27,7 @@ namespace BTCPayServer.Services.Wallets
public IMoney Value { get; set; }
public Coin Coin { get; set; }
public long Confirmations { get; set; }
+ public BitcoinAddress Address { get; set; }
}
public class NetworkCoins
{
@@ -91,7 +92,7 @@ namespace BTCPayServer.Services.Wallets
if (storeId != null)
{
await WalletRepository.EnsureWalletObject(
- new WalletObjectId(new WalletId(storeId, Network.CryptoCode), WalletObjectData.Types.Script, pathInfo.ScriptPubKey.ToHex()),
+ new WalletObjectId(new WalletId(storeId, Network.CryptoCode), WalletObjectData.Types.Address, pathInfo.Address.ToString()),
new JObject() { ["generatedBy"] = generatedBy });
}
return pathInfo;
@@ -360,7 +361,9 @@ namespace BTCPayServer.Services.Wallets
OutPoint = c.Outpoint,
ScriptPubKey = c.ScriptPubKey,
Coin = c.AsCoin(derivationStrategy),
- Confirmations = c.Confirmations
+ Confirmations = c.Confirmations,
+ // Some old version of NBX doesn't have Address in this call
+ Address = c.Address ?? c.ScriptPubKey.GetDestinationAddress(Network.NBitcoinNetwork)
}).ToArray();
}